Category分類添加成員變量

思考:如何實現給分類“添加成員變量”?

默認情況下,因爲分類底層結構的限制,不能添加成員變量到分類中。但可以通過關聯對象來間接實現。

我們現在來一步步分析:如下

// RMPerson類
@interface RMPerson : NSObject
@property (nonatomic, assign) int age;
@end

@implementation RMPerson
@end

---------------------------------------------------

//RMPerson的分類
@interface RMPerson (Test)
//{
//    int _weight;
//}
@property (nonatomic, assign) int weight;
@end

@implementation RMPerson (Test)
@end

// -------------------------------------------------

#import "ViewController.h"
#import "RMPerson.h"
#import "RMPerson+Test.h"

@interface ViewController ()
@end

// ViewController控制器的實現
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    RMPerson *person = [[RMPerson alloc] init];
    person.age = 10;
    person.weight = 30;
    
    RMPerson *person2 = [[RMPerson alloc] init];
    person2.age = 20;
    person2.weight = 40;
    
    NSLog(@"person-----  age : %d ,weight : %d",person.age,person.weight);
    NSLog(@"person2----- age : %d ,weight : %d",person2.age,person2.weight);
}

// 打印
 Terminating app due to uncaught exception 'NSInvalidArgumentException',
 reason: '-[RMPerson setWeight:]: unrecognized selector sent to instance 0x600003d1c4e0'

1.在大括號內直接聲明成員變量_weight,編譯器直接報錯誤Instance variables may not be placed in categories.所以分類中不能直接添加成員變量
2.在RMPerson+(Test)分類中只聲明瞭屬性,直接運行,報錯:reason: '-[RMPerson setWeight:]: unrecognized selector sent to instance 0x600003d1c4e0',沒有找到setWeight方法,那我們來在RMPerson+(Test).m中實現setWeight方法試試,是否就能添加屬性了?
(注意:在分類中聲明屬性,只會生成setter、getter方法的聲明,不會生成成員變量和setter、getter方法的實現)

RMPerson+Test.m
給屬性weight添加set、get方法,因爲分類不能添加成員變量,所以用_weitght接收會報錯。那我們可以用什麼方法取代成員變量_weight去接收setWeight方法傳進來的值呢?
字典?全局變量?我們嘗試下。

1.定義一個int的全局變量weight_替代成員變量_weight
int weight_;
@implementation RMPerson (Test)

// 1.定義一個int weight_替代成員變量_weight
- (void)setWeight:(int)weight {
    weight_ = weight;
}

- (int)weight {
    return weight_;
}

int的全局變量打印圖.png
的確是能實現了,由於使用的是全局變量來替代成員變量,但在創建多個對象的時候,重新調用set方法時,會覆蓋上一個對象的值,所以此方法是不可取的。
#####2.因爲每個對象,其屬性的值是不同的,存在一對一的關係,所以可以聲明字典來實現

#import "RMPerson+Test.h"

NSMutableDictionary *weight_;
+ (void)load {
    weight_ = [NSMutableDictionary dictionary];
}

@implementation RMPerson (Test)
// 2.定義NSMutableDictionary字典_weight替代成員變量_weight
// 在load方法裏創建weight_對象,因爲load只加載一次
+ (void)load {
    weight_ = [NSMutableDictionary dictionary];
}

- (void)setWeight:(int)weight {
    // 爲什麼使用person對象的內存地址作爲其key?因爲其內存地址是唯一的
    NSString *key = [NSString stringWithFormat:@"%p",self]; 
    weight_[key] = @(weight);
}

- (int)weight {
    NSString *key = [NSString stringWithFormat:@"%p",self];
    return [weight_[key] intValue];
}

@end

再次打印看看結果,的確是實現了我們想要的結果。從表面上看沒什麼區別,但實際屬性age屬性weight兩者的本質是有區別的,並且有一定的缺陷。所以,還有更好的處理方法來處理,使用關聯對象。
#####缺陷:
1.本質上的區別

  • person.age 是存放在Person內存裏面
  • person.weight 實際上是定義了一個全局的字典對象裏面

2.因爲使用全局字典對象存儲時,當創建多個對象時,不在同一線程狀態下訪問set或get方法,會造成同時訪問的安全性問題

3.關聯對象
// 3.關聯對象
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @"name");
}

//關聯對象key的常見用法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

使用屬性名作爲key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

使用get方法的@selecor作爲key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

綜合上述的三種方法,使用關聯對象最優,使用關聯對象更好的間接添加成員變量。

本文源碼可以在這裏獲得:https://github.com/476455183/OC-SourceCode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章