2

I have class A which has this declaration in it's .m file:

@implementation A {
    NSObject *trickyObject;
}

And class B which has this declaration in it's .h file:

@interface B : A
@end

Is there any possibility to access the trickyObject from a method declared in the class B?

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
  • 1
    If `trickyObject` is public, you can. But it's really bad practice to expose ivars to other classes. It breaks encapsulation and violates several object oriented design rules. – rmaddy Jul 06 '15 at 15:38
  • I'm not sure about your question's nature; whether you want to grant access to your _own_ base class's ivar in an inherited instance, or you want to _hack_ other classes by accessing their ivars which they are not public on purpose? – holex Jul 06 '15 at 15:44
  • @holex, I want to _hack_ other classes (in this example, the class `A`) from a library. – Iulian Onofrei Jul 07 '15 at 07:58
  • Needless to say, and I suspect you know this, but the hacking other classes is an inherently fragile process. By definition, a class is always free to change its internal implementation details from one release to the next. Lol. The contract with developers is limited to the exposed public interfaces. And, for the sake of future readers, it’s worth noting that apps that attempt to interface with private methods/properties of Apple’s own frameworks will be categorically be rejected (for the reason outlined above). – Rob Apr 20 '20 at 18:12

1 Answers1

1

If you have a property or method that is private, but you want to make accessible to subclasses, you can put the declaration in a category.

So consider A:

//  A.h

@import Foundation;

@interface A : NSObject

// no properties exposed

@end

And

//  A.m

#import "A.h"

// private extension to synthesize this property

@interface A ()

@property (nonatomic) NSInteger hiddenValue;

@end

// the implementation might initialize this property

@implementation A

- (id)init {
    self = [super init];
    if (self) {
        _hiddenValue = 42;
    }
    return self;
}

@end

Then consider this category:

//  A+Protected.h

@interface A (Protected)

@property (readonly, nonatomic) NSInteger hiddenValue;

@end

Note, this extension doesn’t synthesize the hiddenValue (the private extension in A does that). But this provides a mechanism for anyone who imports A+Protected.h to have access to this property. Now, in this example, while hiddenValue is really readwrite (as defined in the private extension within A), this category is exposing only the getter. (You obviously could omit readonly if you wanted it to expose both the getter and the setter, but I use this for illustrative purposes.)

Anyway, B can now do things like:

//  B.h

#import "A.h"

@interface B : A

- (void)experiment;

// but again, no properties exposed

@end

And

//  B.m

#import "B.h"
#import "A+Protected.h"

@implementation B

// but with this category, B now has read access to this `hiddenValue`

- (void)experiment {
    NSLog(@"%ld", (long)self.hiddenValue);
}

@end

Now A isn’t exposing hiddenValue, but any code that uses this A (Protected) category (in this case, just B) can now access this property.

And so now you can call B methods that might be using the hiddenValue from A, while never exposing it in the public interfaces.

//  ViewController.m

#import "ViewController.h"
#import "B.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    B *b = [[B alloc] init];
    [b experiment];          // this calls `B`’s exposed method, and that method is using the property not exposed by `A.h`
}

@end

If you’re interested in a real-world example of this, consider UIKit’s:

@import UIKit.UIGestureRecognizerSubclass;

Generally the state of a UIGestureRecognizer is readonly, but this UIGestureRecognizer (UIGestureRecognizerProtected) category exposes the readwrite accessors for state (to be used, as the name suggests, by gesture recognizer subclasses only).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • "but you could define a property for that ivar". ^ The problem with this is that properties in a category cannot synthesize ivars. Even if I declare the property, as you did in your code example, how can I access the _trickyObject ivar from the category's implementation? – Enrique Apr 20 '20 at 16:57
  • @Enrique - Correct, `A` would have to synthesize this hidden property. But if this property was, for example, defined in a private extension, you could define a category that provides access to that property, but segregate it from `A`’s standard public interface. I’ve fleshed out a more complete example in my revised answer, above. – Rob Apr 20 '20 at 18:04
  • AFAIK, properties in categories not only *not* synthesize instance variables, they also *not* synthesize any property accessor methods either. Would I have to manually write the accessor methods in the category implementation A+Protected.m ? – Enrique Apr 21 '20 at 02:45
  • @Enrique - Again, correct, the category doesn’t cause the accessors and the ivar to be synthesized. The private extension triggered that. So, no, you don’t have to manually implement the accessor methods. See `A.m` in the above example. – Rob Apr 21 '20 at 03:55