以前的三篇文章都讲的是interface和setter/getter,这一篇就讲一下ivar。ide
@interface MyViewController :UIViewController { NSString *name; } @end
.m文件中,你会发现若是你使用 self.name,Xcode会报错,提示你使用->,改为self->name就能够了。由于OC中,点语法是表示调用方法,而上面的代码中没有name这个方法。
因此在oc中点语法其实就是调用对象的setter和getter方法的一种快捷方式, self.name = myName 彻底等价于 [self setName:myName];函数
那么成员变量是什么时候分配内存,又储存在何处呢?ui
因此咱们就要先分析一下objc_class结构体。atom
首先咱们知道,OC中,全部的对象均可以认为是id类型。那么id类型是什么呢?spa
typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
根据runtime源码能够看到,id是指向Class类型的指针。
而Class类型是objc_class结构的指针,因而咱们能够看到objc_class结构体的定义:ssr
struct objc_class { Class superclass; const char *name; uint32_t version; uint32_t info; uint32_t instance_size; struct old_ivar_list *ivars; struct old_method_list **methodLists; Cache cache; struct old_protocol_list *protocols; // CLS_EXT only const uint8_t *ivar_layout; struct old_class_ext *ext; }; // runtime版本不一样会有修改,可是本质属性大体如此
能够看到Objective-C对象系统的基石:struct objc_class。
其中,咱们能够很快地发现struct objc_ivar_list *ivars,这个就是成员变量列表。指针
struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; int space; }; struct objc_ivar_list { int ivar_count; int space; struct objc_ivar ivar_list[1]; }
再深刻看就能看到ivar真正的定义了,名字,type,基地址偏移量,消耗空间。
实际上在objc_class结构体中,有ivar_layout这么一个东西。code
顾名思义存放的是变量的位置属性,与之对应的还有一个weakIvarLayout变量,不过在默认结构中没有出现。这两个属性用来记录ivar哪些是strong或者weak,而这个记录操做在runtime阶段已经被肯定好。
具体的东西能够参考sunnyxx孙源大神的文章Objective-C Class Ivar Layout 探索对象
因此,咱们几乎能够肯定,ivar的确是在runtime时期就已经被肯定。类型,空间,位置,三者齐全。blog
因此,这也就是为何分类不能简单地用@property来添加成员变量的缘由。
仍是sunnyxx大神的文章objc category的秘密,其中对OC分类的本质探索很是透彻。
先看一下分类的结构:
struct category_t { const char *name; /// 类名 classref_t cls; /// 类指针 struct method_list_t *instanceMethods; /// 实例方法 struct method_list_t *classMethods; /// 类方法 struct protocol_list_t *protocols; /// 扩展的协议 struct property_list_t *instanceProperties; /// 扩展属性 method_list_t *methodsForMeta(bool isMeta) { ... } property_list_t *propertiesForMeta(bool isMeta) { ... } };
能够看到,分类结构自己是不存在ivar的容器的,因此天然没有成员变量的位置。所以也很天然地没有办法自动生成setter和getter。
OC自己是一门原型语言,对象和类原型很像。类对象执行alloc方法就像是原型模式中的copy操做同样,类保存了copy所需的实例信息,这些信息内存信息在runtime加载时就被固定了,没有扩充Ivar的条件。
OC固然也没有封死动态添加成员变量这条路,由于咱们有_object_set_associative_reference函数能够用。
那么它的原理又是什么呢?
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); // disguised_ptr_t是包装的unsigned long类型 if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association); }
经过代码能够比较清楚的看出来,大概思路是经过维护Map,经过对象来生成一个惟一的 unsigned long 的变量来做为横坐标,查找到以后,再经过key作纵坐标去查找,这样就能找到对应的变量,也就是说只要在 某个对象 中key是惟一的,就能设置和获取对应的变量,这样就与class无关。
类的结构在runtime时期就已经肯定了,因此按照类结构的角度来看,类中的成员变量的地址都是基于类对象自身地址进行偏移的。
@interface Person: NSObject { NSString * _name; NSString * _sex; char _ch; } @property(nonatomic, copy) NSString *pName; @property(nonatomic, copy) NSString *pSex; @end @implementation Person - (instancetype)init { if (self = [super init]) { NSLog(@"%p, %p, %p, %p, %p, %p, %p", self, &_name, &_sex, &_ch, _pName, _pSex); } return self; } @end
后面三个地址确实相差为8位,可是在类对象self和第一个成员变量之间相差的地址是10位。这0x10位的地址偏移,实际上就是isa指针的偏移。
指针在64位系统中占8位地址很正常,可是char类型的成员变量同样也是偏移了8位,明明char类型只须要1bit。这其实是为了内存对齐。
实际上在给类添加成员变量时,会调用这个函数:
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type)
alignment参数就是表明内存对齐方式。
uint32_t offset = cls->unalignedInstanceSize(); uint32_t alignMask = (1<<alignment)-1; offset = (offset + alignMask) & ~alignMask;
上面这段代码就是地址偏移计算
苹果规定了某个变量它的偏移默认为1 <<
alignment,而在上下文中这个值为指针长度。所以,OC中类结构地址的偏移计算与结构体仍是有不一样的,只要是小于8bit长度的地址,统一归为8bit偏移
具体的绑定ivar都经过addIvar这个函数,包括@synthesize关键字的绑定,具体@synthesize绑定成员变量只须要看一下class_addIvar的具体实现便可。
其实经过对成员变量绑定的分析,还有一个类似的问题:为何OC中的集合类,例如NSArray, NSDictionary等等,都只能存对象而不能存基本类型呢?其实答案不固定,可是思想基本都一致,你们本身思考思考就好。