2012-01-11 6 views
9

Si consideri il seguente codice:iOS: Block proprietà ubicata direttamente si blocca quando accede

@interface ClassA : NSObject 
@property (nonatomic, copy) void(^blockCopy)(); 
@end 

@implementation ClassA 

@synthesize blockCopy; 

- (void)giveBlock:(void(^)())inBlock { 
    blockCopy = inBlock; 
} 

@end 

Quindi utilizzare in una classe che ha una proprietà strong di tipo ClassA chiamato someA:

self.someA = [[ClassA alloc] init]; 
[self.someA giveBlock:^{ 
    NSLog(@"self = %@", self); 
}]; 
dispatch_async(dispatch_get_main_queue(), ^{ 
    self.someA.blockCopy(); 
    self.someA = nil; 
}); 

Se io eseguire quello costruito O3 con ARC abilitato, su iOS, si blocca durante la chiamata self.someA.blockCopy(); all'interno di objc_retain. Perché?

Ora mi rendo conto che probabilmente le persone diranno che dovrei impostarlo con self.blockCopy = inBlock ma ho pensato che ARC avrebbe dovuto fare la cosa giusta qui. Se guardo l'assemblea (ARMv7) prodotta dal metodo giveBlock: sembra che questo:

 .align 2 
     .code 16 
     .thumb_func  "-[ClassA giveBlock:]" 
"-[ClassA giveBlock:]": 
     push {r7, lr} 
     movw r1, :lower16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4)) 
     mov  r7, sp 
     movt r1, :upper16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4)) 
LPC0_0: 
     add  r1, pc 
     ldr  r1, [r1] 
     add  r0, r1 
     mov  r1, r2 
     blx  _objc_storeStrong 
     pop  {r7, pc} 

che chiama objc_storeStrong che a sua volta fa un retain sul blocco e un release sul vecchio blocco. La mia ipotesi è che ARC non sta notando correttamente che si tratta di una proprietà di blocco, perché penso che dovrebbe chiamare objc_retainBlock anziché il normale objc_retain.

Oppure, sono semplicemente totalmente in errore e in realtà ARC sta facendo quello che documenta e l'ho appena letto nel modo sbagliato?

Discussione molto benvenuta su questo - Trovo che questo sia piuttosto intrigante.

Punti da notare:

  • Esso non va in crash su OS X.
  • Non va in crash costruito O0.

risposta

12
- (void)giveBlock:(void(^)())inBlock { 
    blockCopy = inBlock; 
} 

è necessario copiare il blocco sia in missione o quando passò in questa funzione. Mentre ARC risolve il problema di auto-move-to-heap-on-return, non lo fa per gli argomenti (non può fare alle idiosincrasie di C).

Che non si schianti in determinati ambienti è semplicemente una coincidenza; non si blocca finché la versione di stack del blocco non è stata sovrascritta. Un segno sicuro di ciò è quando si verifica un arresto anomalo che va via con l'ottimizzazione disattivata. Con l'ottimizzazione disattivata, il compilatore non riutilizzerà la memoria dello stack all'interno di un determinato ambito, causando la memoria "valida" per molto tempo dopo che dovrebbe essere.


io ancora non capisco il motivo per cui non può fare un objc_blockRetain piuttosto che un objc_retain normale, però. Il compilatore conosce il tipo dopo tutto.

Sono abbastanza sicuro che il problema è il costo potenziale del compito. Se il blocco cattura molto stato, compresi, potenzialmente, altri blocchi, allora Block_copy() potrebbe essere davvero veramentedavvero costoso.

I.e.se si ha qualcosa di simile:

BlockType b = ^(...) { ... capture lots of gunk ... }; 
SomeRandomFunc(b); 

... e che implicava una Block_copy() per il solo fatto della cessione, renderebbe impossibile l'uso di blocchi in modo coerente senza il rischio di problemi di prestazioni patologiche. Perché non c'è modo per il compilatore di sapere se SomeRandomFunc() è sincrono o asincrono, non c'è modo di gestirlo automaticamente (in questo momento - sono sicuro che eliminare questo potenziale tripwire è auspicabile).

+0

Sono solo un po 'sorpreso nel vederlo passare attraverso un 'objc_storeStrong' quando lo assegno, e non essere in grado di" fare la cosa giusta ". – mattjgalloway

+0

+1 interessante - ty! – Till

+0

Da quello che ho capito (stato un po '), ci sono casi limite che impediscono al compilatore di emettere codice che "funziona" in tutti i casi correttamente. Sotto ARC, la linea dura nella sabbia richiede al compilatore di essere in grado di dimostrare esattamente che ogni modello di codice dato funzionerà sempre tutto il tempo. In questo caso, non può farlo perché ci sono usi validi per gli argomenti di blocco solo stack passati attraverso qualsiasi sito di chiamata dato (incluso 'objc_storeStrong()'). – bbum