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))