OC底層原理(三)、isa、對象和類結(jié)構(gòu)、屬性粗略分析

OC底層原理匯總

從探索isa的初始化開始

OC底層原理(一).alloc實際調(diào)用流程分析內(nèi)容最后的流程圖中,_class_createInstanceFromZone,我們分為三步:

  • 1、size = cls->instanceSize(extraBytes);獲取對象需要分配的內(nèi)存大小
  • 2、obj = (id)calloc(1, size);如何申請內(nèi)存
  • 3、obj->initInstanceIsa(cls, hasCxxDtor);初始化isa

上一篇中我們分析了獲取需要分配的大小,以及具體如何分配內(nèi)存,也就是第1步和第2步。本篇我們繼續(xù)對第三步進行探索,也就是isa的創(chuàng)建以及初始化。

分析initInstanceIsa方法的實現(xiàn)

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

initInstanceIsa內(nèi)部調(diào)用了initIsa,并且傳入第一個參數(shù)為cls(類的地址),第二個參數(shù)為true,第三個為hasCxxDtor,我們看看initIsa實現(xiàn)

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        //如果是純指針的isa,那么isa僅僅用于存放類地址
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        //分配一個isa_t變量newisa,并把所有位清零
        isa_t newisa(0);
        /*
         是否支持索引,默認蘋果平臺都不支持的
         SUPPORT_INDEXED_ISA為1表示類表的索引
        */
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        /*
         0x001d 8000 0000 0001ULL
         給isa一個初始值0x001d800000000001ULL
         在這個初始值中,已經(jīng)設(shè)置了nonpointer和magic的值
         */
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        /*
         將類的地址信息右移三位,放入預(yù)設(shè)好的類的存儲位置
         */
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

initIsa中,做了isa位域中四個初始化,分別是給magicnonpointer、has_cxx_dtorshiftcls賦了值。
大致流程如下:

initIsa流程

OC底層原理(一).alloc實際調(diào)用流程分析內(nèi)容中,我們知道initIsa來自于最初的alloc調(diào)用。也就是說,一個類調(diào)用alloc,除了分配內(nèi)存以及內(nèi)存對齊外,它還對內(nèi)部的成員變量isa作了初始化,每個對象一定有一個叫isa的成員變量,那么,isa到底是什么呢?

isa是什么

使用clang重寫分析

我們在main.m中創(chuàng)建一個繼承自NSObject的類LWPerson,代碼如下:

#import <Foundation/Foundation.h>

@interface LWPerson : NSObject

@property (nonatomic,copy) NSString *name;

@property (nonatomic,assign) short age;

@property (nonatomic,assign) BOOL isMan;


@end

@implementation LWPerson

+ (void)LWPersonClassMethod{
    NSLog(@"%s",__func__);
}
- (int)LWPersonInstanceMethod{
    NSLog(@"%s",__func__);
    NSArray *array = @[@1,@2,@3];
    return [array[1] intValue];
}

@end
int main(int argc, const char * argv[]) {
    
    LWPerson *person = [LWPerson alloc];
    person.name = @"Jobs";
    person.age = 5;
    person.isMan = YES;
    
    [person LWPersonInstanceMethod];
    [LWPerson LWPersonClassMethod];
    
    return 0;
}

我們在終端使用如下clang命令將main.m重寫為C、C++實現(xiàn)

clang -rewrite-objc main.m -o main.cpp

執(zhí)行命令后,在目錄中生成一個main.cpp文件,我們打開它,搜索LWPerson,我們刪除和main中無關(guān)的數(shù)據(jù)后,得到最后的代碼如下:

#ifndef _REWRITER_typedef_LWPerson
#define _REWRITER_typedef_LWPerson
typedef struct objc_object LWPerson;
typedef struct {} _objc_exc_LWPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_isMan;
struct LWPerson_IMPL {
    //這就是isa
    struct NSObject_IMPL NSObject_IVARS;
    BOOL _isMan;
    short _age;
    NSString *_name;
};


// @property (nonatomic,copy) NSString *name;

// @property (nonatomic,assign) short age;

// @property (nonatomic,assign) BOOL isMan;


/* @end */


// @implementation LWPerson

//類方法重寫
static void _C_LWPerson_LWPersonClassMethod(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_0,__func__);
}
//實例方法重寫
static int _I_LWPerson_LWPersonInstanceMethod(LWPerson * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_1,__func__);
    NSArray *array = ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);
    return ((int (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("objectAtIndexedSubscript:"), (NSUInteger)1), sel_registerName("intValue"));
}

//name的getter方法
static NSString * _I_LWPerson_name(LWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LWPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

//name的setter方法
static void _I_LWPerson_setName_(LWPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LWPerson, _name), (id)name, 0, 1); }
//age的getter方法
static short _I_LWPerson_age(LWPerson * self, SEL _cmd) { return (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)); }
//age的setter方法
static void _I_LWPerson_setAge_(LWPerson * self, SEL _cmd, short age) { (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)) = age; }
//isMan的setter方法
static BOOL _I_LWPerson_isMan(LWPerson * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)); }
//isMan的setter方法
static void _I_LWPerson_setIsMan_(LWPerson * self, SEL _cmd, BOOL isMan) { (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)) = isMan; }
// @end
int main(int argc, const char * argv[]) {
    //alloc方法調(diào)用
    LWPerson *person = ((LWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("alloc"));
    //調(diào)用setName:
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_2);
    //調(diào)用setAge:
    ((void (*)(id, SEL, short))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), (short)5);
    //調(diào)用setIsMan:
    ((void (*)(id, SEL, BOOL))(void *)objc_msgSend)((id)person, sel_registerName("setIsMan:"), ((bool)1));

    //調(diào)用實例方法LWPersonInstanceMethod
    ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("LWPersonInstanceMethod"));
    //調(diào)用類方法LWPersonClassMethod
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("LWPersonClassMethod"));

    return 0;
}

從上面的代碼可以看出以下幾點:

  • 1.OC類和對象底層都是結(jié)構(gòu)體
  • 2.每個對象都有一個默認的isa變量,它由NSObject繼承而來
  • 3.屬性的本質(zhì),就是實例變量+setter+getter
  • 4.實例變量值的獲取,就是對象首地址+地址偏移
  • 5.屬性值的設(shè)置,對于簡單類型值,直接通過地址偏移設(shè)置,其他底層都是調(diào)用了objc_setProperty函數(shù)。
  • 6.方法的調(diào)用,都是使用objc_msgSend發(fā)送消息

OC類和對象底層都是結(jié)構(gòu)體

我們打開objc781源碼,可以在objc-private.h中找到struct objc_object的定義,如下所示:

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;
    ......
}

我們看到struct objc_object中,有一個成員變量isa,另外有三個獲取isa的成員方法:

  • Class ISA()方法用于非taggedPointer對象獲取isa
  • Class rawISA();方法用于非taggedPointer對象非nonpointer對象獲取isa
  • Class getIsa();方法用于taggedPointer對象獲取isa

另外,它還提供了直接獲取isabits成員的方法uintptr_t isaBits() const,用const在函數(shù)末尾修飾,代表一個常成員函數(shù),僅能讀取。

此外,我們可以看到我們常用于指向?qū)ο箢愋偷?code>id類型,它是struct objc_object *的別名。

/// A pointer to an instance of a class.
typedef struct objc_object *id;

objc-runtime-new.h文件中,我們可以找到struct objc_class的定義,如下所示:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
}

從上面可以看出,struct objc_class繼承于objc_object,它有四個成員:

  • isa,來自于它的父類,isa_t類型
  • superclass,它是一個指向父類的指針
  • cache,cache_t類型,存儲緩存的結(jié)構(gòu)體
  • bitsclass_data_bits_t類型的結(jié)構(gòu)體,存儲了類中方法、協(xié)議、成員列表等的信息。

同樣,我們可以找到關(guān)于Class的定義,它是struct objc_class *的別名

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

對象和類結(jié)構(gòu)如下圖


對象與類結(jié)構(gòu)體

isa結(jié)構(gòu)

我們可以找到isa的定義,如下圖所示:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

從代碼我們可以看出,isa_t是一個聯(lián)合體,它有兩個成員,分別為Class clsuintptr_t bits。

我們知道,所謂聯(lián)合體,也叫共用體,它的所有成員變量共用一段內(nèi)存,它的大小等于最大的成員變量的大小,所以,我們可以得出,isa_t內(nèi)存所占大小為8字節(jié),64位

我們再看定義中更重要的一段定義ISA_BITFIELD;,它在isa.h中,如下所示

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

我們先不討論內(nèi)部的執(zhí)行結(jié)果,我們先談?wù)勱P(guān)于聯(lián)合體中定義struct,而struct中定義一連串的成員,這個是什么呢,在C語言中,我們將它叫做位域。

位域

位域,也叫位段,它是一種特殊的結(jié)構(gòu)體類型,其所有成員的長度均是以二進制位為單位定義的,結(jié)構(gòu)體的成員被稱為位段,位段定義的一般形式如下:

struct 結(jié)構(gòu)名
{
  類型 變量名1:長度
  類型 變量名2:長度
  ……
  類型 變量名n:長度
}

位段定義類型必須是int、unsignedsigned中的一種,也就是必須為整形

位段的特性如下:

  • 從低位到高位排列
  • 一個位段必須存在一個存儲單元中,不能跨兩個存儲單元,如果本單元不夠容納某位段,則從下一個單元開始存儲該位段
  • 可以用%d、%x、%u%o等格式字符,以整數(shù)形式輸出位段
  • 在數(shù)值表達式中引用位段時,系統(tǒng)自動將位段轉(zhuǎn)換為整形數(shù)。

isa中的位域意義

isa位段定義的意義如下圖所示

isa位域

在不同平臺下,isa的內(nèi)部存儲區(qū)域如下圖

isa位域占位

使用lldb對驗證isa存儲類地址

在之前,我們提到struct objc_obejct的成員函數(shù)ISA()是獲取nonpointer isa的類地址的方法,我們來看看它的實現(xiàn)

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

核心代碼為(Class)(isa.bits & ISA_MASK);,這一步將isa的數(shù)據(jù)與一個ISA_MASK進行按位與,就得到了類的地址信息。

define ISA_MASK        0x0000000ffffffff8ULL

我們使用一開始例子,打斷點如下


斷點

然后斷點執(zhí)行到這一步時候,我們使用如下命令得到結(jié)果

(lldb) x/4gx person
0x10054aa30: 0x001d8001000032f5 0x0000000000050001
0x10054aa40: 0x0000000100002040 0x0000000000000000
(lldb) po 0x001d8001000032f5 & 0x0000000ffffffff8ULL
LWPerson

這樣就驗證了isa是存儲類信息的。

objc_setProperty源碼分析

objc_setProperty源碼如下:

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        //如果偏移量為0,就是設(shè)置isa,也就是設(shè)置類地址信息
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        //新值retain
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        //進行賦值
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        //進行賦值
        *slot = newValue;        
        slotlock.unlock();
    }
    //舊值release
    objc_release(oldValue);
}

從源碼中我們可以看到,retain、release的操作在底層已經(jīng)幫我們完成了,所有涉及引用計數(shù)的屬性都要調(diào)用到objc_setProperty方法,這是一種適配器模式的思想,它大大的節(jié)約了我們的代碼量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。