既然要探究的是分类,那么我们先看一下分类的定义
//Category表示一个结构体指针的类型
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
再看下Class的定义
//Class也表示一个结构体指针的类型
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
对比可以发现category中少了 struct objc_ivar_list * _Nullable ivars
也就是说没有ivar数组,猜测这是原因,下面来做实验。
声明一个Dog类
@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Dog
@end
然后打印这个类中的所有的属性、ivar、方法列表:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//获取属性
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([Dog class], &propertyCount);
for (int i = 0; i < propertyCount; i ++) {
objc_property_t property = properties[i];
NSLog(@"属性:%d-----%s", i, property_getName(property));
}
free(properties); //注意释放c指针,以免内存泄露
//获取ivar
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList([Dog class], &ivarCount);
for (int i = 0; i < ivarCount; i ++) {
Ivar ivar = ivars[i];
NSLog(@"ivar:%d-----%s", i, ivar_getName(ivar));
}
free(ivars);
//获取方法列表
unsigned int methodCount;
Method *methods = class_copyMethodList([Dog class], &methodCount);
for (int i = 0; i < methodCount; i ++) {
Method m = methods[i];
NSLog(@"SEL:%d-----%s", i, sel_getName(method_getName(m)));
}
free(methods);
}
打印结果:name的属性,系统默认生成的_name的实例变量,还有getter setter方法
2018-08-15 22:59:24.378267+0800 Dog[46057:1034263] 属性:0-----name
2018-08-15 22:59:24.378466+0800 Dog[46057:1034263] ivar:0-----_name
2018-08-15 22:59:24.378853+0800 Dog[46057:1034263] SEL:0-----.cxx_destruct
2018-08-15 22:59:24.379576+0800 Dog[46057:1034263] SEL:1-----name
2018-08-15 22:59:24.379703+0800 Dog[46057:1034263] SEL:2-----setName:
下面来增加一个类目,看看发生声明情况
@interface Dog (ext)
@property (nonatomic, strong)UIColor *color;
@end
@implementation Dog (ext)
/*此时已经报警告了:警告的意思是没有实现color的getter和setter方法。那么系统有没有帮我们生成_color呢?
Property 'color' requires method 'color' to be defined - use @dynamic or provide a method implementation in this category
Property 'color' requires method 'setColor:' to be defined - use @dynamic or provide a method implementation in this category
*/
@end
加上分类之后打印结果:发现只是在属性的列表里添加了color属性,并没有生成对应的ivar即_color,也出现了上面警告的并没有实现getter和setter方法;所以我们无法通过getter和setter方法操作color也不能直接访问_color,应该是并不存在这样一个地址供我们去访问。
2018-08-15 22:59:24.378029+0800 Dog[46057:1034263] 属性:0-----color
2018-08-15 22:59:24.378267+0800 Dog[46057:1034263] 属性:1-----name
2018-08-15 22:59:24.378466+0800 Dog[46057:1034263] ivar:0-----_name
2018-08-15 22:59:24.378853+0800 Dog[46057:1034263] SEL:0-----.cxx_destruct
2018-08-15 22:59:24.379576+0800 Dog[46057:1034263] SEL:1-----name
2018-08-15 22:59:24.379703+0800 Dog[46057:1034263] SEL:2-----setName:
问题分析完了,如何添加呢?根据问题我们要解决的是实现getter和setter方法,并且找到一个地址空间供我们访问:系统也帮我们提供了这样的方法:
@interface Dog (ext)
@property (nonatomic, strong)UIColor *color;
@end
@implementation Dog (ext)
//这里用@selector(color)来用作 const void *key 的指针
- (UIColor *)color {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setColor:(UIColor *)color {
objc_setAssociatedObject(self, @selector(color), color, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
进行关联之后来看下,是不是对应的getter和setter已经ivar了,打印结果:
2018-08-16 11:02:08.120595+0800 Dog[79591:1182559] 属性:0-----color
2018-08-16 11:02:08.124159+0800 Dog[79591:1182559] 属性:1-----name
2018-08-16 11:02:08.124811+0800 Dog[79591:1182559] ivar:0-----_name
2018-08-16 11:02:08.125605+0800 Dog[79591:1182559] SEL:0-----.cxx_destruct
2018-08-16 11:02:08.125983+0800 Dog[79591:1182559] SEL:1-----name
2018-08-16 11:02:08.126145+0800 Dog[79591:1182559] SEL:2-----setName:
2018-08-16 11:02:08.128130+0800 Dog[79591:1182559] SEL:3-----color
2018-08-16 11:02:08.128328+0800 Dog[79591:1182559] SEL:4-----setColor:
可以看到实现了getter和setter之后已经能够看到了color和setColor了,但是仍然没有ivar的_color,这个是当然的,系统没有实现我们也没添加,所谓的关联是我们通过const char的key(指针)来访问关联的对象的,所以关联之后我们只能通过getter和setter方法去操作,不能直接用ivar _color访问!!!
上面是把我们看到的现象进行分析,思考其为什么会有这样的现象呢?分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译器生效的,所以能直接为类添加属性或者实例变量。
####由此推想protocol添加属性会怎么?
由上面的分析是不是想到了protocol中添加属性?其实由protocol的用途也能猜测出:protocol是一系列的协议,要求代理去实现,自己并没有实现,方法或属性都是这样,只是做了声明要求代理去实现。所以添加的属性也只是声明其代理有实现这个属性,自身并没有实现其getter、setter以及ivar。有兴趣的朋友可以实践看看。
以上问题的分析及解决办法,有什么错误还望指教!