Ubicumque dulce est, ibi et acidum invenies... - 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ů



Začínáme s

Ubicumque dulce est, ibi et acidum invenies...

8. července 2008, 00.00 | Objective C 2.0 je krásná věc a přináší – zvláště pro ty, kdo si mohou dovolit se omezit na čtyřiašedesátibitové prostředí – řadu nezanedbatelných výhod, z nichž asi nejvýraznější je naprosté odstranění problémů s "fragile class". Jenže bohužel, přináší také nové problémy....

Objective C 2.0 je krásná věc a přináší – zvláště pro ty, kdo si mohou dovolit se omezit na čtyřiašedesátibitové prostředí – řadu nezanedbatelných výhod, z nichž asi nejvýraznější je naprosté odstranění problémů s "fragile class". Jenže bohužel, přináší také nové problémy.

Zkrátka a dobře, v novém překladači jsou – bohužel – chyby.

Už jsme se o nich zmínili v diskusi k jednomu ze starších článků v našem seriálu; jelikož však jde o poměrně závažnou záležitost – jedna z chyb může zavinit pád aplikace! – stojí za to se na věc podívat znovu v samostatném článku, a podrobněji.

Syntetizované properties mohou shodit aplikaci!

Asi nejnepříjemnějším problémem Objective C 2.0 je to, že se za určitých podmínek syntetizované properties dokáží "poprat" až tak zásadně, že to vede k pádu celého procesu.

Přesný mechanismus problému není (mimo firmu Apple ;)) známý – alespoň nakolik vím; zjevně ale nějak souvisí s generovanými accessory pro přístup k objektovým properties, neboť nastává pouze v případě properties typu "copy" nebo "retain", ale nikoli u properties typu "assign". Souvisí také s dědičností.

Problém nastane ve chvíli, kdy dědičnost uloží "vedle sebe" dvě syntetizované instanční proměnné, z nichž ta v následníkovi je typu "odkaz na objekt" s netriviálním accessorem ("copy" nebo "retain"). Typ té v kořenové třídě je poměrně nepodstatný; stačí, aby obsahoval hodnotu, jež není korektním odkazem na objekt (z toho je vidět, že základem problému je to, že generovaný accessor nesmyslně "sáhne" na tuto hodnotu jako na objekt).

Příklad

Ukažme si nejprve nejjednodušší – nakolik je mi aspoň známo – příklad situace, v níž dojde k pádu: instanční proměnná základní třídy nabude "neobjektové" hodnoty, a následující přístup k instanční proměnné podtřídy proces "shodí":

130 /tmp> >crash.m
#import <Cocoa/Cocoa.h>

@interface X:NSObject
@property long x; //1
@end
@interface Y:X
@property (retain) id y;
@end
@implementation X
@synthesize x;
@end
@implementation Y
@synthesize y;
-(void)check {
self.x=1; //2
NSLog(@"so far so good...");
NSLog(@"oops: %x",self.y);
}
@end
int main(int ac,char **av) {
[NSAutoreleasePool new];
[[Y new] check];
return 0;
}
131 /tmp> cc -Wall -framework Cocoa crash.m -arch x86_64 && ./a.out
2008-04-27 19:29:21.505 a.out[14969:10b] so far so good...
zsh: segmentation fault  ./a.out
132 /tmp> 

Na řádku označeném //1 můžeme použít libovolný typ (včetně typu id), a nic se na věci nezmění; podstatná je "neobjektová" hodnota, dosazená na řádku, označeném //2. Pokud použijeme "objektovou" hodnotu – např. nulu nebo self – program proběhne bez obtíží.

(Pro ilustraci této možnosti jsme na řádku //1 použili typ long; pokud bychom použili int, samozřejmě by byl další problém s tím, že velikost typu je oproti ukazateli na objekt pouze poloviční – nezapomínejme, že jsme ve čtyřiašedesátibitovém režimu.)

Oprava

Nejjednodušším "work-aroundem" je deklarovat instanční proměnnou v základní třídě – v našem případě tedy v třídě X:

...
@interface X:NSObject { long x; }
@property long x; //*
@end
...

Narazíme-li však na tento problém v kombinaci tříd, kdy máme přístup pouze ke zdrojovému kódu podtřídy, je řešení náročnější: kupodivu, explicitní deklarace instanční proměnné v odvozené třídě Y nepomůže.

Jednou z možností je vůbec syntetizování nevyužít, a vedle explicitní deklarace instanční proměnné – jež je v tomto případě samozřejmě nutností – definovat explicitně také oba accessory, stejně, jako v Objective C 1:

...
@interface Y:X { id y; }
@property (retain) id y;
@end
...
@implementation Y
-y { return y; }
-(void)setY:new {
  if (new!=y) {
    [y release];
    y=[new retain];
  }
}
...

To je samozřejmě dost nepohodlné; jedná se o dost práce navíc, a sami si také musíme dát pozor na to, abychom udrželi konsistenci mezi deklarací accessoru ("retain") a jeho implementací.

Jednoduché řešení, jež pomůže – alespoň ve všech případech, v nichž jsem je zkoušel – je deklarovat v podřízené třídě novou instanční proměnnou, jež se nijak nevyužívá, jen "oddělí" od sebe "staré" a "nové" syntetizované instanční proměnné, takto:

...
@interface X:NSObject
@property long x; //*
@end
@interface Y:X { int not_used; }
@property (retain) id y;
@end

@implementation X
@synthesize x;
@end
@implementation Y
@synthesize y;
-(void)check {
...

Jediný potenciální problém s touto opravou spočívá v tom, že jde tak trochu o "černou magii": není – bez znalosti implementačních detailů Objective C 2.0 – příliš zřejmé, proč by to mělo pomoci, a zda to skutečně pomůže ve všech případech :)

Nekorektní varování

Ve srovnání s minulým problémem, který může vést až k pádu zcela správně napsané aplikace, je tento celkem zanedbatelný; proto se již na něj podíváme jen v rychlosti: za jistých – opět mimo Apple ne zcela zřejmých, vizte níže :) – okolností překladač považuje typy, jež jsou stejně velké (ale vzájemně nepřevoditelné) za totožné, a v případě, kdy jsou k dispozici různé metody týchž signatur, lišící se typem, nevydá odpovídající varování:

48 /tmp> >warn.m
#import <Cocoa/Cocoa.h>

@interface Y
-m1:(float)arg;
-m2:(double)arg;
-m3:(double)arg;
@end
@interface X
-m1:(int32_t)arg;
-m2:(int64_t)arg;
-m3:(int32_t)arg;
@end
void foo() {
[(id)0 m1:1]; // should warn, but doesn't
[(id)0 m2:1]; // should warn, but doesn't
[(id)0 m3:1]; // warns correctly
}
49 /tmp> cc -Wall -S warn.m
warn.m: In function 'foo':
warn.m:18: warning: multiple methods named '-m3:' found
warn.m:6: warning: using '-(id)m3:(double)arg'
warn.m:12: warning: also found '-(id)m3:(int32_t)arg'
50 /tmp> 

Zajímavé přitom je, že zdaleka překladač pravidla "je to stejně velké, tak se neobtěžuji varovat" nevyužívá důsledně; to ilustruje poslední příklad:

52 /tmp> <warn.m
#import <Cocoa/Cocoa.h>

typedef struct {
  float f;
} Float;
typedef struct {
  double d;
} Double;
@interface X
-(Float)m1;
-(Double)m2;
@end
@interface Y
-(float)m1;
-(double)m2;
@end
void foo() {
  [(id)0 m1];
  [(id)0 m2];
}
53 /tmp> cc -Wall -S warn.m
warn.m: In function 'foo':
warn.m:21: warning: multiple methods named '-m2' found
warn.m:12: warning: using '-(Double)m2'
warn.m:16: warning: also found '-(double)m2'
54 /tmp> cc -Wall -E warn.m

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  

 

 

 

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

 

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

Uživatelské jméno:

Heslo: