3

I'm trying to create a generic implementation for the NSCoding protocol. The code will be wrapped around in a macro the will implement NSCoding. In order to implement the protocol, we need two functions:

-(void)encodeWithCoder:(NSCoder*)coder;
-(id)initWithCoder:(NSCoder*)coder;

A generic implementation of the initWithCoder function would be:

-(id)initWithCoder:(NSCoder*)coder { 
    if ([super conformsToProtocol:@protocol(NSCoding)]) 
        self = [super initWithCoder:coder];
    else {
        self = [super init];
    }
    if (!self) return self;
    self = [MyGenericCoder initWithCoder:coder forObject:self withClass:[__clazz class]]; 
    return self; 
}

The problematic line is self = [super initWithCoder:coder]; it will not compile since super does not respond to initWithCoder: when we use the in a class that its super does not implements NSCoding. Casting super to NSObject<NSCoding>* will not work with the LLVM compiler.

[super performSelector:(initWithCoder:) withObject:coder] will not work either since super == self, Which will result in an infinite loop.

How can I call [super initWithCoder:coder] in manner that will trigger the function in the superclass and will not generate a compilation warning / error?

Tomer Shiri
  • 949
  • 1
  • 10
  • 19
  • By 'it will not compile' you presumably mean you get warnings? Unless you have the relevant compiler option set to treat warnings as errors then I think it should at least compile. What does LLVM think if you just cast super to `(id)`? – Tommy Aug 24 '12 at 22:15

3 Answers3

2

You can use +instancesRespondToSelector: to find out if your superclass responds to the selector, and then objc_msgSendSuper() directly to actually call it.

#import <objc/message.h>

- (id)initWithCoder:(NSCoder *)coder {
    // Note: use [__clazz superclass] directly because we need the
    // compile-time superclass instead of the runtime superclass.
    if ([[__clazz superclass] instancesRespondToSelector:_cmd]) {
        struct objc_super sup = {self, [__clazz superclass]};
        ((id(*)(struct objc_super *, SEL, NSCoder*))objc_msgSendSuper)(&sup, _cmd, coder);
    } else {
        [super init];
    }
    if (!self) return self;
    self = [MyGenericCoder initWithCoder:coder forObject:self withClass:[__clazz class]];
    return self;
}
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • [My solution](http://stackoverflow.com/a/12116975/1300347) finds the superclass in runtime. Why do think its necessary to find it in compile time? – Tomer Shiri Aug 25 '12 at 12:31
  • @TomerShiri: No it doesn't. You're also referencing `__clazz`, which I'm assuming is a `Class` passed to the macro. That's the compile-time aspect. The caution was to avoid trying to figure it out based on `self`. – Lily Ballard Aug 25 '12 at 21:30
0
#import <objc/runtime.h>
-(id)initWithCoder:(NSCoder*)coder {
    Class superclass = class_getSuperclass([__clazz class]);
    SEL constructor = @selector(initWithCoder:);
    if (class_conformsToProtocol(superclass,@protocol(NSCoding))) {
        self = class_getMethodImplementation(superclass,constructor)(self,constructor,coder);
    }
    else {
        self = [super init];
    }
    if (!self) return self;
    self = [MyGenericCoder initWithCoder:coder forObject:self withClass:[__clazz class]];
    return self;
}
Tomer Shiri
  • 949
  • 1
  • 10
  • 19
0

How can I call [super initWithCoder:coder] in manner that will trigger the function in the superclass and will not generate a compilation warning / error?

Just create two variants of your macro -- one for types whose superclasses adopt NSCoding, and another for those which do not.

Either that, or abstract the detail from yourself and derive from intermediate types which abstract the condition from your bases and adopts NSCopying -- then you can just call initWithCoder: on any such type.

justin
  • 104,054
  • 14
  • 179
  • 226