从定义分析
先看一下分类的定义:
//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;
}
再看下 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
,也就是说没有 ivars
数组,也就没有地方来保存属性的值。
源码实测
先定义一个类 Person:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
然后打印这个类中的所有的属性、ivar、方法列表:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 获取属性
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([Person 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([Person 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([Person 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);
}
打印结果如下:
2019-11-15 09:58:22.117 MusicPlayer[25393:2629911] 属性:0-----name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] 属性:1-----age
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] ivar:0-----_age
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] ivar:1-----_name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:0-----.cxx_destruct
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:1-----setName:
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:2-----name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:3-----setAge:
2019-11-15 09:58:22.120 MusicPlayer[25393:2629911] SEL:4-----age
接着添加分类:
@interface Person (Height)
@property (nonatomic, assign) float height;
@end
@implementation Person (Height)
@end
此时 XCode 报警告,没有实现 setter 和 getter。必须实现:
- (float)height {
return [objc_getAssociatedObject(self, _cmd) floatValue];
}
- (void)setHeight:(float)height {
objc_setAssociatedObject(self, @selector(height), @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
此时,输出变为:
2019-11-15 10:20:12.838 MusicPlayer[25451:2634649] 属性:0-----height
2019-11-15 10:20:12.839 MusicPlayer[25451:2634649] 属性:1-----name
2019-11-15 10:20:12.839 MusicPlayer[25451:2634649] 属性:2-----age
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] ivar:0-----_age
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] ivar:1-----_name
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] SEL:0-----.cxx_destruct
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] SEL:1-----setName:
2019-11-15 10:20:12.841 MusicPlayer[25451:2634649] SEL:2-----name
2019-11-15 10:20:12.844 MusicPlayer[25451:2634649] SEL:3-----height
2019-11-15 10:20:12.845 MusicPlayer[25451:2634649] SEL:4-----setHeight:
2019-11-15 10:20:12.845 MusicPlayer[25451:2634649] SEL:5-----setAge:
2019-11-15 10:20:12.846 MusicPlayer[25451:2634649] SEL:6-----age
可以看到实现了 setter 和 getter 之后,仍然没有 ivar: _height
,系统没有实现,我们也没有添加。所谓的关联是我们通过 const char 的 key (指针)来访问关联的对象的。所以关联之后我们只能通过 getter 和 setter 方法去操作,不能直接用 ivar _height
来访问!
关联对象的释放
关联对象会在 object_dispose()
方法中释放,不需要用户去释放。
NSObject
调用 -dealloc
时,只调用了 object_dispose()
。
而 object_dispose()
则做了以下事情:
- 为 C++ 的实例变量们(iVars)调用 destructors
- 为 ARC 状态下的 实例变量们(iVars) 调用 -release
- 解除所有使用 runtime Associate 方法关联的对象
- 解除所有 __weak 引用
- 调用 free()
总结
分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译时生效的,所以能直接为类添加属性或者实例变量。
另外,个人认为:分类中添加 setter 和 getter,其实可以交给编译器来处理,这样会显得语言的设计更加简洁。在类中,声明属性是可以不写 setter 和 getter 的,编译器会帮我们实现,而分类为什么不呢?毕竟分类的属性声明确定之后,setter 和 getter 的实现其实也已经确定了。
由上面的分析联想到 protocol 中添加属性,其实由 protocol 的用途也能猜测出:protocol 是一系列的协议,要求代理去实现,自己并没有实现。方法或属性都是这样,只是做了声明要求代理去实现。所以添加的属性也只是声明其代理有实现这个属性,自身并没有实现其 getter、setter 以及 ivar。
附
- 深入理解Objective-C:Category