Objective-C: Property / instance variable in category

前端 未结 6 1363
清酒与你
清酒与你 2020-11-22 16:56

As I cannot create a synthesized property in a Category in Objective-C, I do not know how to optimize the following code:

@interface MyClass (Variant)
@prope         


        
相关标签:
6条回答
  • 2020-11-22 17:29

    @lorean's method will work (note: answer is now deleted), but you'd only have a single storage slot. So if you wanted to use this on multiple instances and have each instance compute a distinct value, it wouldn't work.

    Fortunately, the Objective-C runtime has this thing called Associated Objects that can do exactly what you're wanting:

    #import <objc/runtime.h>
    
    static void *MyClassResultKey;
    @implementation MyClass
    
    - (NSString *)test {
      NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
      if (result == nil) {
        // do a lot of stuff
        result = ...;
        objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
      return result;
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-22 17:38

    Another possible solution, perhaps easier, which doesn't use Associated Objects is to declare a variable in the category implementation file as follows:

    @interface UIAlertView (UIAlertViewAdditions)
    
    - (void)setObject:(id)anObject;
    - (id)object;
    
    @end
    
    
    @implementation UIAlertView (UIAlertViewAdditions)
    
    id _object = nil;
    
    - (id)object
    {
        return _object;
    }
    
    - (void)setObject:(id)anObject
    {
        _object = anObject;
    }
    @end
    

    The downside of this sort of implementation is that the object doesn't function as an instance variable, but rather as a class variable. Also, property attributes can't be assigned(such as used in Associated Objects like OBJC_ASSOCIATION_RETAIN_NONATOMIC)

    0 讨论(0)
  • 2020-11-22 17:43

    Just use libextobjc library:

    h-file:

    @interface MyClass (Variant)
    @property (nonatomic, strong) NSString *test;
    @end
    

    m-file:

    #import <extobjc.h>
    @implementation MyClass (Variant)
    
    @synthesizeAssociation (MyClass, test);
    
    @end
    

    More about @synthesizeAssociation

    0 讨论(0)
  • 2020-11-22 17:45

    .h-file

    @interface NSObject (LaserUnicorn)
    
    @property (nonatomic, strong) LaserUnicorn *laserUnicorn;
    
    @end
    

    .m-file

    #import <objc/runtime.h>
    
    static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;
    
    @implementation NSObject (LaserUnicorn)
    
    - (LaserUnicorn *)laserUnicorn {
        return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
    }
    
    - (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
        objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
    }
    
    @end
    

    Just like a normal property - accessible with dot-notation

    NSObject *myObject = [NSObject new];
    myObject.laserUnicorn = [LaserUnicorn new];
    NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);
    

    Easier syntax

    Alternatively you could use @selector(nameOfGetter) instead of creating a static pointer key like so:

    - (LaserUnicorn *)laserUnicorn {
        return objc_getAssociatedObject(self, @selector(laserUnicorn));
    }
    
    - (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
        objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
    }
    

    For more details see https://stackoverflow.com/a/16020927/202451

    0 讨论(0)
  • 2020-11-22 17:47

    The given answer works great and my proposal is just an extension to it that avoids writing too much boilerplate code.

    In order to avoid writing repeatedly getter and setter methods for category properties this answer introduces macros. Additionally these macros ease the use of primitive type properties such as int or BOOL.

    Traditional approach without macros

    Traditionally you define a category property like

    @interface MyClass (Category)
    @property (strong, nonatomic) NSString *text;
    @end
    

    Then you need to implement a getter and setter method using an associated object and the get selector as the key (see original answer):

    #import <objc/runtime.h>
    
    @implementation MyClass (Category)
    - (NSString *)text{
        return objc_getAssociatedObject(self, @selector(text));
    }
    
    - (void)setText:(NSString *)text{
        objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    @end
    

    My suggested approach

    Now, using a macro you will write instead:

    @implementation MyClass (Category)
    
    CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)
    
    @end
    

    The macros are defined as following:

    #import <objc/runtime.h>
    
    #define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
    #define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
    #define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)
    
    #define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
    #define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
    
    #define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
    #define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
    #define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)
    

    The macro CATEGORY_PROPERTY_GET_SET adds a getter and setter for the given property. Read-only or write-only properties will use the CATEGORY_PROPERTY_GET and CATEGORY_PROPERTY_SET macro respectively.

    Primitive types need a little more attention

    As primitive types are no objects the above macros contain an example for using unsigned int as the property's type. It does so by wrapping the integer value into a NSNumber object. So its usage is analog to the previous example:

    @interface ...
    @property unsigned int value;
    @end
    
    @implementation ...
    CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
    @end
    

    Following this pattern, you can simply add more macros to also support signed int, BOOL, etc...

    Limitations

    1. All macros are using OBJC_ASSOCIATION_RETAIN_NONATOMIC by default.

    2. IDEs like App Code do currently not recognize the setter's name when refactoring the property's name. You would need to rename it by yourself.

    0 讨论(0)
  • 2020-11-22 17:52

    Tested only with iOS 9 Example: Adding an UIView property to UINavigationBar (Category)

    UINavigationBar+Helper.h

    #import <UIKit/UIKit.h>
    
    @interface UINavigationBar (Helper)
    @property (nonatomic, strong) UIView *tkLogoView;
    @end
    

    UINavigationBar+Helper.m

    #import "UINavigationBar+Helper.h"
    #import <objc/runtime.h>
    
    #define kTKLogoViewKey @"tkLogoView"
    
    @implementation UINavigationBar (Helper)
    
    - (void)setTkLogoView:(UIView *)tkLogoView {
        objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (UIView *)tkLogoView {
        return objc_getAssociatedObject(self, kTKLogoViewKey);
    }
    
    @end
    
    0 讨论(0)
提交回复
热议问题