3

I have got two classes, Class1 and Class2, the second one inherited from the first one. I need to override -update method of Class1 to achieve my goals. The changes of -update method in inherited method are performed in the middle of code, so I can not use [super update]. Thats why I need to copy-paste original method from parent to inherited class. This method is using private methods of parent, so when I am trying to do overriding, I got warnings about absence of private methods because Class2 imports only Class1.h. To clarify, here is the code:

Class1.h:

@interface Class1 : NSObject
-(void) update;
@end

Class1.m:

@interface Class1 (Private)
-(void) private1;
-(void) private2;
@end

@implementation Class1

-(void) update
{
    [self private1];
    [self private2];
}

-(void) private1
{
    // some code
}

-(void) private2
{
    // another code
}

@end

Class2.h:

@interface Class2 : Class1
-(void) update;
@end

Class2.m:

@implementation Class2

-(void) update
{
    [self private1]; // warning here
    // do my own stuff between private methods, that is the reason of inheritance
    [self private2]; // warning too
}

@end

Also, Class1 is not in my ownership, it is the one from open-source library (Cocos3D, to be precise), so I could not change it (and that is why I do inheritance).

The question is: how can I remove warnings? The only solution I can see is to copy private methods' signatures to Class2, but it seems to be a dirty trick. Or, it would be perfect if somebody points not how to remove warnings, but how to achieve my goal in changing method more nicely.

medvedNick
  • 4,512
  • 4
  • 32
  • 50
  • Copying the class extension `@interface Class1 (Private) ... @end` seems like a clean solution to me. Using `performSelector` or `objc_msgSend` would be a dirty trick. – Martin R Mar 07 '13 at 13:54
  • 1
    What about putting out those common methods into public visibilty, as you need in sub-class. – Anoop Vaidya Mar 07 '13 at 13:58
  • @MartinR well, -performSelector is the most interesting solution, but since I copy-paste code from original -update, it seems to be too uncomfortable to rewrite invocation of private methods (there are about 10 of these methods, actually) – medvedNick Mar 07 '13 at 14:00
  • @AnoopVaidya I could not modify Class1 since it is not mine class, and putting them into Class2 means another copy-paste work, which I try to minimize. But so far I think this is the easiest way for now. – medvedNick Mar 07 '13 at 14:03
  • Could you elaborate why you can't use ` [super update]`? – Monolo Mar 07 '13 at 14:04
  • @medvedNick: But `performSelector` gives also warnings if you compile with ARC (see e.g. http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown). The compiler needs to know the message signatures, so a class extension interface seems OK to me. – Martin R Mar 07 '13 at 14:06
  • @Monolo I need to insert custom code into the middle of -update, so I can not use that – medvedNick Mar 07 '13 at 14:07
  • @MartinR hmm I not using ARC so that is not problem :) – medvedNick Mar 07 '13 at 14:09
  • But if you copy the class category interface, the compiler can check the number and types of the arguments and the return value. With `performSelector` the compiler cannot check. – Martin R Mar 07 '13 at 14:11
  • 1
    @NikolaiRuhe thanks for link, but I need the vise versa thing - to make a protected setter be public – medvedNick Mar 07 '13 at 14:24
  • adding to my last comment - not to "protected setter be public", but to "be setter from .m be protected" – medvedNick Mar 07 '13 at 15:03
  • @medvedNick As I understood you want to expose certain methods to subclasses. That's what is called "protected" access. In objc `@protected` is only applicable to ivars so we need a workaround for methods. Here's another post explaining how this is done: http://stackoverflow.com/questions/15212321/inherit-methods-declared-in-m-file – Nikolai Ruhe Mar 07 '13 at 15:22
  • @NikolaiRuhe yes, that is what I'm trying to achieve. And your answer in this link advices to copy headers to shared category, but this is the same if I copy private method's signature to the place, where it will be visible (in inherited class, for example), and this is what Martin R and Anoop Vaida were saying. I think I will end with that way – medvedNick Mar 07 '13 at 15:31
  • @ABHI... please do not mess up the layout of the question, you've broken code tags and changed my text to a non-understandable one – medvedNick Sep 26 '14 at 15:47

4 Answers4

7

No need for swizzling, performSelector: or any other runtime buggery.

Just move or copy this:

@interface Class1 (Private)
-(void) private1;
-(void) private2;
@end

To the beginning of your subclass's .m file.

Normally, when trying to achieve something like an @protected scope for methods, the declaration of such would be in a header file like Class1_Private.h and that header would be set to the private role in Xcode.

As others have noted, exposing a private method via this mechanism is a bit dangerous. Given that Cocos2D is open source, that danger is mitigated somewhat or you could always just modify it directly. Of course, doing so means you effectively have a branch, which is costly to maintain.

If this is something that other developers are likely to do, I'd suggest filing a bug with Cocos2D requesting that the methods be exposed for subclassing purposes.

bbum
  • 162,346
  • 23
  • 271
  • 359
2

The way Apple does this is to make any methods that can be overridden in a subclass public, but document the fact that they shouldn't be called directly and are only there to be overridden. Documentation for Apple's code is (usually) comprehensive, and often referred to, so this works. Of course, typical third party code isn't as well documented, nor is documentation as likely to be read...

Another solution: I often create a second header called "MyClass+SubclassMethods.h", and publicly declare subclass-overrideable, but otherwise private methods in a category there. In source files for users of the base class and subclasses, this isn't imported, so the methods stay (effectively) private. In subclasses of the base class, I import "MyClass+SubclassMethods.h" to allow me to override/call these methods. It's not a perfect solution, but without the notion of 'protected' methods in Objective-C, I'm not sure there's a much better one.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97
0

A category, as suggested in another response is a good solution given that Class1 cannot be modified. If it were possible to make adjustments to source, a class extension declared in a separate header file and #imported in both the Class1 implementation, and the Class2 implementation file would be the way because then you can share not only private methods but also instance variables marked @public or @protected this way. Find out more about class extensions from here:

http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

Briefly, a class extension is a bit like a category (sometimes called a private category) but it can only be declared in the same compilation unit where the class it extends is implemented, and it can declare (and auto-synthesize) properties and instance variables. There's a source code template included in Xcode for creating class extensions (see below.)

Class extension creation in Xcode

mz2
  • 4,672
  • 1
  • 27
  • 47
-2

You should be able to solve this problem using categories to extend your original class and try method swizzling to achieve your desired behavior.

beegz
  • 30
  • 4
  • that won't work since the category to original class has not access to private categories in .m file. So that will be almost the same as with my current code with inherited class. – medvedNick Mar 07 '13 at 14:19
  • @medvedNick My bad, I didn't consider that. I guess you will need to copy & paste and use 'performSelector' then. Since the situation is kinda bad design-wise in any case that's probably the best solution to keep stuff clean down the road. – beegz Mar 07 '13 at 14:59