從@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等等,都只能存對象而不能存基本類型呢?其實答案不固定,可是思想基本都一致,你們本身思考思考就好。

相關文章
相關標籤/搜索