思考:如何實現給分類“添加成員變量”?
默認情況下,因爲分類底層結構的限制,不能添加成員變量到分類中。但可以通過關聯對象來間接實現。
我們現在來一步步分析:如下
// 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方法的實現)
給屬性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_;
}
的確是能實現了,由於使用的是全局變量來替代成員變量,但在創建多個對象的時候,重新調用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