4

I have a class that essentially acts as a light weight wrapper class around another class. It holds that other class as an iVar. I want to be able to expose certain properties (quite a few actually) of the iVar, but to do so I have to write out each property accessor like so:

- (void) setProperty:(Class *)value{
    _iVar.property = value;
}
- (Class *) property{
    return  _iVar.property;
}

Of course, I have to do this for every single property, which is a pain (there are about 30 of them). I would love to be able to synthesize this but I haven't been able to figure out how.

Is it possible to synthesize?

Also, I can't subclass....well, I might be able to but it's really not recommended. The iVar class is really quite heavy (it implements CoreText). I'd rather write out the methods by hand.

Aaron Hayman
  • 8,492
  • 2
  • 36
  • 63
  • Take a look at this question (and my own answer on it): http://stackoverflow.com/questions/8763028/ A very good question, though! – Richard J. Ross III Feb 23 '12 at 15:19
  • @RichardJ.RossIII Thanks for the help. The post on mikeash.com really helped me get my head around the issue. I posted the answer. – Aaron Hayman Feb 23 '12 at 19:13

2 Answers2

4

Ok, so here's the solution I found...ended up being pretty simple once you knew what to do. First overwrite '- (id) forwardingTargetForSelector:(SEL)aSelector' and return the iVar:

- (id) forwardingTargetForSelector:(SEL)aSelector{
    return iVar;
}

When the runtime is looking for a method and cannot find one, it will call this method to see if there is another object to forward the message to. Note that this method normally returns nil and if you return nil here, your program will crash (which is the appropriate behavior).

The second part of the problem is to shush the compiler errors/warnings you'll get when you try to send a message that's not declared. This is easily done by declaring a category you don't implement.

@interface Class (iVarClassMethods)
@propoperty (strong) Class *property1;
......more properties
@end

As long as you don't put in an implementation anywhere, aka @implementation Class (category), the compiler won't complain (it'll assume that the implementation is somewhere....).

Now the only drawback I see is if you change any of the properties in the interface of the iVar Class, you need to make sure you update all other classes that use the method described above, otherwise you'll crash when another class tries to send what is now the wrong method (and the compiler won't warn you beforehand). However, this can be gotten around. You can declare protocols in a category. So instead you create a separate protocol for the iVar class and move the methods/properties you wish out of the iVar class into the protocol.

@protocol iVarClassProtocol
@propoperty (strong) Class *property1;
......more properties
@end

Add that protocol to the iVar subclass so it has those methods declared through the protocol now.

@interface iVarClass <iVarClassProtocol>
....other methods/properties you don't need forwarded
@end

Finally, simply add the protocol to the category. So instead of the aforementioned category with explicit declarations you'll have:

@interface Class (iVarClassMethods) <iVarClassProtocol>
@end

Now, if you need to change any of the to-be-fowarded properties/methods, you change them in the protocol. The compiler will then warn you when you try to send the wrong method to the forwarding class.

Aaron Hayman
  • 8,492
  • 2
  • 36
  • 63
2

I think you can forward the messages to the ivar:

- (void) forwardInvocation: (NSInvocation*) invocation
{
    [invocation invokeWithTarget:ivar];
}

- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
    NSMethodSignature *our = [super methodSignatureForSelector:selector];
    NSMethodSignature *ivars = [ivar methodSignatureForSelector:selector];
    return our ? our : ivars;
}

Then you have to hide or fake the type of your object, for example by casting to id, otherwise the compiler will complain that your class does not implement those methods. Of course it would be best if you could come up with some better design that would do without such tricks.

zoul
  • 102,279
  • 44
  • 260
  • 354
  • However, you couldn't use dot-notation for this, as there wouldn't be the proper interface for it. – Richard J. Ross III Feb 23 '12 at 15:22
  • Yes. You can cast the object to the ivar’s class, that makes sense sometimes and will make the compiler happy even with dot-notation. – zoul Feb 23 '12 at 15:24
  • Generally speaking, you don't want to cast an object to a class that it is not, especially when dealing with something like properties, as many things could break along the way (what if the wrapper defines a property with the same name as one of the iVar, unexpected behavior comes out, as you would expect that it would call the iVar's implementation when it in reality calls the wrapper's implementation). – Richard J. Ross III Feb 23 '12 at 15:26
  • Yes. But generally speaking, this is a design mess and the solutions will probably all hurt, just on different places. – zoul Feb 23 '12 at 15:32
  • I agree, recasting the object isn't desirable. Especially since the primary class has methods the iVar class does not. – Aaron Hayman Feb 23 '12 at 16:25
  • I think you're on the right track though. I did find a solution, thanks to Richard J. Ross III (see his comment under my question). I did not write a proxy, but I got the solution here: http://mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html. Look at '- (id) forwardingTargetForSelector:(SEL)aSelector'. If you can figure out the solution and edit your answer, I'll accept it. Otherwise, I'll write out an answer in a couple hours. – Aaron Hayman Feb 23 '12 at 16:32