思考:如何实现给分类“添加成员变量”?
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现。
我们现在来一步步分析:如下
// 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
网友评论