objective-coopobjective-c-category

extend class in objective-c with variable based property


I've got a form implementation in objective-c and I'd like to extend my widgets (NSButton, NSTextField, etc..) to contain additional string representing their unique identifier string to be used after submit event occur, which trigger generation of json contain all widget id/value pairs.

I've tried using categories to extend NSControl which is the common parent of all those widgets in the following way.

NSControl+formItemSupport.h
-------------------------------

@interface NSControl (formItemSupport)

@property NSString * formItemId;

@end

NSControl+formItemSupport.m
-------------------------------
@implementation NSControl (formItemSupport)

-(NSString *)formItemId {
    return self.formItemId;
}

-(void)setFormItemId:(NSString *)formItemId {
    self.formItemId = formItemId;
}


in the form.m file I import from NSControl+formItemSupport.m but when I try to set this field in NSButton : NSControl object. However, when I try to set the property formItemId, I get into infinite loop. Perhaps there's another way for extending objc class with variable based property without using inheritance ?


Solution

  • you can

    @synthesize formItemId = _formItemId;
    
    //synthesize needs local declaration of _formItemId;
    @implementation ExtraWurst {
        NSString *_formItemId;
    }
    

    but this is done behind the scene for you from Xcode without @synthesize.
    Sometime it is still easier to define the use of an internal variable for a property in this way.

    apart from that you can and have to change your setter and getter methods in the following way.

    -(NSString *)formItemId {
        return _formItemId;
    }
    
    -(void)setFormItemId:(NSString *)formItemId {
        _formItemId = formItemId;
    }
    

    this will prevent you from ending up in a loop.

    Why?
    Because self.formItemId = refers to -(void)setFormItemId:
    So you would call the setter inside the setter that will set with the same again and again aka an endless loop. You can take care of the getter the same way as shown above.

    Where to use self.yourProperty then?
    You can use self.formItemId anywhere in the class but not inside getter and setter of formItemId.

    Correctly mentioned, Instance variables may not be placed in categories. Meaning if you need such you have to subclass UIControl but that breaks the inheritance of your used UIControls. You would have to subclass all your SpecialUIControls you are using later.

    Another solution, you could define a constant in your implementation and go with objective-C runtime functions and associate this constant yourself. Beware because you transform the ObjectModel for all UIControl classes then..

    #import "NSControl+formItemSupport.h"
    #import <objc/runtime.h>
    
    @implementation UIControl (formItemSupport)
    NSString const *key = @"formItemSupport.forItemKey";
    -(void)setFormItemId:(NSString *)formItemId {
        objc_setAssociatedObject(self, &key, formItemId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    -(NSString *)formItemId {
        return objc_getAssociatedObject(self, &key);
    }
    @end
    

    still, its much easier and safer and flexible to subclass your own UIControl instead to extent all subclasses inherited from UIControl.
    Why is subclassing easier here?
    As you mentioned you want to json later on with the given formItemId per Control you can make use of an archiver / unarchiver design pattern of your subclasses which are nice to jsonify later.