从@property提及(四)深刻成员变量

图片描述

以前的三篇文章都讲的是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

objc_class

首先咱们知道,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等等,都只能存对象而不能存基本类型呢?其实答案不固定,可是思想基本都一致,你们本身思考思考就好。

相关文章
相关标签/搜索