11

Sono in procinto di convertire il mio progetto in ARC. Ho una categoria a NSColor con un metodo che restituisce una rappresentazione CGColor autoreleased:Metodo di conversione che restituisce un CGColor con autoreleased su ARC

@implementation NSColor (MyCategory) 

- (CGColorRef)CGColor 
{ 
    NSColor *colorRGB = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; 
    CGFloat components[4]; 
    [colorRGB getRed:&components[0] 
       green:&components[1] 
       blue:&components[2] 
       alpha:&components[3]]; 
    CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); 
    CGColorRef theColor = CGColorCreate(space, components); 
    CGColorSpaceRelease(space); 
    return (CGColorRef)[(id)theColor autorelease]; 
} 

@end 

Qual è il modo corretto di fare questo con ARC? Non voglio restituire un CGColor conservato.

Il convertitore ARC in XCode consigliamo di usare

return (CGColorRef)[(__bridge id)theColor autorelease]; 

ma che provoca il seguente messaggio di errore:

[masterizzatore] non è sicuro di gettare a 'CGColorRef' il risultato di messaggio 'autorelease'; un cast __bridge può comportare un puntatore a un oggetto distrutto e __bridge_retained può perdere l'oggetto

+2

Hai una bella perdita di memoria qui. CGColorCreate creerà un oggetto CGColor che ogni volta che chiamerai questo metodo verrà tenuto in memoria. Consiglio vivamente di fare qualcosa del tipo: 'CGColorRef colorRef = CGColorCreate (colorSpaceRGB, componenti); UIColor * retColor = [UIColor colorWithCGColor: colorRef]; CGColorRelease (colorRef); return retColor; ' – jfeldman

+2

Hai letto la domanda? So che sta perdendo, questo è il mio problema. Volevo restituire un CGColor, non un UIColor (è comunque una domanda OS X come si potrebbe dire dalla mia citazione di NSColor). Ad ogni modo, questo è stato risolto sei mesi fa. – DrummerB

risposta

7

CGColor è un oggetto core Foundation. Non si dovrebbe tentare di utilizzare autorelease con esso. Invece, è necessario rinominare il metodo copyCGColor e restituire un oggetto mantenuto.

Auto-rilascio è un concetto Objective-C. Non esiste a livello di Core Foundation.

Dal momento che CGColor non è un bridge gratuito a qualsiasi classe Objective-C, è molto strano provare a autorizzarlo automaticamente (anche se ciò potrebbe funzionare).

Aggiornamento pochi anni dopo

V'è ora CFAutorelease() a livello CoreFoundation (disponibile dal Mavericks e iOS 7).

+5

Per quanto strano possa essere, NSColor.h dice di '-CGColor':" Restituisce un CGColor autoreleased ". –

+0

Disponibile in OS X 10.9 (e iOS 7), ora esiste una funzione CFAutorelease(). – NSDestr0yer

+0

Puoi fornire un esempio? Ho aggiunto 'CF_RETURNS_RETAINED' alla mia proprietà, ma l'analizzatore statico si lamenta ancora di una potenziale perdita ... – NSAddict

0

Penso che si voglia utilizzare __bridge_transfer in questo caso.

Docs

8

Essenzialmente è perché non c'è buon modo per convertire il seguente codice nel ARC:

CGColorRef a = ...; 
id b = [(id)a autorelease]; 
CGColorRef c = (CGColorRef)b; 
// do stuff with c 

Il convertitore rimuove -autorelease e aggiunge un po 'a ponte calchi, ma si blocca:

CGColorRef a = ...; 
id b = (__bridge_transfer id)a; 
CGColorRef c = (__bridge_SOMETHING CGColorRef)b; 
// do stuff with c. Except the compiler sees that b is no longer being used! 

Ma cosa dovrebbe scegliere il migratore per __bridge_SOMETHING?

  • Se raccoglie __bridge, quindi b è più utilizzato quindi il compilatore può immediatamente rilasciarlo. Questo si blocca.
  • Se prende __bridge_retained, la proprietà viene trasferita indietro su "CF-land", ma il codice originale presuppone che l'oggetto sia di proprietà del pool di autorelease. Il codice ora perde.

Il problema è che ARC vieta di chiamare -autorelease ma non ha un metodo documentato per garantire che un oggetto viene aggiunto al pool autorelease - l'unica buona ragione per fare questo per restituire un tipo CF autoreleased da un metodo, ma sacco di classi UIKit hanno proprietà CF-digitato (e MKOverlayPathView ha una proprietà atomica CGPathRef che deve restituire un valore autoreleased).

Questo è uno dei bit più complicati dell'ARC che vorrei davvero fosse meglio documentato.

Ci sono alcuni ostacoli che è possibile superare e che potrebbero funzionare con diversi gradi di successo. In ordine crescente di ickiness:

  1. definire una funzione CFAutorelease() in un file compilato senza ARC (aggiungi -fno-objc-arc alle opzioni del compilatore in contesti di destinazione → Costruire Fasi → compilare i sorgenti). Lascio questo come esercizio al lettore. Funziona perché il codice ARC deve interagire con il codice MRC. Questa è probabilmente la soluzione più pulita. (Questo è destinato ad attrarre un commento che dice che non dovrebbe usare il prefisso CF, ma finché non si vede un errore di collegamento, le collisioni dei nomi dei simboli C sono generalmente sicure perché è stato introdotto lo "spazio dei nomi a due livelli" in 10.3 circa)

  2. Vari telegrammi per inviarlo un messaggio -autorelease o equivalente. Tutti questi sono un po 'confusi perché si basano su ARC "ingannare", tranne l'ultimo che presuppone che id sia compatibile con ABI con void*. Probabilmente sono anche più lenti di quanto sopra perché devono cercare una classe/selettore (objc_lookUpClass() e potrebbero essere più veloci o addirittura ottimizzati, ma non ci scommetterei su di esso).

    return (__bridge CGColorRef)[(__bridge id)theColor performSelector:NSSelectorFromString(@"autorelease")] 
    
    [NSClassFromString(@"NSAutoreleasePool") addObject:(__bridge id)theColor] 
    return theColor; 
    
    return (__bridge CGColorRef)((id(*)(id,SEL))objc_msgSend)((__bridge id)theColor,NSSelectorFromString(@"autorelease")); 
    
    return ((void*(*)(void*,SEL))objc_msgSend)(theColor,NSSelectorFromString(@"autorelease")); 
    
  3. forzarlo da aggiungere al pool autorelease assegnando ad una variabile __autoreleasing che il compilatore non può ottimizzare via. Non sono sicuro se questo è garantito (in particolare, qualcosa di simile a objc_autoreleaseReturnValue() e objc_retainAutoreleasedReturnValue() potrebbe essere possibile, ma penso che sia improbabile dal momento che rallenterebbe il caso comune di (NSError * __autoreleasing *)error).

    -(id)forceAutorelease:(id)o into:(id __autoreleasing*)p 
    { 
        *p = o; 
        return p; 
    } 
    
    -(CGColorRef)CGColor 
    { 
        ... 
        CGColorRef theColor = CGColorCreate(...); 
        CGColorSpaceRelease(space); 
        id __autoreleasing temp; 
        return (__bridge CGColorRef)[self forceAutorelease:(__bridge_transfer id)theColor into:&temp]; 
    } 
    

    (Potrebbe anche essere possibile per il compilatore/runtime di cooperare e utilizzare statica spedizione/messa in linea fino a quando i relativi metodi vengono sovrascritte, ma che sembra difficile e non senza spese significative della propria.)

  4. Utilizzare uno typedef con __attribute__((NSObject)). Questa è la parte più confusamente documentata della ARC spec, ma qualcosa di simile sembra lavoro:

    typedef CGColorRef MyCGColorRef __attribute__((NSObject)); 
    -(MyCGColorRef)CGColor 
    { 
        ... 
        return (__bridge MyCGColorRef)(__bridge_transfer id)theColor; 
    } 
    

    ho penso avete bisogno di due ponti per far funzionare tutto questo (uno a trasferire la proprietà a ARC e un altro per); se tu semplicemente return theColor; sospetto che sia trapelato. Dalla mia lettura dei documenti, è necessario per il solo (__bridge_transfer MyCGColorRef) perché converte da un puntatore non ARC (CGColorRef) a un puntatore ARC (MyCGColorRef), ma ciò fa lamentare il compilatore. Ahimè, i documenti non forniscono esempi su come utilizzare i typedef __attribute__((NSObject)).

    Nota che non è necessario modificare il tipo di ritorno nell'intestazione. Ciò potrebbe consentire l'ottimizzazione del valore di ritorno autorelitta, ma non sono sicuro di come il compilatore gestisca la conversione da MyCGColorRef a CGColorRef. Le sospiro.

1

Infatti, in gestione manuale della memoria è possibile retain, release e autorelease qualsiasi CoreFoundation oggetto, perché tutti sono numero verde ponte ad almeno NSObject.

Poiché ARC proibisce l'uso della gestione manuale della memoria, dovremmo in qualche modo dire al compilatore cosa fare. Un modo è quello di denominare il metodo - (CGColorRef)copyCGColor; in modo che il compilatore sappia che il metodo restituisce l'oggetto con il conteggio di mantenimento +1.

Tuttavia, se siete come me e preferisce pianura "CGColor" per tali metodi, è possibile basta aggiungere __attribute__((cf_returns_retained)) alla definizione del metodo:

@interface NSColor (MyCategory) 

- (CGColorRef)CGColor __attribute__((cf_returns_retained)); 

@end 
4

partire con OS X 10.9 o iOS 7 si può semplicemente utilizzare CFAutorelease() (dichiarato in CFBase.h).

Problemi correlati