iOS开发iOSiOS面试

分类为什么不能添加成员变量

2018-08-18  本文已影响301人  说不出口的喵

Property是属性y,Ivar是成员变量

其实分类中是可以为一个类添加属性的,但是一定做不到添加成员变量,不要混淆了成员变量和属性的概念

在一个分类中添加了一个属性,Xcode不会自动的为其生成一个下划线开头的成员变量及set和get方法,如果你没有手动的实现这两个方法,直接在外面通过点语法调用这个属性,肯定就直接挂了,Unrecognised selector send to instance,因为他压根就没有这两个方法,所以当你真的在分类中声明了一个属性的时候,就要手动的去实现这个属性的set和get方法,这个时候就要用到运行时机制了,关联上去这个属性的存取过程。

那么为什么不能给分类添加成员变量呢

成员变量是一个类的东西,分类本身就不是一个类,分类本来就是OC里面通过运行时动态的为一个类添加的一些方法和属性等,不是一个真正的类,你怎么给他添加成员变量呢?

分类里面不能添加Ivar是因为分类本身并不是一个真正的类,它并没有自己的ISA。类最开始生成了很多基本属性,比如IvarList,MethodList,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。这就是为什么分类里面不能增加成员变量的原因。

为什么不能添加成员变量呢?

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

typedef struct objc_class *Class;

objc_class结构体的定义如下:

structobjc_class{Class isa  OBJC_ISA_AVAILABILITY;#if!__OBJC2__Class super_class                      OBJC2_UNAVAILABLE;// 父类constchar*name                        OBJC2_UNAVAILABLE;// 类名longversion                            OBJC2_UNAVAILABLE;// 类的版本信息,默认为0longinfo                              OBJC2_UNAVAILABLE;// 类信息,供运行期使用的一些位标识longinstance_size                      OBJC2_UNAVAILABLE;// 该类的实例变量大小structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;// 该类的成员变量链表structobjc_method_list**methodListsOBJC2_UNAVAILABLE;// 方法定义的链表structobjc_cache*cacheOBJC2_UNAVAILABLE;// 方法缓存structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;// 协议链表#endif} OBJC2_UNAVAILABLE;

在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。

在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_allocateClassPair之后,objc_registerClassPair之前才可以被使用,同样没有机会再添加成员变量。

Category不能添加成员变量(instance variables),那到底能不能添加属性(property)呢?

这个我们要从Category的结构体开始分析:

typedefstructcategory_t{constchar*name;//类的名字classref_tcls;//类structmethod_list_t*instanceMethods;//category中所有给类添加的实例方法的列表structmethod_list_t*classMethods;//category中所有添加的类方法的列表structprotocol_list_t*protocols;//category实现的所有协议的列表structproperty_list_t*instanceProperties;//category中添加的所有属性}category_t;

从Category的定义也可以看出Category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。

那我们为什么经常听说说类别不能添加属性呢?实际上,Category实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。那我们想要实现我们平时的属性所具有的功能应该怎么样做呢?

其实我们可以使用runtime去做,使用runtime去实现Category为已有的类添加新的属性并生成getter和setter方法。不要忘记了Objective-C是动态语言。方法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。

//NSObject+IndieBandName.h@interfaceNSObject(IndieBandName)@property(nonatomic,strong)NSString*indieBandName;@end

上面是头文件声明,下面的实现的.m文件:

// NSObject+IndieBandName.m #import"NSObject+Extension.h"#importstaticconstvoid*IndieBandNameKey = &IndieBandNameKey;@implementationNSObject(IndieBandName)@dynamicindieBandName;- (NSString*)indieBandName {returnobjc_getAssociatedObject(self, IndieBandNameKey);}- (void)setIndieBandName:(NSString*)indieBandName { objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end

通过runtime的两种方法就可以为类别添加一个实例变量了。

形式的转载都请联系作者获得授权并注明出处。

上一篇下一篇

猜你喜欢

热点阅读