0

I have a generator which returns objects conforming to protocol A. I would like to add a property, probably with categories, to these objects so i can do something to serve my purposes, which obviously isn't there in the protocol.

is this doable?

sramij
  • 4,775
  • 5
  • 33
  • 55
  • 2
    What kind of properties, of what types? Show us at least schematically what are you going to achieve. – Asperi Feb 08 '20 at 07:18
  • @Asperi Why is it important? assume it's an 'int' property. – sramij Feb 09 '20 at 22:00
  • Note that the linked answer won't directly for an `int` property. If you're serious about that, then you'll need to adjust it to wrap and unwrap it through `NSNumber`. If you weren't serious about `int`, that's why Asperi was asking. It matters. You can't do exactly the same things with primitive types that you can do with objects in ObjC. – Rob Napier Feb 09 '20 at 22:09
  • @RobNapier this question is different from the question linked above "Objective-C: Property / instance variable in category" in the sense that I am trying to add a property to all instances conforming to a protocol, and not to a specific class, and I don't have access to these classes. – sramij Feb 10 '20 at 01:34

2 Answers2

0

The more I've thought about this, the more I agree that it's not a duplicate, and in fact the answer is very straightforward as long as what you say you want is what you really want.

Given that you are returning values that conform to some protocol, create a type that conforms to that protocol, and forwards all protocol methods to a wrapped value. Then you can add whatever additional properties you'd like.

For example, given a protocol like:

@protocol Runnable <NSObject>
- (void)run;
@end

You can create a trivial wrapper like:

@interface AnyRunnable: NSObject <Runnable>
- (instancetype)initWithRunnable:(id<Runnable>)runnable;
@end

@interface AnyRunnable (Private)
@property (nonatomic, readwrite) id<Runnable> wrapped;
@end

@implementation AnyRunnable

- (instancetype)initWithRunnable:(id<Runnable>)wrapped
{
    self = [super init];
    if (self) {
        self.wrapped = wrapped;
    }
    return self;
}

- (void)run {
    [self.wrapped run];
}

@end

Now, AnyRunnable is a Runnable, so you can return that from your method. It's also a type you control, so you can add any properties you like to it. This scales to any protocol; you just need to implement the required methods.

It's not possible to do this via a category for the reasons given in Why can't categories have instance variables? (If it were possible to add properties based on protocols, but not classes, then you could just define a protocol that matched your class, and bypass that limitation.)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
0

Here is possible approach (based on Objective-C associated objects). Tested & worked.

Assume we have some class, which we cannot touch

    @interface SomeClass: NSObject
    @end

    @implementation SomeClass
    @end

Then some new properties can inject in the following way

    @interface SomeClass (VirtualProperty)
        @property (atomic) NSInteger virtualProperty;
        @property (nonatomic, readonly) NSInteger calculableProperty;
    @end

    static const char *kVirtualPropertyKey = "virtualProperty";

    @implementation SomeClass (VirtualProperty)
    @dynamic virtualProperty;

    - (NSInteger)calculableProperty {
        return self.virtualProperty * 2;
    }

    - (NSInteger)virtualProperty {
        return [(NSNumber *)objc_getAssociatedObject(self, 
            kVirtualPropertyKey) integerValue];
    }

    - (void)setVirtualProperty:(NSInteger)newValue {
        objc_setAssociatedObject(self, kVirtualPropertyKey, 
            @(newValue), OBJC_ASSOCIATION_RETAIN);
    }
    @end

Usage:

    SomeClass *some = SomeClass.new;
    some.virtualProperty = 5;
    NSLog(@"Result: %lu", some.calculableProperty);
Asperi
  • 228,894
  • 20
  • 464
  • 690