Hoe retourneer ik een struct-waarde van een runtime-defined class-methode onder ARC?

Ik heb een klassemethode die een CGSize retourneert en ik zou het willen noemen via deobjective-cruntime-functies omdat ik de klasse- en methodamen als tekenreekswaarden heb gekregen.

Ik compileer met ARC-vlaggen in XCode 4.2.

Methodehandtekening:

+(CGSize)contentSize:(NSString *)text;

Het eerste dat ik probeerde was om het met objc_msgSend als volgt aan te roepen:

Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");

id result = objc_msgSend(clazz, method, text);

Dit is gecrasht met "EXC_BAD_ACCESS" en zonder een stacktracering. Ik heb dit eerst gebruikt omdat de documentatie voor objc_msgSend zegt,

Wanneer het een methodeaanroep ontmoet, genereert de compiler een aanroep naar een van de functies objc_msgSend , objc_msgSend_stret , objc_msgSendSuper of objc_msgSendSuper_stret . [...] Methoden met datastructuren als retourwaarden worden verzonden met objc_msgSendSuper_stret en objc_msgSend_stret .

Vervolgens gebruikte ik objc_msgSend_stret als volgt:

Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");

CGSize size = CGSizeMake(0, 0);
objc_msgSend_stret(&size, clazz, method, text);

Met behulp van de bovenstaande handtekening krijg ik de volgende twee compilerfouten en twee waarschuwingen:

fout: Probleem met Automatische Referentie Telling: Impliciete conversie van een niet-Objective-C wijzer type 'CGSize *' (ook bekend als 'struct CGSize *') naar 'id' is niet toegestaan ​​met ARC

     

waarschuwing: semantisch probleem: incompatibele aanwijzertypen die 'CGSize *' (ook bekend als 'struct CGSize *') doorgeven aan de parameter van het type 'id'

     

fout: Probleem met automatische referentietelling: Impliciete conversie van eenobjective-cpointer naar 'SEL' is niet toegestaan ​​met ARC

     

warning: Semantic Issue: Incompatible pointer types passing       '__unsafe_unretained Class' naar parameter van het type 'SEL'

Als ik de declaratie van de methode bekijk, is het:

OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

die identiek is aan objc_msgSend :

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

Dit verklaart de compileerfouten voor mij, maar wat gebruik ik op runtime-niveau om een ​​klasse en de statische methode tijdens runtime op te roepen en een struct-waarde terug te geven?

8

2 antwoord

U moet objc_msgSend_stret casten naar het juiste functie-aanwijzertype. Het is gedefinieerd als void objc_msgSend_stret (id, SEL, ...) , wat een ongepast type is om te callen. U wilt zoiets gebruiken

CGSize size = ((CGSize(*)(id, SEL, NSString*))objc_msgSend_stret)(clazz, @selector(contentSize:), text);

Hier gieten we objc_msgSend_stret naar een functie van het type (CGSize (*) (id, SEL, NSString *)) , het daadwerkelijke type van de IMP dat implementeert + contentSize: .

Opmerking: we gebruiken ook @selector (contentSize:) omdat er geen reden is om NSSelectorFromString() te gebruiken voor selectors die bekend zijn tijdens het compileren.

Merk ook op dat het casten van de functie pointer vereist is, zelfs voor normale aanroepen van objc_msgSend() . Zelfs als het aanroepen van objc_msgSend() direct in uw specifieke geval werkt, is het gebaseerd op de aanname dat de varargs methode aanroep ABI hetzelfde is als de ABI voor het aanroepen van een niet-varargs methode, die misschien niet waar is op alle platforms.

15
toegevoegd
@JonasGardner: Ik geloof dat dit eigenlijk afhankelijk is van de grootte van de structuur en de architectuur in kwestie. Het is heel goed mogelijk dat CGSize op de architecturen waar u om geeft een eenvoudige objc_msgSend() is. U zou de ABI-documentatie van de architectuur moeten raadplegen om erachter te komen welke structuurgroottes/-types in de registers worden geretourneerd of op de stapel worden geretourneerd.
toegevoegd de auteur Kevin Ballard, de bron
Het lijkt erop dat je 'objc_msgSend_stret' niet eens wilt gebruiken, maar in plaats daarvan objc_msgVerzending met het juiste bericht dat je hebt aangegeven. Bedankt!
toegevoegd de auteur Jonas Gardner, de bron

Als u een struct wilt ophalen van uw klassemethode, kunt u een NSInvocation als volgt gebruiken:

Class clazz = NSClassFromString(@"MyClass");
SEL aSelector = NSSelectorFromString(@"testMethod");

CGSize returnStruct;//Or whatever type you're retrieving

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[clazz methodSignatureForSelector:aSelector]];

[invocation setTarget:clazz];
[invocation setSelector:aSelector];

[invocation invoke];
[invocation getReturnValue:&returnStruct];

Aan het einde hiervan moet returnStruct uw struct-waarde bevatten. Ik heb dit zojuist getest in een ARC-toepassing en dit werkt prima.

7
toegevoegd
@ KevinBallard - Ja, ik dacht dat ik het eruit zou gooien voor het geval iemand een meer geabstraheerde oplossing wilde dan objc_msgSend() .
toegevoegd de auteur Brad Larson, de bron
NSInvocation is vrij duur. Het is in veel gevallen nuttig, maar er zijn vaak snellere manieren om te bereiken wat je wilt.
toegevoegd de auteur Kevin Ballard, de bron
Eerlijk genoeg. NSInvocation zou ook de verwarring wegnemen over welke architecturen objc_msgSend() versus objc_msgSend_stret() voor een bepaalde methode gebruiken.
toegevoegd de auteur Kevin Ballard, de bron