ISA swizzling and calls to `super` - objective-c

Assume the following class hierarchy. Class A is publicly declared:
#interface A : NSObject
+ (A)createInstance;
- (void)a;
#end
Class _B is a private subclass of A:
#interface _B : A
- (void)a;
- (void)b;
#end
Assume objects of class A should only be created using the factory method createInstance, which creates and returns an instance of _B.
I want to enhance the functionality of an instance of A on a per-instance basis. So I decided to do some ISA swizzling to achieve:
#interface ExtA : A
- (void)a;
#end
#implementation ExtA
- (void)a
{
NSLog("ExtA_a");
[super a];
}
#end
And I do the ISA swizzling using the following method on an NSObject category (naive implementation shown here):
- (void)changeToSubclass:(Class)cls prefix:(NSString*)prefix suffix:(NSString*)suffix
{
NSString* className = [NSString stringWithFormat:#"%#%#%#", prefix ? prefix : #"", NSStringFromClass(object_getClass(self)), suffix ? suffix : #""];
if([className isEqualToString:NSStringFromClass(object_getClass(self))])
{
className = [NSString stringWithFormat:#"%#(%#)", NSStringFromClass(object_getClass(self)), NSStringFromClass(cls)];
}
Class newSubclass = objc_getClass(className.UTF8String);
if(newSubclass == nil)
{
newSubclass = objc_allocateClassPair(object_getClass(self), className.UTF8String, 0);
objc_registerClassPair(newSubclass);
unsigned int listCount = 0;
Method *list = class_copyMethodList(cls, &listCount);
for(int i = 0; i < listCount; i++)
{
class_addMethod(newSubclass, method_getName(list[i]), method_getImplementation(list[i]), method_getTypeEncoding(list[i]));
}
free(list);
listCount = 0;
list = class_copyMethodList(objc_getMetaClass(class_getName(cls)), &listCount);
for(int i = 0; i < listCount; i++)
{
class_addMethod(objc_getMetaClass(class_getName(newSubclass)), method_getName(list[i]), method_getImplementation(list[i]), method_getTypeEncoding(list[i]));
}
free(list);
}
object_setClass(self, newSubclass);
}
Everything seemingly works, but I noticed that [super a]; does not behave as expected, actually the implementation of -[A a] is called, if if the superclass in runtime is actually _B.
Replacing the call to super with the following code works, but is ugly, and requires knowledge of and work by developers:
struct objc_super superInfo = {
self,
[self superclass]
};
objc_msgSendSuper(&superInfo, #selector(a));
What does the compiler emit when calling super and any way to change this emitted code?

The difference is minor, but important. The compiler is issuing a function call, not to objc_msgSendSuper, but to objc_msgSendSuper2.
What's the difference, you may ask? It's minor, but important.
From apple's open source:
/********************************************************************
*
* id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...);
*
* struct objc_super {
* id receiver;
* Class class;
* };
********************************************************************/
ENTRY _objc_msgSendSuper
MESSENGER_START
// search the cache (objc_super in %a1)
movq class(%a1), %r11 // class = objc_super->class
CacheLookup SUPER // calls IMP on success
/* Snipped code for brevity */
/********************************************************************
* id objc_msgSendSuper2
********************************************************************/
ENTRY _objc_msgSendSuper2
MESSENGER_START
// objc_super->class is superclass of class to search
// search the cache (objc_super in %a1)
movq class(%a1), %r11 // cls = objc_super->class
movq 8(%r11), %r11 // cls = class->superclass
CacheLookup SUPER2 // calls IMP on success
For those reading who are unfamiliar with x86_64 assembly, the important line of code is here:
movq 8(%r11), %r11 // cls = class->superclass
What does this do, you may ask? It's fairly simple - instead of the caller passing the superclass to search, the objc_msgSend implementation does it.
However, this important distinction causes one crucial problem - when doing a super call, it does not invoke [self class]. Instead, it uses the class of the current implementation, which is, of course, ExtA.
Therefore, the only way to 'fix' this is to change the superclass of ExtA at run-time, which should cause your method invoking to perform as expected.

Related

Call original class method when called from child

I have a class A. B inherits A. Both classes implement method1 and method2.
method1 in A calls method2. It look like...
- (void)method1{
// some code
[self method2];
// some code
}
- (void)method2{
// some work
}
method1 in B calls super class method 1, and B also overrides method2.
- (void)method1{
[super method1];
}
- (void)method2{
// some work
}
Now, when B's instance is created and called method1 A's method1 calls method2 in B. What I want to do is calling A's method2 from A's method1 even when it is called from child(B).
In other words, in A's method1, I want to "forcefully" call the method in the same owner(class).
Is there any easy way to do it? I think I can do it with calling objective-c runtime functions but I want to know if there is easier way.
I know that this is not the design we should make in usual case, but from a little complex reason I have to do it. So please don't propose me to change the design or ask me what is the original goal of the program.
As a simplest solution I can come up with, use BOOL flag to decide how method2 should behave:
#interface B ()
#property (nonatomic) BOOL shouldCallSuperMethod2;
#end
#implementation B
- (void)method1{
self.shouldCallSuperMethod2 = YES;
[super method1];
self.shouldCallSuperMethod2 = NO;
}
- (void)method2{
if (self.shouldCallSuperMethod2) {
[super method2];
}
else {
// some work
}
}
#end
Note that this solution is not thread safe.
UPD Another interesting way, using runtime magic:
#import ObjectiveC.runtime;
#implementation B
- (void)method2 {
NSUInteger returnAddress = (NSUInteger)__builtin_return_address(0);
NSUInteger callerIMPAddress = 0;
SEL interestingSelector = #selector(method1);
// Iterate over the class and all superclasses
Class currentClass = object_getClass(self);
while (currentClass)
{
// Iterate over all instance methods for this class
unsigned int methodCount;
Method *methodList = class_copyMethodList(currentClass, &methodCount);
unsigned int i;
for (i = 0; i < methodCount; i++)
{
// Ignore methods with different selectors
if (method_getName(methodList[i]) != interestingSelector)
{
continue;
}
// If this address is closer, use it instead
NSUInteger address = (NSUInteger)method_getImplementation(methodList[i]);
if (address < returnAddress && address > callerIMPAddress)
{
callerIMPAddress = address;
}
}
free(methodList);
currentClass = class_getSuperclass(currentClass);
}
if (callerIMPAddress == (NSUInteger)[self methodForSelector:interestingSelector]) {
// method2 is called from method1, call super instead
[super method2];
}
else {
// some work
}
}
#end
Other interesting ways to identify caller may be found in answers to this question

'Assigning to 'id' from incompatible type'

I'm implementing a objective C wrapper for Box2d (which is written in c++). The b2Body keeps a reference to its wrapper B2Body in its userData field. GetUserData returns a void*. I'm now implementing fast iteration for getting the B2Bodies out of the B2World.
I get an 'Assigning to 'id' from incompatible type 'B2Body *' error at the line indicated below. Why?
#import "B2Body.h"
#import "B2World.h"
#import "Box2d.h"
#implementation B2World
-(id) initWithGravity:(struct B2Vec2) g
{
if (self = [super init])
{
b2Vec2 *gPrim = (b2Vec2*)&g;
_world = new b2World(*gPrim);
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len;
{
if(state->state == 0)
{
state->mutationsPtr = (unsigned long *)self;
state->extra[0] = (long) ((b2World*)_world)->GetBodyList();
state->state = 1;
}
// pull the box2d body out of extra[0]
b2Body *b = (b2Body*)state->extra[0];
// if it's nil then we're done enumerating, return 0 to end
if(b == nil)
{
return nil;
}
// otherwise, point itemsPtr at the node's value
state->itemsPtr = ((B2Body*)b->GetUserData()); // ERROR
state->extra[0] = (long)b->GetNext();
// we're returning exactly one item
return 1;
}
`
B2Body.h looks like this:
#import
#interface B2Body : NSObject
{
int f;
}
-(id) init;
#end
NSFastEnumerationState is a C structure, and the itemsPtr field is:
id __unsafe_unretained *itemsPtr;
In earlier versions, the __unsafe_unretained specifier was obviously missing.
Note, that the field itemsPtr is a pointer-to-id. Since id is essentially a pointer, itemsPtr is a pointer to an object pointer. Actually, this field is what holds the array of objects that allows the fast enumeration. Basically, it trolls through this array of object pointers.
Since I know nothing about Box2d, that's about all I can say. Assuming b->GetUserData() returns a pointer to an array of objects, you should be able to do this:
state->itemsPtr = (__unsafe_unretained id *)b->GetUserData();
While a bit dated, Mike Ash's article is still a great source for implementing fast enumeration.
EDIT
Just noticed that you are returning a single object. So, I assume GetUserData just returns a single object pointer. Since you need to return a pointer to object pointers, you would need to do something like this:
id object = (__bridge id)b->GetUserData();
state->itemsPtr = &object;
However, that stack object will be gone once you return from this method, which is why you are passed a stack buffer you can use. Thus, you should probably stuff that single pointer into the provided stack buffer:
*buffer = (__bridge id)b->GetUserData()
state->itemsPtr = buffer;

Instantiating global object in objective C when they're passed to a function. Confusion regarding object scope

I'm new to Objective-C and C in general. I've been looking around and I couldn't find the solution to this issue. Any help would be appreciated.
I have the following global variables
CCSprite* BackgroundImage;
CCSprite* BackgroundGhost;
CCSprite* Globe;
CCSprite* Logo;
in my init I call a function and pass the global variables as parameters.
if(_ourDevice == iPad)
{
[self CustomCodeSetAssetForIpad:BackgroundImage ghost:BackgroundGhost TheGlobe:Globe AndTheLogo:Logo];
}
Here is the code for CustomCodeSetAssetForIpad:
-(void) CustomCodeSetAssetForIpad:(CCSprite*) _Background ghost:(CCSprite*) _BackgroundGhosts TheGlobe:(CCSprite*)_Globes AndTheLogo:(CCSprite*) _Logos
{
_Background = [CCSprite spriteWithFile:#"1028-768-sunray.png"];
_BackgroundGhosts = [CCSprite spriteWithFile:#"1028-768-sunray.png"];
_Globes = [CCSprite spriteWithFile:#"BigGlobe.png"];
_Logos = [CCSprite spriteWithFile:#"DefaultLogo.png"];
[_BackgroundGhosts setAnchorPoint:CGPointMake(0.5, 0)];
[_BackgroundGhosts setScale:2];
[_BackgroundGhosts setOpacity:120];
//[BackgroundGhost setPosition: CGPointMake(BackgroundGhost.position.x, BackgroundGhost.position.y-500)];
[_BackgroundGhosts setPosition:CGPointMake([[CCDirector sharedDirector]winSize].width/2, -100)];
[_Globes setAnchorPoint:CGPointMake(0.5, 0.5)];
[_Globes setScale:0.7];
[_Globes setPosition:CGPointMake([[CCDirector sharedDirector]winSize].width/2, -260)];
[_Logos setPosition:CGPointMake([self CenterOfTheScreen].x, [[CCDirector sharedDirector]winSize].height-[[CCDirector sharedDirector]winSize].height*0.2)];
[_Logos setScale:0.05];
}
The first few lines instantiate the global variables that were passed. However when the function is done, the reference to those objects are lost. I thought when you pass a pointer to a function, as the object is instantiated, it would retain its reference to the instantiated object. Am I missing something here?
Ah... Variables of type classname * are effectively references to instances of that class. So in your case, _Background is an instance reference passed in as an argument to your function. If you are trying to return multiple results from a function (via pointers), your arguments should really of type classname **, which is a pointer to a reference.
So the calling code would look like this:
CCSprite * background = nil ;
CCSprite * ghosts = nil ;
CCSprite * globes = nil ;
CCSprite * logos = nil ;
[ self customCodeSetAssetForIpad:&background ghosts:&ghosts globes:&globes logos:&logos ] ;
And your method looks like this:
-(void)customCodeSetAssetForIPad:(CCSprite**)background ghosts:(CCSprite**)backgroundhosts globe:globes logos:(CCSprite**)logos
{
*background = [CCSprite spriteWithFile:#"1028-768-sunray.png"];
// ... the rest of your code ...
}
Also, I took the liberty of making your method name and variable names more objective-c like (methods and variables begin with lowercase letters)
EDIT: I'd personally structure it like this:
//
// World... global things go in here
//
#interface World
#property ( nonatomic, readonly, strong ) CCSprite * background ;
+(id)theWorld // accessor to get the global world object
#end
#implementation World
#synthesize CCSprite * background = _background ;
static World * __theWorld = nil ; // global variable to hold our shared global World instance
+(void)load
{
// when this class is loaded, create our global world object
__theWorld = [ [ [ self class ] alloc ] init ] ;
}
+(id)theWorld
{
return __theWorld ;
}
// return the background sprite, creating it if it hasn't be created yet
-(CCSprite*)background
{
if ( !_background) { _background = [ CCSprite spriteWithFile:[CCSprite spriteWithFile:#"1028-768-sunray.png"] ; }
return _background ;
}
#end

Why can some methods (-retainWeakReference, -allowsWeakReference, +load, +initialize) on class NSObject not be added to other classes at runtime?

It is straightforward at runtime to create a copy MyNSObject of the Class NSObject:
First, create a new class pair.
Class MyNSObject = objc_allocateClassPair(nil, "MyNSObject", 0);
Second read the methods, protocols, and ivars from NSObject and add them to the new class.
uint instanceMethodCount;
Method *instanceMethodArray = class_copyMethodList([NSObject class], &instanceMethodCount);
for (int i = 0; i < instanceMethodCount; i++) {
Method method = *(instanceMethodArray + i);
SEL selector = method_getName(method);
IMP implementation = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
BOOL success = class_addMethod(MyNSObject, selector, implementation, types);
}
free(instanceMethodArray);
uint protocolCount;
Protocol **protocolArray = class_copyProtocolList([NSObject class], &protocolCount);
for (int i = 0; i < protocolCount; i++) {
Protocol *protocol = *(protocolArray + i);
BOOL success = class_addProtocol(MyNSObject, protocol);
}
free(protocolArray);
uint ivarCount;
Ivar *ivarArray = class_copyIvarList([NSObject class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = *(ivarArray + i);
const char *name = ivar_getName(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
NSUInteger size, alignment;
NSGetSizeAndAlignment(typeEncoding, &size, &alignment);
BOOL success = class_addIvar(MyNSObject, name, size, alignment, typeEncoding);
}
free (ivarArray);
Third, read the methods from the metaclass of NSObject and add them to the new metaclass.
uint classMethodCount;
Method *classMethodArray = class_copyMethodList(object_getClass([NSObject class]), &classMethodCount);
for (int i = 0; i < classMethodCount; i++) {
Method method = *(classMethodArray + i);
SEL selector = method_getName(method);
IMP implementation = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
BOOL success = class_addMethod(object_getClass(MyNSObject), selector, implementation, types);
}
free(classMethodArray);
And finally, register the class pair.
objc_registerClassPair(MyNSObject);
Well, it's almost that straightforward. There are a couple of problems with this. Well, a couple of couples. If we were to add the following lines at the end but within the first for block
if (!success) {
NSLog(#"unable to add method with selector named %# to class MyNSObject", NSStringFromSelector(selector));
}
and the following lines at the end but within the last for block
if (!success) {
NSLog(#"unable to add method with selector name %# to metaclass MyNSObject", NSStringFromSelector(selector));
}
Then we would see the following output:
unable to add method with selector name retainWeakReference to class MyNSObject
unable to add method with selector name allowsWeakReference to class MyNSObject
unable to add method with selector name load to metaclass MyNSObject
unable to add method with selector name initialize to metaclass MyNSObject
What is going on here? Do classes (resp. metaclasses) implement retainWeakReference and allowsWeakReferenc (resp. load and initialize) "out of the box"?
References:
1. Cocoa with Love - What is a meta-class in Objective-C?
2. Stack Overflow - Justin Spahr-Summers response to "How can one obtain the sizeof a type for which one has an encoding?"
NSObject is an even more interesting beast than expected. Typically one thinks about the map
method_getName: Method -> SEL
as being one-to-one. I.e one usually thinks that method_getName(methodA) == method_getName(methodB) just in case methodA == methodB. One is encouraged to think this: one cannot create a class during coding via #interface which has multiple methods with the same selector, nor can one add two methods with the same selector to a class using class_addMethod() during runtime.
However, it is evidently possible to do it by hand. The following code demonstrates this. This code gets all the instance methods on NSObject and prints out each one named either "retainWeakReference" or "allowsWeakReference" and then gets all the class methods on NSObject and prints out each one named either "initialize" or "load".
uint NSObjectInstanceMethodCount;
Method *NSObjectInstanceMethodArray = class_copyMethodList([NSObject class], &NSObjectInstanceMethodCount);
for (int i = 0; i < NSObjectInstanceMethodCount; i++) {
Method method = *(NSObjectInstanceMethodArray + i);
SEL selector = method_getName(method);
IMP implementation = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
if (strcmp(selector, "retainWeakReference") == 0 || strcmp(selector, "allowsWeakReference") == 0) {
NSLog(#"NSObject implements method(%s,%p,%s)", selector, implementation, types);
}
}
uint NSObjectClassMethodCount;
Method *NSObjectClassMethodArray = class_copyMethodList(object_getClass([NSObject class]), &NSObjectClassMethodCount);
for (int i = 0; i < NSObjectClassMethodCount; i++) {
Method method = *(NSObjectClassMethodArray + i);
SEL selector = method_getName(method);
IMP implementation = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
if (strcmp(selector, "initialize") == 0 || strcmp(selector, "load") == 0) {
NSLog(#"metaNSObject implements method(%s,%p,%s)", selector, implementation, types);
}
}
The output is not what one might, aside from the preceding build-up, have expected:
NSObject implements method(retainWeakReference,0x7fff8a120b1f,c16#0:8)
NSObject implements method(allowsWeakReference,0x7fff8a120b05,c16#0:8)
NSObject implements method(retainWeakReference,0x7fff80ad6db0,c16#0:8)
NSObject implements method(allowsWeakReference,0x7fff80ad6d90,c16#0:8)
metaNSObject implements method(load,0x7fff8a09e4f2,v16#0:8)
metaNSObject implements method(initialize,0x7fff8a00cb89,v16#0:8)
metaNSObject implements method(load,0x7fff80a57670,v16#0:8)
metaNSObject implements method(initialize,0x7fff80a133d0,v16#0:8)
So, as is now evident, NSObject has two implementations for each of the selectors -retainWeakReference, -allowsWeakReference, +load, and +initialize. These are the only four methods on NSObject for which there are multiple implementations, which is demonstrated by the fact that these were the only four reported by the code in the question as being unable to be added to MyNSObject.
A statement which gets close to counting as an answer to the question then is that you can't add multiple methods with the same selector to a class created at runtime via class_addMethod(). In particular, though, no, no methods are implemented by a class created at runtime with objc_allocateClassPair() "out of the box".

Multiple methods warning

I'm currently learning Objective C and in the process I've made the silly little program below. The program compiles fine - however I get the warning "multiple methods named '-setName:' found".
I've only interfaced and implemented the method once.
What does this warning mean, and how do I correct it?
#import <Foundation/Foundation.h>
// these are the three yoga-exercises we can perform
typedef enum {
kCobra,
kUniversal,
kDog
} ExerciseName;
// translating our variables into human
NSString *nameExercise (ExerciseName nameExercise)
{
switch (nameExercise) {
case kCobra:
return #"Cobra Pose";
break;
case kUniversal:
return #"Universal Stretch";
break;
case kDog:
return #"Dog Pose";
break;
}
return #"no clue!";
} // nameExercise
#interface Exercise : NSObject
{
ExerciseName name;
}
-(void) setName: (ExerciseName) name;
-(void) exerciseDo;
#end
#implementation Exercise
-(void) setName: (ExerciseName) n {
name = n;
} // setName
-(void) exerciseDo {
NSLog(#"Exercise: %#",
nameExercise(name));
}
#end
void executeExercises(id exercises[], int count) {
int i;
for(i=0; i<count; i++) {
id exercise = exercises[i];
[exercise exerciseDo];
}
}
int main (int argc, const char * argv[]) {
id exercises[1];
exercises[0] = [Exercise new]; // initiating an object of class Exercise
[exercises[0] setName:kDog];
executeExercises(exercises, 1);
return 0;
} //main
the meaning of the message is that there are multiple selectors with the name setName: in the translation (that is, it is declared in at least on other place among all included headers). the compiler may choose the wrong selector (which can introduce undefined behavior).
you can typically correct the problem using one (or more) of the following approaches:
1) rename the method to a unique name: e.g. setExerciseName may be ok, if not used in other translations.
2) match the signature of the other selector. e.g. setName:(NSString *)name
3) use type safety:
Exercise * ex = [Exercise new];
[ex setName:kCobra];
4) cast the variable to the type: [(Exercise*)exercise setName:kCobra];
5) restore the type with a new variable: Exercise * ex = exercise;
since you have declared the var as an id, you have erased the type, and it means that the object may respond any visible selector. in general, you should not erase the type in this manner, except when truly necessary.
the best approach i see is a combination of 1 and 3:
[ex setExerciseName:kCobra];