A máme tady i...OS! - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Seriály

Více seriálů



Software

A máme tady i...OS!

8. července 2010, 00.00 | Se správou MujMacu jsme se domluvili, že v našem seriálu "Nastal čas" už nejen na Kakao, ale také na iPhone OS. Nebudeme se zabývat nejzákladnějšími základy sestavování aplikací; budeme se ale věnovat zajímavým oblastem API.

Se správou MujMacu jsme se domluvili, že v našem seriálu "Nastal čas" už nejen na Kakao, ale také na iPhone OS, pardon, OS X, tedy vlastně iOS (Apple k tomu už přistupuje jako Češi ke jménům ulic – např. taková Vinohradská, přednedávnem Stalinova, dříve Schwerinova, předtím Fochova, původně Jungmannova přijde člověku maně na mysl. Ne že by to byla novinka: připomeňme postupné přejmenovávání OpenStepu na všeliké barevné skříňky, než se ustálilo jméno "Cocoa"...).

Nebudeme se zabývat nejzákladnějšími základy sestavování aplikací pro iPhone/iPad; těm bude v nejbližší době věnován samostatný seriál (možná již ve chvíli, kdy tento článek čtete, je jeho prvý díl k dispozici – v době psaní tomu tak ještě nebylo). Stejně ale, jako se postupně díváme na různé zajímavé oblasti API Cocoa, se budeme věnovat také zajímavým oblastem API iOS (tedy v zásadě UIKitu, neboť sám jazyk, runtime či knihovny Foundation jsou oběma platformám společné, a až na některé okrajové výjimky – např. bloky, jež nebyly k dispozici před OS 4, nebo garbage collector, který není v iOS k dispozici vůbec – není třeba při psaní kódu nižší úrovně mezi Cocoa a iOS rozlišovat).

Dohodli jsme se proto, že bude praktičtější prostě rozšířit záběr našeho stávajícího seriálu "Nastal čas", než zavádět seriál nový.

Komunikace mezi telefonem a počítačem

Abychom se k novému tématu odrazili co nejplynuleji, začneme kódem, který funguje stejně dobře v Cocoa (tedy na počítači s Mac OS X), jako na iPhone: ukážeme si, jak implementovat vzájemnou komunikaci typu peer-to-peer mezi dvěma takovými zařízeními.

Proč tedy vůbec hovoříme o iOSu, když by kód, kterým se budeme zabývat, mohl stejně dobře běžet na dvou Macech? Inu, v Cocoa máme k dispozici pro tento účel daleko pohodlnější a silnější aparát, totiž distribuované objekty; ty ale, bohužel, patří mezi API, které v iOS nenalezneme. Musíme tedy sestoupit níže, na úroveň tříd Foundation, které zajišťují přístup k systému Bonjour a ke komunikačním streamům – a dokonce, jak uvidíme, místy je nutno použít i nízkoúrovňové API Carbon pro práci se sockety. Tento kód by se zjevně nikdo neobtěžoval psát pro komunikaci mezi dvěma Macy; pro "domluvu" mezi Macem a iPhonem (nebo dvěma iPhony) prostřednictvím TCP/IP je ale bohužel nutný.

A co Bluetooth?

Moment, "sockety", "TCP/IP"... cožpak by nebyl pro tento typ komunikace daleko šikovnější Bluetooth?

Inu, byl by... kdyby byl. Pro komunikaci mezi dvěma iPhony, jež jsou dostatečně blízko pro spolehlivé spojení Bluetooth, není nic snazšího: pro implementaci můžeme použít velmi pohodlné služby šikovně navrženého a krajně nevhodně pojmenovaného frameworku GameKit (který slouží ke zcela obecné bluetoothové komunikaci mezi zařízeními, a není vůbec nijak omezen na použití pro pouhé hry), a v podstatě "není co řešit".

Chceme-li však komunikovat mezi dvěma vzdálenými iPhony, nezbývá, než sáhnout po Internetu (tedy TCP/IP); podobně je tomu mezi iPhonem a počítačem – na libovolnou vzdálenost. To proto, že bohužel

• GameKit pro Mac OS X k dispozici není (a patrně nebude);

• naopak iOS nenabízí nízkoúrovňový přístup ke službám Bluetooth – ten zde máme k dispozici jen prostřednictvím vysokoúrovňových služeb GameKitu;

• vlastní implementace protokolu GameKitu je netriviální a problematická: protokol není zdokumentován, a i kdybychom jej "vyhackovali", nemáme žádnou záruku, že naše aplikace bude fungovat i s příštím upgradem iOS.

Zkrátka, s bluetoothem máme prozatím – jde-li o komunikaci mezi iPhonem a Macem – smůlu, protože rozumné řešení zde bohužel neexistuje.

Klient

Ačkoli jde v zásadě o komunikaci typu peer-to-peer, stejně má smysl rozlišovat klienta a server: "server" zde je ten, kdo se přihlásí prostřednictvím systému Bonjour k síti, a kdo nabídne komunikační socket. "Klient" je pak ten, kdo v systému Bonjour nalezne server a připojí se k jeho komunikačními socketu. Vlastní komunikace pak již samozřejmě probíhá z obou stran stejně, prostřednictvím streamů a je čistě "peer-to-peerová".

(Pokud bychom naopak potřebovali plnohodnotnou komunikaci typu klient/server, samozřejmě použijeme na Macu docela standardní Apache, případně ve spojení s vhodným aplikačním serverem – nabízejí se samozřejmě WebObjects nebo Ruby on Rails, zdaleka ne tak dobře navržené a výkonné, ale zato používající mnohem lepší jazyk. iPhone pak se k serveru připojí prostřednictvím protokolu HTTP a opět "není co řešit".)

Kód klienta si ukážeme dříve, protože je jednodušší, a na rozdíl od serveru v něm není zapotřebí páchat carbonovské nízkoúrovňové ošklivosti. V podstatě sestává z následujících kroků:

• klient si vyžádá vyhledání serverů prostřednictvím Bonjour;

• k nalezenému serveru se připojí a sestaví komunikační streamy;

• jejich prostřednictvím pak může přijímat i odesílat libovolná data.

My si zde samozřejmě ukážeme kód mírně zjednodušený; oproti mé přednášce na totéž téma na nedávné konferenci iDevcamp, kde se možná někteří z čtenářů tohoto článku mohli s těmito informacemi již setkat, ale přece jen ukážeme alespoň nejzákladnější ošetření chyb i některé mírně pokročilé funkce: na to samozřejmě na přednášce nebyl dostatek místa. Poznamenejme také, že kód vychází z ukázkové aplikace Apple WiTap, kde jsou služby klienta i serveru propojeny dohromady do skutečně "čistého" kódu peer-to-peer.

Budeme předpokládat, že kompletní komunikaci řídí jediný kontrolér; v praxi tomu tak obvykle asi bude, ačkoli samozřejmě – pokud by tomu složitost aplikace odpovídala – rozložit služby do samostatných řídicích objektů, z nichž jeden by se staral pouze o vyhledání serveru, druhý o navázání spojení a třetí o vlastní komunikaci, by nebyl žádný problém.

Ve složeném řídicím objektu můžeme použít kupříkladu následující instanční proměnné:

@interface ClientController:NSObject <...> {
  NSNetServiceBrowser *browser;
  NSNetService *resolving;
  NSString *currentName,*currentDomain;
  NSInputStream *inStream;
  NSOutputStream *outStream;
  NSTimeInterval lastConnectionTime;
}

Zdaleka ne všechny jsou nutně zapotřebí, některé jen usnadňují komunikaci nebo udržují pomocné informace; to uvidíme hned v implementaci odpovídajících metod. Tři tečky v lomených závorkách jsou samozřejmě deklarace protokolů – s novým přístupem Apple, kdy každý delegát má formální protokol, jemuž musí odpovídat, jich obvykle bývá dlouhá řada.

Začneme inicializací; její součástí je ale také uvolnění "starých" objektů – to proto, abychom mohli bezpečně znovu inicializovat komunikaci např. po chybě nebo poté, kdy třeba výpadek na straně providera dočasně přeruší spojení:

@implementation ClientController
...
-(void)releaseStreams {
  [currentName release]; currentName=nil;
  [currentDomain release]; currentDomain=nil;
  [inStream removeFromRunLoop:
    [NSRunLoop currentRunLoop]
    forMode:NSDefaultRunLoopMode];
  [inStream release]; inStream=nil;
  [outStream removeFromRunLoop:
    [NSRunLoop currentRunLoop]
    forMode:NSDefaultRunLoopMode];
  [outStream release]; outStream=nil;
}
-(void)releaseNetSearch {
  [resolving stop];
  [resolving release]; resolving=nil;
  [browser stop];
  [browser release]; browser=nil;
}
-(void)setupCommunication {
  [self releaseStreams];
  [self releaseNetSearch];
  if ((browser=[[NSNetServiceBrowser alloc] init])) {
    [browser setDelegate:self];
    [browser searchForServicesOfType:@"_FOOBAR._tcp."
      inDomain:@"local"];
  } else
    [self networkFailAlert:@"Cannot set up browser"];
}

Služba releaseStreams uvolní pomocné informace a komunikační streamy (k "run loopu" se vrátíme za chvilku, až je budeme inicializovat). Podobně služba releaseNetSearch přeruší vyhledávání serveru – běží-li – a uvolní odpovídající objekty.

Podstatná zde je služba setupCommunication: ta (zavolá obě služby release... a) vytvoří instanci třídy NSNetServiceBrowser; tato třída je standardní systémové API pro vyhledávání serverů (v terminologii Apple "služeb") prostřednictvím systému Bonjour. Inicializace je triviální: řídicí objekt nastaví sám sebe jako delegáta a spustí vyhledávání požadované služby v lokální doméně. My budeme metodu setupCommunication volat ve chvíli, kdy chceme navázat komunikaci – nejspíše tedy hned po spuštění aplikace z metody applicationDidFinishLaunching:, nebo po explicitní aktivaci prostřednictvím libovolného vhodného GUI.

"Typ" hledané služby je právě to, podle čeho se klient a server navzájem poznají. Podtržítka a tečky okolo jsou vyžadovány protokolem Bonjour; "tcp" na konci je identifikace transportní vrstvy. Vlastní jméno služby – v ukázce výše tedy FOOBAR – můžeme v zásadě použít libovolné, ovšem je třeba se vyhnout konfliktům s ostatními vývojáři a jejich službami. Pro jednoznačnost je ideální reversní DNS, jen pozor na omezenou délku (max. 14 znaků) a také na to, že tečka nepatří mezi znaky, jež zde smíme použít: vhodné jméno by mohlo být např. "cz-mujmac-XYZ". Více detailů (včetně odkazu na možnost registrace jména u obecné registrační autority, jež zcela vyloučí případnou možnost konfliktů) lze nalézt v dokumentaci Apple.

V případě chyby nezbývá, než ji ohlásit uživateli (a případně po nějaké době opakovat pokus): konkrétní obsah metody networkFailAlert: je zbytečné uvádět, každý si něco podobného může napsat jinak podle potřeb dané aplikace.

Pokud se v systému Bonjour podaří požadovanou službu nalézt, ohlásí to NSNetServiceBrowser svému delegátovi prostřednictvím standardní zprávy netServiceBrowser:didFindService:moreComing:. Seznam služeb, jež jsou k dispozici, se může dynamicky měnit; browser proto posílá delegátovi také případnou zprávu netServiceBrowser:didRemoveService:moreComing: v případě, že nalezená služba opět zanikne.

V naší jednoduché implementaci po nalezení služby nastavíme náš universální kontrolér jako jejího delegáta a vyžádáme si připojení k odpovídajícímu serveru ("resolve"). Zmizí-li služba, jejímž prostřednictvím se právě připojujeme, akci prostě ukončíme. Pokud by bylo pravděpodobné, že serverů bude více, zde bychom mohli sestavit jejich seznam a nechat uživatele, ať zvolí ten, který chce použít – tento přístup je vidět v ukázkové aplikaci Apple WiTap.

-(void)netServiceBrowser:(NSNetServiceBrowser*)nsb
  didFindService:(NSNetService*)service
  moreComing:(BOOL)more {
  [resolving=[service retain] setDelegate:self];
  [resolving resolveWithTimeout:0];
}
-(void)netServiceBrowser:(NSNetServiceBrowser*)nsb
  didRemoveService:(NSNetService*)service
  moreComing:(BOOL)more {
  if (resolving && [service isEqual:resolving]) {
    [resolving stop];
    [resolving release]; resolving=nil;
  }
}

Připojování k serveru je ovšem déletrvající akce, a je proto samozřejmě asynchronní – po úspěšném připojení služba (tj. instance NSNetService, již jsme dostali jako argument zprávy netServiceBrowser:didFindService:moreComing:) svému delegátu ohlásí výsledek pomocí standardní zprávy netServiceDidResolveAddress: – její implementace by mohla vypadat následovně (pozn.: jelikož jsme nepoužili timeout, nemůže dojít k tomu, že by se připojení nepodařilo – objekt NSNetService to prostě bude zkoušet tak dlouho, dokud je třeba; pokud bychom ovšem timeout nastavili nenulový, bylo by vhodné implementovat také metodu netService:didNotResolve:).

-(void)netServiceDidResolveAddress:(NSNetService*)ns {
  assert(ns==resolving);
  [[resolving autorelease] stop]; resolving=nil;
  if (![ns getInputStream:&inStream
             outputStream:&outStream])
    [self networkFailAlert:@"Failed connecting"];
  else { // no retain!
    currentName=[ns.name copy];
    currentDomain=[ns.domain copy];
    lastConnectionTime=
      [NSDate timeIntervalSinceReferenceDate];
    inStream.delegate=self;
    [inStream scheduleInRunLoop:
      [NSRunLoop currentRunLoop]
      forMode:NSDefaultRunLoopMode];
    [inStream open];
    _outStream.delegate=self;
    [outStream scheduleInRunLoop:
      [NSRunLoop currentRunLoop]
      forMode:NSDefaultRunLoopMode];
    [outStream open];
  }
}

Zde již fakticky navážeme spojení se serverem, vytvoříme a inicializujeme komunikační streamy; kromě toho si v pomocných instančních proměnných zapamatujeme jméno aktuální služby (k tomu, co toto jméno přesně obsahuje, se vrátíme za chvíli, až se budeme zabývat kódem serveru), její doménu a také moment, kdy byla komunikace navázána: tyto údaje mohou být pro uživatele zajímavé a můžeme je proto chtít zobrazit někde v GUI.

Prvá "finta", u níž se na chvilku zdržíme, spočívá v tom, že streamům, jež nám vrátí standardní služba getInputStream:outputStream:, nepošleme zprávu retain. To proto, že Apple má v implementaci metody getInputStream:outputStream: chybu, a ta vrací streamy "neautoreleasované": pokud bychom jim poslali zprávu retain, nebyly by při pozdějším volání metody releaseStreams uvolněny vůbec!

Pak již jen pro oba streamy nastavíme náš universální řídicí objekt jako delegáta, a přidáme je do "run loopu". My se dosud třídou NSRunLoop a jejími službami nezabývali; jejím úkolem je spravovat tzv. "event loop", tj. přijímat události od nejrůznějších zařízení – od myši a klávesnice přes časovač až po komunikační rozhraní – a presentovat je prostřednictvím odpovídajících zpráv. Zatímco pro běžné zdroje událostí (jako je např. klávesnice) se o nic nemusíme starat a vše je zajištěno plně automaticky, některé další zdroje musíme explicitně v aktuální instanci třídy NSRunLoop zaregistrovat, aby je brala v úvahu a – dojde-li na nich k nějaké události – předala naší aplikaci odpovídající zprávu. Právě k tomu slouží standardní metoda scheduleInRunLoop:forMode: (a když už nadále sledovat zdroj nechceme, metoda removeFromRunLoop:forMode:, s níž jsme se setkali výše, jej z "run loopu" opět odstraní).

Poté, co streamy (zaregistrujeme u aktuálního run loopu a) spustíme pomocí zprávy open, můžeme je volně používat ke komunikaci.

Odesílat data budeme nejspíše pomocí synchronní služby xxx; odpovídající metoda by mohla vypadat kupříkladu takto:

-(void)send:(const uint8_t)message {
  if ([outStream hasSpaceAvailable] &&
      [outStream write:&message
             maxLength:sizeof(const uint8_t)]==-1)
    [self networkFailAlert:@"Failed sending"];
}

Pokud bychom ovšem potřebovali vyšší spolehlivost a nemohli si dovolit ztrátu dat v případě, že zrovna náhodou stream není s to další byte přijmout ("![outStream hasSpaceAvailable]"), případně pokud by se to lépe hodilo do konkrétního návrhu naší aplikace, můžeme data odesílat také asynchronně na základě události NSStreamEventHasSpaceAvailable, již si hned ukážeme.

Pro příjem dat samozřejmě užijeme asynchronního přístupu vždy. Pro ten streamy standardně podporují zprávu stream:handleEvent:, již pošlou delegátu, kdykoli na straně streamu nastane odpovídající událost; my bychom ji mohli implementovat kupříkladu takto:

-(void)stream:(NSStream*)stream
  handleEvent:(NSStreamEvent)eventCode {
  switch(eventCode) {
    case NSStreamEventOpenCompleted:
      [self releaseNetSearch];
      break;
    case NSStreamEventHasSpaceAvailable:
      break; // ignored, sending synchronously
    case NSStreamEventHasBytesAvailable: {
      if (stream==inStream) {
        uint8_t b;
        unsigned int len=[inStream read:&b
                              maxLength:sizeof(uint8_t)];
        if (!len) {
          if ([stream streamStatus]!=NSStreamStatusAtEnd)
            [self networkFailAlert:@"Failed reading"];
        } else
          [self proceedReceivedByte:b];
      }
      break;
    case NSStreamEventErrorOccurred: // fall through
    case NSStreamEventEndEncountered:
      [self performSelector:@selector(setupCommunication)
                 withObject:nil afterDelay:1];
  }
}
@end

Jednotlivé události, jež dostaneme od streamu prostřednictvím argumentu eventCode a jež zde zpracováváme, mohou být

NSStreamEventOpenCompleted: stream byl úspěšně otevřen. V podstatě není zapotřebí dělat nic; my jen definitivně ukončíme vyhledávání serveru (uvědomme si, že tuto událost dostaneme od obou streamů – a podívejme se do implementace metody releaseNetSearch, proč to vůbec nevadí);

NSStreamEventHasSpaceAvailable: stream může přijmout odesílaná data; tuto událost dostaneme od výstupního streamu. Zde ji ignorujeme, protože data – jak jsme si ukázali výše – odesíláme synchronně; pokud bychom ale chtěli využít asynchronního přístupu, použili bychom službu write:maxLength: právě zde;

NSStreamEventHasBytesAvailable: stream přijal data; tuto událost dostaneme od vstupního streamu. Data načteme a (po ošetření případné chyby) zpracujeme v metodě proceedReceivedByte:, jejíž konkrétní implementace samozřejmě závisí na dané aplikaci a proto ji neuvádíme;

• konečně události NSStreamEventErrorOccurred – již dostaneme od streamu při chybě – a NSStreamEventEndEncountered – již stream pošle je-li uzavřen – zde zpracujeme týmž způsobem: naplánujeme prostě nové otevření komunikace (to samozřejmě závisí na konkrétních potřebách dané aplikace; mohli bychom také např. chybu či ukončení ohlásit uživateli a počkat na explicitní aktivaci nového připojení).

To je na straně klienta vše; nic jiného není pro základní úspěšnou komunikaci zapotřebí.

Server

Kód serveru je poněkud složitější, protože na jeho straně je pro vytvoření a aktivaci komunikačního socketu zapotřebí sestoupit k nízkoúrovňovému API Carbon. Na druhou stranu je kód zčásti totožný s kódem klienta; nebudeme si proto již podrobně vysvětlovat to, co jsme si ukázali výše (např. registraci streamů v aktuálním "run loopu").

Podobně jako u klienta budeme předpokládat, že kompletní komunikaci serveru řídí jediný kontrolér; ten by mohl mít kupříkladu následující instanční proměnné:

@interface ServerController:NSObject <...> {
  CFSocketRef ipControlSocket;
  NSNetService* ipControlService;
  NSInputStream *ipControlIStream;
  NSOutputStream *ipControlOStream;  
}

I základní struktura kódu bude podobná jako u klienta: definujeme především dvě metody, setupControlListeningPortAndPublish a removeControlListeningPortAndUnpublish; prvou z nich voláme ve chvíli, kdy chceme zahájit komunikaci (např. z applicationDidFinishLaunching:), druhá slouží k "úklidu" např. ve chvíli, kdy je zapotřebí komunikaci po pádu obnovit. Tu si ukážeme nejdříve; vzhledem k tomu, že na straně serveru je toho k uvolňování více, bude delší a malinko složitější:

@implementation ServerController
...
-(void)removeControlListeningPortAndUnpublish {
  if (ipControlService) {
    [ipControlService stop];
    [ipControlService removeFromRunLoop:
      [NSRunLoop currentRunLoop]
      forMode:RUN_LOOP_COMMON_MODES];
    [ipControlService release]; ipControlService=nil;
  }
  if (ipControlSocket) {
    CFSocketInvalidate(ipControlSocket);
    CFRelease(ipControlSocket); ipControlSocket=NULL;
  }
  if (ipControlIStream) {
    [ipControlIStream removeFromRunLoop:
      [NSRunLoop currentRunLoop]
      forMode:RUN_LOOP_COMMON_MODES];
    [ipControlIStream close];
    [ipControlIStream release]; ipControlIStream=nil;
  }
  if (ipControlOStream) {
    [ipControlOStream removeFromRunLoop:
      [NSRunLoop currentRunLoop]
      forMode:RUN_LOOP_COMMON_MODES];
    [ipControlOStream close];
    [ipControlOStream release]; ipControlOStream=nil;
  }
}

Vlastní inicializaci socketu a přihlášení k Bonjour ale prozatím odložíme do další pomocné metody, jejíž obsah si ukážeme později; prozatím ji jen použijeme a pokud zklame, naplánujeme "za chvilku" nový pokus (samozřejmě není naprosto nutné problém řešit takto; je to jen jedna z mnoha možností, jež se náhodou v praxi celkem osvědčila). Metoda setupControlListeningPortAndPublish je proto velmi jednoduchá:

-(void)setupControlListeningPortAndPublish {
  [self removeControlListeningPortAndUnpublish];
  if (![self doSetupCLPortAndPublishSucceeded])
    [self performSelector:_cmd
               withObject:nil afterDelay:30];
}

Naopak metoda doSetupCLPortAndPublishSucceeded, jež skutečně vytváří a publikuje komunikační socket, je poměrně složitá; proto si její kód rozdělíme na několik bloků, a každý z nich si vysvětlíme zvlášť:

-(BOOL)doSetupCLPortAndPublishSucceeded {
  CFSocketContext sctxt={0,self,NULL,NULL,NULL};
  ipControlSocket=CFSocketCreate(kCFAllocatorDefault,
    PF_INET,SOCK_STREAM,IPPROTO_TCP,
    kCFSocketAcceptCallBack,
    (CFSocketCallBack)&TCPSocketCallBack,&sctxt));
  if (!ipControlSocket) return NO;
  int yes=1;
  setsockopt(CFSocketGetNative(ipControlSocket),
    SOL_SOCKET,SO_REUSEADDR,(void *)&yes,sizeof(yes));

Nejprve vytvoříme komunikační socket a nastavíme potřebným způsobem jeho atributy. Detailní vysvětlení všech argumentů lze nalézt v dokumentaci, ale pro nás na naší úrovni není příliš podstatné; opravdu důležité jsou pouze dva momenty, oba v kódu výše označené tučně: předání odkazu na řídicí objekt (self) v kontextu socketu, abychom jej měli k dispozici v callbacku; a adresa vlastního callbacku – zde TCPSocketCallBack: to je jméno funkce, která se zavolá ve chvíli, kdy se k socketu připojí klient. Jelikož užíváme nízkoúrovňové API Carbon, nejde o zprávu, ale o obyčejnou funkci plain C; její obsah si ukážeme za chvilku.
  struct sockaddr_in addr4;
  memset(&addr4,0,sizeof(addr4));
  addr4.sin_len=sizeof(addr4);
  addr4.sin_family=AF_INET;
  addr4.sin_port=0;
  addr4.sin_addr.s_addr=htonl(INADDR_ANY);
  NSData *a4=[NSData dataWithBytes:&addr4
                            length:sizeof(addr4)];
  if (CFSocketSetAddress(ipControlSocket,(CFDataRef)a4)
                                   !=kCFSocketSuccess) {
    CFRelease(ipControlSocket); ipControlSocket=NULL;
    return NO;
  }

Výše uvedený několikařádkový kód je v nízkoúrovňovém API zapotřebí na jedinou drobnost: nastavení čísla portu, k němuž je socket připojen (sin_port). Nastavíme-li je na nulu – jak jsme právě učinili –, přidělí jádro libovolný volný port. To je obecně výhodné (jen pozor na nastavení firewallu), protože nehrozí, abychom se o pevně zvolené číslo portu "poprali" s jinou aplikací; před publikací socketu prostřednictvím Bonjour ovšem musíme přidělené číslo portu opět zjistit:

  NSData *addr=[(NSData*)CFSocketCopyAddress
                         (ipControlSocket) autorelease];
  memcpy(&addr4,[addr bytes],[addr length]);
  uint16_t port=ntohs(addr4.sin_port);

Za zmínku možná stojí, že kopírování dat do proměnné addr4 je docela zbytečné; stejně dobře bychom mohli port načíst pomocí přetypování rovnou z [addr bytes]. Jde ovšem o nepodstatnou drobnost; tento kód je převzatý beze změny z příkladu WiTap, a nestálo za to jej přepisovat ☺

  CFRunLoopSourceRef source=CFSocketCreateRunLoopSource
                (kCFAllocatorDefault,ipControlSocket,0);
  CFRunLoopAddSource(CFRunLoopGetCurrent(),source,
                kCFRunLoopCommonModes);
  CFRelease(source);

"Run loop" již známe; zde je rozdíl jen v tom, že namísto streamů v něm registrujeme socket, a že namísto objektového rozhraní NSRunLoop používáme nižší úroveň, Carbon (carbonovský CFRunLoop je součástí objektového NSRunLoopu, ale není s ním totožný).

  if (!(ipControlService=[[NSNetService alloc]
      initWithDomain:@"local" type:@"_FOOBAR._tcp."
      name:nil port:port])) return NO;
  [ipControlService setDelegate:self];
  [ipControlService scheduleInRunLoop:
    [NSRunLoop currentRunLoop]
    forMode:RUN_LOOP_COMMON_MODES];
  [ipControlService publish];
  return YES;
}

Nakonec vytvoříme objekt NSNetService, jehož prostřednictvím server svou službu publikuje tak, aby ji mohli klienti nalézt prostřednictvím protokolu Bonjour. Stran "typu" FOOBAR vizte výše. Jméno "name" je právě místo, v němž můžeme určit, jaké jméno uvidí klient, nahlédne-li do atributu name získané služby; užijeme-li – stejně jako zde – hodnotu nil, automaticky se použije jméno počítače.

Vzhledem k tomu, že socket po připojení klienta volá "callback" (v našem případě funkci TCPSocketCallBack, jejíž kód si ukážeme za chvilku), v podstatě není zapotřebí služeb delegáta; nastavujeme jej pouze proto, abychom mohli zachytit případnou chybu při publikování služby a – pokud by nastala – po krátké pause inicializaci zopakovat (nebo ohlásit chybu, atakdále, atakdále):

-(void)netService:(NSNetService*)sender
  didNotPublish:(NSDictionary*)errorDict {
  [self performSelector:
    @selector(setupControlListeningPortAndPublish)
    withObject:nil afterDelay:30];
}

"Callback" TCPSocketCallBack se automaticky zavolá ve chvíli, kdy se ke komunikačnímu socketu připojí klient. V něm musíme opět – a naštěstí již naposledy – sáhnout po nízkoúrovňovém API Carbon, jehož vinou je kód poměrně komplikovaný, ačkoli se toho vlastně moc neděje: jen vyrobíme pro daný socket potřebné komunikační streamy (na rozdíl od obdobné služby Apple, již používáme hotovou na straně klienta, je nezapomeneme uvolnit ☺), a předáme je inicializační metodě connectInputStream:outputStream: řídicího objektu (jehož adresa byla uložena v kontextu socketu):

static void TCPSocketCallBack(CFSocketRef socket,
  CFSocketCallBackType type, CFDataRef address,
  const void *data, void *info) {
  if (type!=kCFSocketAcceptCallBack) return;
  ServerController *self=(ServerController*)info;
  CFSocketNativeHandle nativeSocketHandle=
    *(CFSocketNativeHandle*)data;
  CFReadStreamRef readStream=NULL;
  CFWriteStreamRef writeStream=NULL;
  CFStreamCreatePairWithSocket(kCFAllocatorDefault,
    nativeSocketHandle,&readStream,&writeStream);
  if (readStream && writeStream) {
    CFReadStreamSetProperty(readStream,
      kCFStreamPropertyShouldCloseNativeSocket,
      kCFBooleanTrue);
    CFWriteStreamSetProperty(writeStream,
      kCFStreamPropertyShouldCloseNativeSocket,
      kCFBooleanTrue);
    [self connectInputStream:(NSInputStream*)readStream
          outputStream:(NSOutputStream*)writeStream];
  } else {
    [self removeControlListeningPortAndUnpublish];
    [self performSelector:
      @selector(setupControlListeningPortAndPublish)
      withObject:nil afterDelay:30];
  }
  if (readStream) CFRelease(readStream);
  if (writeStream) CFRelease(writeStream);
}

Tím jsme v podstatě hotovi; jen pro úplnost si ukážeme inicializační metodu connectInputStream:outputStream: řídicího objektu, a také jeho kód pro vlastní komunikaci prostřednictvím streamů, jež tato metoda dostane (od výše popsané funkce TCPSocketCallBack) v argumentech; popisovat funkci jejich kódu si ale už nebudeme, neboť jde o služby dávno známé z klienta (a v případě, kdy obě části implementujeme v témže kódu, který může sloužit stejně dobře jako server i jako klient – jak tomu je např. v příkladu Apple WiTap –, můžeme jejich kód samozřejmě napsat pouze jednou a šikovně vícekrát využít):

-(void)connectInputStream:(NSInputStream*)istr
             outputStream:(NSOutputStream*)ostr {
  [self removeControlListeningPortAndUnpublish];
  [ipControlIStream=[istr retain] setDelegate:self];
  [ipControlIStream scheduleInRunLoop:
    [NSRunLoop currentRunLoop]
    forMode:NSDefaultRunLoopMode];
  [ipControlIStream open];
  [ipControlOStream=[ostr retain] setDelegate:self];
  [ipControlOStream scheduleInRunLoop:
    [NSRunLoop currentRunLoop]
    forMode:NSDefaultRunLoopMode];
  [ipControlOStream open];
}
-(void)send:(const uint8_t)message {
  if ([ipControlOStream hasSpaceAvailable] &&
      [ipControlOStream write:&message
             maxLength:sizeof(const uint8_t)]==-1)
    [self networkFailAlert:@"Failed sending"];
}

-(void)stream:(NSStream*)stream
  handleEvent:(NSStreamEvent)eventCode {
  switch (eventCode) {
    case NSStreamEventHasBytesAvailable:
      if (stream!=ipControlIStream) break;
      uint8_t b;
      unsigned len=[ipControlIStream read:&b
                         maxLength:sizeof(uint8_t)];
      if (!len) {
        if ([stream streamStatus]!=NSStreamStatusAtEnd)
          [self networkFailAlert:@"Failed reading"];
      } else
          [self proceedReceivedByte:b];
      break;
    case NSStreamEventErrorOccurred:
    case NSStreamEventEndEncountered:
      [self setupControlListeningPortAndPublish];
  }
}
@end

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Tipy a Triky  

 » Rubriky  » Začínáme s  

 » Rubriky  » Software  

Diskuse k článku

 

Vložit nový příspěvek   Sbalit příspěvky

 

neni nad to, kdyz Ondra pusti do programovani :)

Autor: gargous Muž

Založeno: 08.07.2010, 01:16
Odpovědí: 0

Tak jsem od Ondreje dlouho necetl zadny zajimavy clanek, ale timhle me uplne posekal. Nejen, ze ukazal jak jsou pravdiva slova o tom, ze na iPhone jede "orezany" MacOS, ale navic to ukazal v praxi. Klobouk dolu Ondreji, zase jsi zavalel. A predpokladam, ze uz jsi si napsal ve spolupraci se Cydii nejeden zajimavy projekt a odzkousel sis, ze to maka.

Diky gargous

Odpovědět na příspěvek

RE: neni nad to, kdyz Ondra pusti do programovani :)

Autor: OC Muž

Založeno: 08.07.2010, 01:25

Díky za vlídná slova.

On ten Mac OS X na iPhone není ani tak moc ořezaný (ačkoli samozřejmě leccos tam skutečně není), jako spíše pozamykaný, se zákazem vstupu na každém rohu :(

"... a chtěl bych zapomenout na všechny ty zákazy vjezdů rozsetý po celé Telegraph road ..."

Odpovědět na příspěvek

RE: RE: neni nad to, kdyz Ondra pusti do programovani :)

Autor: hroch32 Muž

Založeno: 08.07.2010, 20:09

Článek je skutečně pěkný!

A náhodou z iPhonu je ořezáno pár věcí, které by mohly vypadnout i z OS X, třeba takový Carbon :-) Jestli ten přežije Sněžharta, tak mě Appláci zklamou.

Odpovědět na příspěvek

CoreFoundation _neni_ Carbon

Autor: kuapi Muž

Založeno: 08.07.2010, 10:30
Odpovědí: 0

Pan Cada je sice zkusenym programatorem v Objective-C, ale stale si i po 10 letech plete nejnovejsi ceckove API CoreFoundation se zastaralym Carbonem...

Odpovědět na příspěvek

RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 08.07.2010, 18:43

Je to přesně stejné API: stejně nešikovné, stejně nepraktické, stejně blbě navržené a stejně zbytečně "ukecané".

Ale máte naprostou pravdu v tom, že jaksi "de iure" se jmenuje jinak; v tom se velice omlouvám za mystifikaci.

Tedy prosím čtenáře -- nahraďte si pojem "Carbon" pojmem "CoreFoundation". Ta špatná zpráva je, že, bohužel, i pod jiným jménem tato senkrovna stále stejně páchne.

Odpovědět na příspěvek

RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 08.07.2010, 19:58

Dovolím si mírně nesouhlasit. Ne s tím, že Core Foundation je krám (zase ve srovnání jak s čím, oproti Win32..... :-) ), nýbrž s jeho odvozením od Carbonu. Carbon jakožto pozůstatek starších versí OS je prasárna ještě mnoooohem ohavnější. Core Foundation vzniklo za tím účelem, aby základní systémové služby bylo možné volat z Cocoa i ze skutečného Carbonu.

CF navíc rozšířilo služby Carbonu oproti původním knihovnám, jež byly obsaženy ve starých systémech. Ono mnoho z CF je naopak odvozeno z původního Foundation, jen je to implementováno v čistém C, takže to prostě nemůže být tak pohodlné na používání a musí to být ukecanější.

Hlavní chyba CF IMHO není v jeho navržení jako takovém, ale:
a) v tom, že vůbec existuje, aneb o co snadněji by se nám žilo, kdyby Appláci jako základní vrstvu použili Foundation a nad ní postavili nějaké C API, které by využíval Carbon
b) v praktické neexistenci jakéhokoliv rozumného ošetření chyb – jakákoliv chyba vede k nekontrolovatelnému a nepředvídatelnému chování aplikace. V lepším případě se vrátí kCFUnknownError. Oproti tomu je naopak původní Carbonovské (a jiná novější C či C++ API, třeba Core Audio) inteligentnější, neb většina funkcí v něm vrací chybovou hodnotu (včetně setterů, což je ROFL :-) ). Tím nechci říct, že by daná API při hrubších chybách netrpěla vším, čím nízkoúrovňová C/C++ API trpívají.... Jen je to u nich zpravidla ošetřeno lépe, než u samotného CF.

A jen jako detail na závěr, spousta věcí kolem sítí, které nejdou udělat pomocí Cocoa ani pomocí CF, jde udělat pomocí CFNetwork (on je to teda spíš Netvor...) frameworku, který je součástí Core Services a je, jak jinak, taktéž v C. Ovšem proč jsou některé služby součástí CF a jiné CS, respektive klíč k tomuto rozdělení, je mi zcela záhadou...

Odpovědět na příspěvek

RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 08.07.2010, 20:28

Tak jistě, je to otázka terminologie; podle mého ale pokud něco vzniklo "za tím účelem, aby základní systémové služby bylo možné volat ... ze skutečného Carbonu", pak je to "od něj odvozeno".

A mimochodem, stojí za to zdůraznit, že samo plaincéčkové CoreFoundation je _DALEKO_ pomalejší, než původní čistý Foundation (ten současný je pomalejší než CF, ale to jen proto, že je shellem kolem CF). Ona si spousta lidí myslí, že CF je rychlejší "protože je to plain C" a že Foundation je pomalejší "protože to jsou objekty", ale opak je pravdou.

Tj. je z toho zřejmé, jak totálně idiotské rozhodnutí bylo dělat CF a nad ním stavět Carbon i Cocoa (což jsem psal už tehdy a psal jsem to i Applům, to no avail :( ) -- když už museli mít Carbon, měli udělat naopak tenký plaincéčkový shell, který by do sebe balil služby Foundation, a který by se používal jen v tom prevítu...

Odpovědět na příspěvek

RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 09.07.2010, 10:03

No to právě ne, ono je fakt odvozené (strukturou a z velké části i logikou) od Foundation. Carbon samotný vypadá úúúúplně jinak. Koneckonců z kódu v Carbonu často vůbec nejsou funkce CF volány, programuje (tedy naštěstí většinou *programovalo*) se prostě dál v té předpotopní hrůze a to, že takový kód funguje, je prostě výsledkem nějaké vrstvy, která z volání Carbonovských fcí udělá volání CF. Něco málo o Carbonu musím tušit, neb máme ještě několik takových ohavností v portfoliu, a s CF ta hrůza skutečně nemá po stránce návrhu absolutně nic společného.

Tohle by mě také enormně zajímalo. Mmchd, jak je to s tím, že základní třídy Foundation byly i na OpenStepu v C? Chápu, že nějakému tomu Cčku se člověk na takhle nízké úrovni nevyhne, otázkou je kvantita těch situací.

Však přesně to jsem psal :-) Obávám se ale, že by to v praxi znamenalo zpomalení Carbonovských šmejdů, což si Appláci nechtěli dovolit, protože už tak jely aplikace v Carbonu na OS X pomaleji než na Classicu...

Odpovědět na příspěvek

RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 09.07.2010, 14:00

Ale ne, nic nebylo v plain C. Přesněji: pár podpůrných funkcí v plain C samozřejmě bylo, je a bude -- např. věci jako NSAllocateObject apod., -- ale nic na úrovni ohavností typu současného CF, toll-free bridging etc. A NSArray byla NSArray byla NSArray (tedy byl to cluster, ale "opravdický"; ty überprasečiny s NSCFArray, jež může podle nálady být NSArray nebo NSMutableArray a nelze to nijak poznat neexistovaly).

Odpovědět na příspěvek

RE: RE: CoreFoundation _neni_ Carbon

Autor: kuapi Muž

Založeno: 08.07.2010, 23:18

Vsechno spatne.
Tedy vsechno uplne ne, ale chtel jsem hned na zacatek prilakat pozornost...
Odpovim jakoby obema (hroch32 i OC):

1. Kdyby bylo CoreFoundation "stejne blbe navrzene", pak by to nerikalo nic pekneho ani o Cocoa. Neni od veci zminit, ze za CF stoji ti sami lide, kteri stoji za Foundation a AppKitem. CF prebira nektere zakladni principy z jeho starsiho objektovyho bratricka, nektere musely byt kvuliva cecku upraveny a nektere byly zavedeny nove.

2. Nebylo-li by CF, bylo by neco jineho. A to nikoliv proto, ze by museli delat nejakou ulitbu Carbonu, ale i pro ciste Cocoa by s nejvetsi pravdepodobnosti neco takoveho existovalo. Duvodem je hlavne navrh zakladnich trid typu NSString, NSDictionary, NSSet apod. spolu s jejich menitelnymi variantami, ktere by se v Obj-C musely psat zvlast. A tak i ve starem OpenStepu pro tyto tridy existovalo spolecne ceckove API (prefix NSSimple). Jedine, co se udelalo, bylo, ze se tyto implementace vytahly do noveho frameworku. Vice o tom viz primo od Chrise Kana zde: http://bit.ly/bdhW9Y; do tehoz vlakna ostatne pan Cada sam prispival, stoji za to precist cele.
Suma sumarum -- CF nejen ze neni Carbon, ale ani nebyl (jen) kvuli Carbonu vyvinut.

3. K pomalosti CF viz odkaz tamtez -- rad bych videl nejake testy, prokazujici, ze "tridy" CF jsou _DALEKO_ pomalejsi nez puvodni cisty Foundation. Moc tomu neverim; ale i kdyby se prokazalo, ze pomalejsi jsou, ptal bych se, zda to treba nebylo vykoupeno necim jinym (nizsi spotreba pameti, orientace na rychlost pri jinych use-casech, bezpecnost vuci SMP, ulitby pro Garbage Collector, atd.).

4. CFNetwork je sice separatni framework, ale svou povahou je primo odvozen od principu CF.

5. CF je low-level API, ktere nabizi pomerne velmi vyrazne customizovatelne tridy. Proto je sice ukecanejsi nez Cocoa, ale nabizi veci znalemu programatorovi casto vice moznosti, jak neceho docilit.

6. Osetreni chyb bylo v releasove verzi CF zamerne vynechano. CF se v tomto chova podobne jako klasicke ceckove funkce -- vrazite-li do memcpy jako 'dest' NULL, jste zpravidla v pr...i. Vsechny argumenty techto funkci, stejne jako funkci CF, je nutno nechat proletet nejakou kontrolou jeste pred jejich volanim. Delaji to tak tridy v Cocoa (resp. ony casto vyuzivaji "privatnich" funkci CF, ktere to udelaji za ne), musi to tak delat i ostatni. Pokud nekdo chce, muze pouzit debugovou verzi knihovny, kde se tech kontrol provadi habadej.

Odpovědět na příspěvek

RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 08.07.2010, 23:43

Marcel Weiher dělal benchmarky, půjde to dohledat někde v maillistech; tady je excerpt z mého archivu:

===
In fact, the pre-CF Foundation was not just faster than Foundation with CF, it was also faster than CF is by itself. The belief that CF is somehow faster because it is C, rather than Objective-C, is as widespread as it is false. That doesn't mean that CF isn't currently faster than Foundation, but that is purely because of the decision to base Foundation on CF and not the other way around.

Message dispatch tends to be vastly overrated as a performance issue, leading to absurdities such as using CFDictionaries of CFNumbers instead of a simple object with an integer instance variable because "C is fast". Dispatch is really quick, and in a pinch you can always IMP-cache.

In general, CF objects tend to be fairly generic and heavyweight, and the genericity is what kills you, performance-wise. For example, getting the integer value out of a CFNumber takes 25ns via CFNumberGetValue(). Sending -intValue to an object that is implemented with a simple integer instance variable takes 6.6ns, so almost 4x faster. IMP-cached, that goes down to 3.7 ns, so around 6x faster. Had we stored this CFNumber in a CFDictionary, we would have needed another 51ns to get it out of there, total 78 ns or 12x slower than the simple message-send or 20x slower than the IMP- cached message send.
===

Odpovědět na příspěvek

RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: kuapi Muž

Založeno: 09.07.2010, 22:47

Marcel Weiher sice mohl delat benchmarky, ale urcite v nich netestoval rychlost CF vs puvodni Foundation, a to jste zapomnel dodat. Co v tom prispevku predlozil, bylo srovnani CFNumberGetValue a zaslani intValue zpravy do nejakeho jeho MPW objektu. Problem je ten, ze pokud je ten objekt implementovan "with a simple instance variable", pak -- jak predpokladam -- nebude ani nahodou odpovidat komplexnimu API CF/NS Number. Zaslanim zpravy intValue do NSNumber se mimo jine kontroluji rozsahy a prip. se provadi i konverze. Implementace takove funkce tedy nespociva jen v tom, ze vratim atribut pretypovanej na int, coz zrejme Weiher v te sve tride dela (ostatne stejne jako implementace ciselnych trid v Jave). Pak se neni cemu divit, ze nasel takovy nepomer.

Aby mi bylo spravne rozumeno -- osobne si myslim, ze CF _je_ pomalej, ale nikoliv ve srovnani s (puvodnim) Foundation, to je nesmysl. Rovnez je nesmysl davat mu za vinu prilisnou "genericitu" -- to neni vysada CF, ale i Cocoa trid obecne, a to _zejmena_ kvuliva
i) rozdeleni na nemenitelne a menitelne objekty,
ii) velmi rozsirene "clustrovosti",
iii) relativne volnemu API s ohledem na vykonnost.
CF, jelikoz nedela nic jinyho, nez ze se v tomto drzi principu Cocoa, je pak zakonite napsano dosti obecne.
Bod i) je problem zejmena s ohledem na bod ii) -- protoze jednotlive konkretni podtridy menitelnych variant uz samozrejme nemohou dedit od konkretni podtridy nemenitelne varianty. Pritom ale prinejmensim primitivni metody nemenitelne tridy musi implementovat obe dve stejne. Nedivim se programatorum v applu resp. drive v NeXTu, ze si udelali neco na zpusob CF, kde do jedne tridy narvali implementaci jak nemenitelne, tak menitelne varianty...
Bod ii) -- cluster tridy jsou kuprikladu zamerne navrzeny a napsany pomerne obecne, uz jen proto, aby pocet primitivnich metod zustal na rozumne nizkem poctu. A opet, kdyz uvazime opravy chyb, drobnou udrzbu kodu a koneckoncu i rozsahlejsi zmeny v kodu, pak neni prilis prakticke kvuli optimalizaci na rychlost prepisovat v konkretnich potomkach i vsechny mozne ne-primitivni metody abstraktniho predka. Proto se taky napr. pridani jednoho prvku do NSArray nakonec rozvine v nejakou formu "replaceRange" metody, ktera je navic napsana tak obecne, az z toho cloveka zaliva pot. A tak je to se vsemi add/append/remove/delete operacemi.
K bodu iii) lze zminit jako priklad opet NSArray. Nikde neni garantovano, ze jeho operace budou mit takovou a takovou casovou slozitost (u CFArray se ale docist uz lze, ze napr. pristup k prvku pole muze vzit az O(log n) casu, i kdyz vetsinou bude konstantni). Toho pak samozrejme applaci vyuzivaji a zatimco pro mensi policka je NS(CF)Array implementovano jako deque, po prekroceni jistyho limitu se meni na tusim ternarni strom. Uzivateli pritom zcela skryto. Takovyhle machrovinky ovsem neco stoji -- napr. vyrazne slozitejsi a obecnejsi kod.

Neboli, suma sumarum, CF je obecne prave natolik, nakolik je obecne API Cocoa.

Odpovědět na příspěvek

RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 10.07.2010, 14:22

Ježíšmarjá... člověče, podívejte se na princip clusterů.

(a) z hlediska koncepce zde jde právě o to, že na rozdíl od šíleného CF mamuta, který obsahuje vše na jedné hromadě a proto je strašlivě překombinovaný, cluster obsahuje pro každou variantu (např., ale zdaleka nejen, dequeue/strom) jednu malou, štíhlou a optimalisovanou konkrétní třídu. V jejímž kódu právě na rozdíl od CF _není_ bordel odpovídající všem těm ostatním;

(b) pokud to náhodou zapotřebí je, je ObjC dostatečně flexibilní na to, aby fakticky nějaká BlaBlaMutableBleBle sdílela některé primitivní metody s nějakou BlaBlaImmutableBleBle, ačkoli jedna není dědicem druhé. (Zda toho programátoři NeXT v clusterech využili nebo ne, to, upřímně řečeno, nevím. Ale vím, že problém to skutečně není; je ale možné, že to nestálo za to.)

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: kuapi Muž

Založeno: 11.07.2010, 10:40

ad a) Ale vzdyt ano. A protoze je to napsane v C a zaroven panove z NeXTu/Applu nechteli mit v CF dedicnost ci funkcni pointery obecne (i kdyz ve streamech tusim udelali vyjimku), je z toho takovej humbuk. Ja to nerozporuju, jen rikam, ze je to jeden z dusledku toho, jak je navrzene API Cocoa.
Mimochodem, pokud se domnivate, ze "cistej" cluster v Objective-C by byl rychlejsi, tak to si dovolim pochybovat. I kdyz pravda, pro jiste singularitky typu prazdne/1-prvkove pole, pro nez by se udelala specialni podtrida, by to nejaky pozitivni vyznam asi mit mohlo. Ale jinak, pri zachovani stejne funkcionality, by se nakonec stejne dospelo k podobne vykonnosti -- velmi zjednodusene, rozliseni by uz nebylo na bazi prikazu switch ala CF, nybrz na bazi dedicnosti. Ta hlavni cast implementace by ale zustala...

ad b) To by me zajimalo... jak se toho da dosahnout?

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 13.07.2010, 01:32

(a) Mně přijde logické a pochopitelné, aby čistý cluster byl efektivnější (proto, že "rozlišení na úrovni tříd" nestojí vůbec nic, naopak "rozlišení switchem" stojí leccos). Mohu se samozřejmě mýlit. Ale Marcelovy benchmarky tu hypotézu podporují.

(b) Není problém, aby různé třídy sdílely společný IMP (tím jsem si absolutně jist, mám vyzkoušeno), nebo i rovnou celou položku Method (tím si nejsem absolutně jist, nemám vyzkoušeno, ale jeví se to pravděpodobným).

Odpovědět na příspěvek

RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 09.07.2010, 09:54

ad 1) Ano, CF (bez jiných obdobných frameworků) se tváří jako light-weight plain C verse Foundation, ale prostě není. Jestli za tím stojí Ti samí lidé neuším, ale ono něco jiného jsou lidé, kteří framework upravují a rozšiřují. A něco jiného lidé, kteří navrhli jeho filosofii a základní strukturu a implementaci. Nicméně osobně nejsem zrovna povolanou osobou řešit toto téma, protože kód CF jsem sice viděl, ale nejsem zas takový Cčkař, abych si o něm udělal kvalifikovaný názor.

2) Rád si počtu, děkuji. Nicméně stále si myslím, že kdyby to udělali obráceně, tzn. malý wrapper kolem Foundation, který by se volal z Carbonu, bylo by to z dlouhodobého hlediska lepší.

3) Neb jsem neměl možnost vyzkoušet původní OpenStep, tak se k rychlosti nejsem schopen sofistikovaně vyjádřit. Nicméně o úlitbu pro jaký Garbage Collector by se mělo jednat? Copak on nějaký na OS X 10.0 byl?

4) Ano, takových frameworků je po systému spousta a striktně řečeno – všechny jsou odvozeny od Core Services. Jen mi zrovna v případě těchto dvou (CF a CFN) není jasné, proč některé "třídy" jsou tam a jiné onde. Ale třeba to mělo nějaký rozumný důvod; doufám, že ano :)

5) Ale jen a jenom proto, že se Appláci neobtěžovali ty věci implementovat i pro Cocoa! Jak dlouho nám slibují CFNetworkConfiguration jako high-level API? Minimálně od Tygra... a mohl bych pokračovat. BTW, nic proti, ale jak je *obecně* možné mít víc funkcí ve "třídě" v plain C než ve třídě Objective-C, když Obj-C je plain C + objekty? To nedává smysl, principielně je tomu samozřejmě naopak. Čili platí - je to jen a jen o lenosti Applu implementovat mnohé služby jako high-level API.

6) Jenže v tom je právě ten zásadní rozdíl při programování v Cocoa a v CF! O tom to celé je. Kód, který mi v Cocoa zabere 3 řádky a return, se v CF klidně roztahne na třicet a to se ještě modlím, jestli jsem nějakou kontrolu nevynechal. V Obj-C, když očekávám nebo se bojím chyby v konkrétním místě, tak to hodím do @try bloku a jsem vcelku vysmátý. V CF místo toho napíšu hromadu ifů a podobných serepetiček. Ve výsledku pak veškerou rychlost plain C ztratím, a to i přesto, že ošetření výjimek v Obj-C je poměrně žrout výkonu.

Navíc mnohá jiná C/C++ API ošetření mají (třeba Core Audio), takže důvod, proč je CF nemá, mi poněkud uniká... leda, že by se s tím prostě někomu nechtělo dělat. Protože proč jinak dávat programátorům do ruky framework, který a) musejí používat, ať se jim to líbí nebo ne; b) je natolik nebezpečný, že chybě při práci s ním se pravděpodobně neubrání ani zkušení programátoři, takže jeho používání pravděpodobně povede k méně stabilním aplikacím.... Tohle rozhodnutí prostě nechápu.


PS: Děkuji za pěknou diskusi, čtivo na večer a náměty k přemýšlení. A směle do mě :)

Odpovědět na příspěvek

RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: OC Muž

Založeno: 09.07.2010, 14:02

P.S. "ošetření výjimek v Obj-C je poměrně žrout výkonu"

Už ne, předělali to. Teď je drahý throw, ale try nestojí skoro nic.

Odpovědět na příspěvek

RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 09.07.2010, 22:40

Tak to jsi mě velice potěšil, díky za info :)

Odpovědět na příspěvek

RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: kuapi Muž

Založeno: 09.07.2010, 22:53

Uz jen kratce, castecne jsem snad odpovedel i v reakci na Ondru Cadu.

1. Ano, stoji za tim prakticky ti sami lide -- Chris Kane, Ali Ozer, Douglas Davidson a dalsi.

2. Ale vzdyt primo dle slov Chrise Kanea meli uz v dobach NeXTu, tedy pred Mac OS X, neco jako CF, akorat to bylo zcela skryto a nikdo o tom nevedel. Ono to dava smysl i proto, ze se takovej framework pak da celkem snadno pouzit i pro Win32 aplikace od Applu.

3. V 10.0 samozrejme ne, ale ve zdrojacich CF lze vyhledat, ze s ohledem na vicejadrove masiny ci prave GC se v prubehu (poslednich) let delaly v kodu rozlicne zmeny. A ty samozrejme taky mohly mit nejaky vliv na vykonnost.

5. Ne ne, tady si nerozumime. U Cocoa jde predevsim o pomerne obecne a pritom jednoduche pouzivani. U CF zvolili jinej pristup -- vetsi moznost customizace (kterou ostatne oni sami z vyssich vrstev Cocoa vyuzivaji), ale samozrejme s tim take jdouci slozitejsi a delsi kod. Co lze v Cocoa udelat na jedno zaslani metody, to se v CF casto musi delat trochu (vice) sloziteji a zdlouhave... jedno pitome performSelector:onThread se v CF rozvine do neceho, co snad ani nechci videt, staci si to jen predstavit :). (Ostatne napr. nahled do kodu CFRunLoop, ktery by si i tady zahral svoji roli, bych doporucil jen silnym naturam, to je skutecne masakr.)

6. Ano, v tom je rozdil. CF ma proste jinou filosofii.

Odpovědět na příspěvek

RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 11.07.2010, 23:07

2) Tohle je poměrně jednoduše vyřešitelný spor (teda mezi OC a kuapi, hroch o tom neví dost, bo je moc mladej :) ) – OpenStep byl open source, čili stačí se podívat do jeho kódu. Nicméně to dle mého prakticky vylučuje \"...akorat to bylo zcela skryto a nikdo o tom nevedel...\". Oni by se to lidi dost rychle dozvěděli :) Ale třeba se pletu.

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: CoreFoundation _neni_ Carbon

Autor: hroch32 Muž

Založeno: 12.07.2010, 00:33

Hroch o tom evidentně ví prd, neb OpenStep byl open standard a nikoliv open source. Omlouvám se.

Odpovědět na příspěvek

Šlo by to ?

Autor: Krtek Muž

Založeno: 08.07.2010, 18:36
Odpovědí: 0

Asi se zeptam jako blbec ale moje programování skončilo kdysi v basicu na 16bitech. Bylo by teda možný naprogramovat pro iPhone aplikaci která dokáže přez bluetooth poslat fotky z jeho foťáku do počítače jako to dělá každej normální mobil s BT a foťákem ?

Odpovědět na příspěvek

RE: Šlo by to ?

Autor: OC Muž

Založeno: 08.07.2010, 18:45

Já se musím přiznat, že bohužel neznám ten protokol, který zde fotoaparáty používají -- je to nějaký standard?

Nicméně téměř jistě zní odpověď "ne bez jailbreaku" bez ohledu na výše zmíněné: jak píši v článku, iOS v současnosti "nepustí" programátora k Bluetoothu jinak než přes GameKit.

Odpovědět na příspěvek

 

 

Vložit nový příspěvek

Jméno:

Pohlaví:

,

E-mail:

Předmět:

Příspěvek:

 

Kontrola:

Do spodního pole opište z obrázku 5 znaků:

Kód pro ověření

 

 

 

 

Nejčtenější články
Nejlépe hodnocené články
Apple kurzy

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: