探秘Runtime - 剖析Runtime結(jié)構(gòu)體

該文章屬于劉小壯原創(chuàng)呀潭,轉(zhuǎn)載請(qǐng)注明:劉小壯


NSObject

之前的定義

OC1.0中睛蛛,Runtime很多定義都寫在NSObject.h文件中锨侯,如果之前研究過(guò)Runtime的同學(xué)可以應(yīng)該見過(guò)下面的定義,定義了一些基礎(chǔ)的信息鸭你。

// 聲明Class和id
typedef struct objc_class *Class;
typedef struct objc_object *id;

// 聲明常用變量
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;

// objc_object和objc_class
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class {  
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

之前的Runtime結(jié)構(gòu)也比較簡(jiǎn)單稿械,都是一些很直接的結(jié)構(gòu)體定義,現(xiàn)在新版的Runtime在操作的時(shí)候乏沸,各種地址偏移操作和位運(yùn)算淫茵。

之后的定義

后來(lái)可能蘋果也不太想讓開發(fā)者知道Runtime內(nèi)部的實(shí)現(xiàn),所以就把源碼定義從NSObject中搬到Runtime中了蹬跃。而且之前的定義也不用了匙瘪,通過(guò)OBJC_TYPES_DEFINED預(yù)編譯指令,將之前的代碼廢棄調(diào)了蝶缀。

現(xiàn)在NSObject中的定義非常簡(jiǎn)單丹喻,直接就是一個(gè)Class類型的isa變量,其他信息都隱藏起來(lái)了翁都。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

這是最新的一些常用Runtime定義碍论,和之前的定義也不太一樣了,用了最新的結(jié)構(gòu)體對(duì)象柄慰,之前的結(jié)構(gòu)體也都廢棄了鳍悠。

typedef struct objc_class *Class;
typedef struct objc_object *id;

typedef struct method_t *Method;
typedef struct ivar_t *Ivar;
typedef struct category_t *Category;
typedef struct property_t *objc_property_t;

對(duì)象結(jié)構(gòu)體

objc_object定義

OC中每個(gè)對(duì)象都是一個(gè)結(jié)構(gòu)體税娜,結(jié)構(gòu)體中都包含一個(gè)isa的成員變量,其位于成員變量的第一位藏研。isa的成員變量之前都是Class類型的敬矩,后來(lái)蘋果將其改為isa_t

struct objc_object {
private:
    isa_t isa;
};

OC中的類和元類也是一樣蠢挡,都是結(jié)構(gòu)體構(gòu)成的弧岳。由于類的結(jié)構(gòu)體定義繼承自objc_object,所以其也是一個(gè)對(duì)象业踏,并且具有對(duì)象的isa特征禽炬。

對(duì)象結(jié)構(gòu)體

所以可以通過(guò)isa_t來(lái)查找對(duì)應(yīng)的類或元類,查找方法應(yīng)該是通過(guò)uintptr_t類型的bits勤家,通過(guò)按位操作來(lái)查找isa_t指向的類的地址腹尖。

實(shí)例對(duì)象或類對(duì)象的方法,并不會(huì)定義在各個(gè)對(duì)象中却紧,而是都定義在isa_t指向的類中桐臊。查找到對(duì)應(yīng)的類后,通過(guò)類的class_data_bits_t類型的bits結(jié)構(gòu)體查找方法晓殊,對(duì)象断凶、類、元類都是同樣的查找原理巫俺。

isa_t定義

isa_t是一個(gè)union的結(jié)構(gòu)對(duì)象认烁,union類似于C++結(jié)構(gòu)體,其內(nèi)部可以定義成員變量和函數(shù)介汹。在isa_t中定義了cls却嗡、bitsisa_t三部分嘹承,下面的struct結(jié)構(gòu)體就是isa_t的結(jié)構(gòu)體構(gòu)成窗价。

下面對(duì)isa_t中的結(jié)構(gòu)體進(jìn)行了位域聲明,地址從nonpointer起到extra_rc結(jié)束叹卷,從低到高進(jìn)行排列撼港。位域也是對(duì)結(jié)構(gòu)體內(nèi)存布局進(jìn)行了一個(gè)聲明,通過(guò)下面的結(jié)構(gòu)體成員變量可以直接操作某個(gè)地址骤竹。位域總共占8字節(jié)帝牡,所有的位域加在一起正好是64位。

小提示:unionbits可以操作整個(gè)內(nèi)存區(qū)蒙揣,而位域只能操作對(duì)應(yīng)的位靶溜。

下面的代碼是不完整代碼,只保留了arm64部分,其他部分被忽略掉了罩息。

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

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; // 是32位還是64位
        uintptr_t has_assoc         : 1; // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用嗤详,如果沒有關(guān)聯(lián)引用,可以更快的釋放對(duì)象
        uintptr_t has_cxx_dtor      : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的析構(gòu)函數(shù)
        uintptr_t shiftcls          : 33; // 對(duì)象指向類的內(nèi)存地址扣汪,也就是isa指向的地址
        uintptr_t magic             : 6; // 對(duì)象是否初始化完成
        uintptr_t weakly_referenced : 1; // 對(duì)象是否被弱引用或曾經(jīng)被弱引用
        uintptr_t deallocating      : 1; // 對(duì)象是否被釋放中
        uintptr_t has_sidetable_rc  : 1; // 對(duì)象引用計(jì)數(shù)太大断楷,是否超出存儲(chǔ)區(qū)域
        uintptr_t extra_rc          : 19; // 對(duì)象引用計(jì)數(shù)
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
// ····
# else
// ····
# endif
};

ARM64架構(gòu)下,isa_t以以下結(jié)構(gòu)進(jìn)行布局崭别。在不同的CPU架構(gòu)下,布局方式會(huì)有所不同恐锣,但參數(shù)都是一樣的茅主。

isa_t定義

類結(jié)構(gòu)體

objc_class結(jié)構(gòu)體

Runtime中類也是一個(gè)對(duì)象,類的結(jié)構(gòu)體objc_class是繼承自objc_object的土榴,具備對(duì)象所有的特征诀姚。在objc_class中定義了三個(gè)成員變量,superclass是一個(gè)objc_class類型的指針玷禽,指向其父類的objc_class結(jié)構(gòu)體赫段。cache用來(lái)處理已調(diào)用方法的緩存。

bitsobjc_class的主角矢赁,其內(nèi)部只定義了一個(gè)uintptr_t類型的bits成員變量糯笙,存儲(chǔ)了class_rw_t的地址。bits中還定義了一些基本操作撩银,例如獲取class_rw_t给涕、raw isa狀態(tài)、是否swift等函數(shù)额获。objc_class結(jié)構(gòu)體中定義的一些函數(shù)够庙,其內(nèi)部都是通過(guò)bits實(shí)現(xiàn)的。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;    

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    // .....
}

objc_class的源碼可以看出抄邀,可以通過(guò)bits結(jié)構(gòu)體的data()函數(shù)耘眨,獲取class_rw_t指針。我們進(jìn)入源代碼中看一下境肾,可以看出是通過(guò)對(duì)uintptr_t類型的bits變量剔难,做位運(yùn)算查找對(duì)應(yīng)的值。

class_rw_t* data() {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

uintptr_t本質(zhì)上是一個(gè)unsigned longtypedef准夷,unsigned long64位處理器中占8字節(jié)钥飞,正好是64位二進(jìn)制。通過(guò)FAST_DATA_MASK轉(zhuǎn)換為二進(jìn)制后衫嵌,是取bits中的47-3的位置读宙,正好是取出class_rw_t指針。

OC中一個(gè)指針的長(zhǎng)度是47楔绞,例如打印一個(gè)UIViewController的地址是0x7faf1b580450结闸,轉(zhuǎn)換為二進(jìn)制是11111111010111100011011010110000000010001010000唇兑,最后面三位是占位的,所以在取地址的時(shí)候會(huì)忽略最后三位桦锄。

// 查找第0位扎附,表示是否swift
#define FAST_IS_SWIFT           (1UL<<0)
// 當(dāng)前類或父類是否定義了retain、release等方法
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// 類或父類需要初始化isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// 數(shù)據(jù)段的指針
#define FAST_DATA_MASK          0x00007ffffffffff8UL
// 11111111111111111111111111111111111111111111000 總共47位

因?yàn)樵?code>bits中最后三位是沒用的结耀,所以可以用來(lái)存儲(chǔ)一些其他信息留夜。在class_data_bits_t還定義了三個(gè)宏,用來(lái)對(duì)后三位做位運(yùn)算图甜。

class_ro_t和class_rw_t

class_data_bits_t相關(guān)的有兩個(gè)很重要結(jié)構(gòu)體碍粥,class_rw_tclass_ro_t,其中都定義著method list黑毅、protocol list嚼摩、property list等關(guān)鍵信息。

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

在編譯后class_data_bits_t指向的是一個(gè)class_ro_t的地址矿瘦,這個(gè)結(jié)構(gòu)體是不可變的(只讀)枕面。在運(yùn)行時(shí),才會(huì)通過(guò)realizeClass函數(shù)將bits指向class_rw_t缚去。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

在程序開始運(yùn)行后會(huì)初始化Class潮秘,在這個(gè)過(guò)程中,會(huì)把編譯器存儲(chǔ)在bits中的class_ro_t取出病游,然后創(chuàng)建class_rw_t唇跨,并把ro賦值給rw,成為rw的一個(gè)成員變量衬衬,最后把rw設(shè)置給bits买猖,替代之前bits中存儲(chǔ)的ro。除了這些操作外滋尉,還會(huì)有一些其他賦值的操作玉控,下面是初始化Class的精簡(jiǎn)版代碼。

static Class realizeClass(Class cls) 
{
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;

    ro = (const class_ro_t *)cls->data();
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);

    isMeta = ro->flags & RO_META;
    rw->version = isMeta ? 7 : 0;

    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()))

    cls->superclass = supercls;
    cls->initClassIsa(metacls);
    cls->setInstanceSize(ro->instanceSize);

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls);
    return cls;
}

在上面的代碼中我們還發(fā)現(xiàn)了兩個(gè)函數(shù)狮惜,addRootClassaddSubclass函數(shù)高诺,這兩個(gè)函數(shù)的職責(zé)是將某個(gè)類的子類串成一個(gè)列表,大致是下面的鏈接順序碾篡。由此可知虱而,我們是可以通過(guò)class_rw_t,獲取到當(dāng)前類的所有子類开泽。

superClass.firstSubclass -> subClass1.nextSiblingClass -> subClass2.nextSiblingClass -> ...

初始化rwro之后牡拇,rwmethod listprotocol listproperty list都是空的惠呼,需要在下面methodizeClass函數(shù)中進(jìn)行賦值导俘。函數(shù)中會(huì)把rolist都取出來(lái),然后賦值給rw剔蹋,如果在運(yùn)行時(shí)動(dòng)態(tài)修改旅薄,也是對(duì)rw做的操作。所以ro中存儲(chǔ)的是編譯時(shí)就已經(jīng)決定的原數(shù)據(jù)泣崩,rw才是運(yùn)行時(shí)動(dòng)態(tài)修改的數(shù)據(jù)少梁。

static void methodizeClass(Class cls)
{
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
}

假設(shè)創(chuàng)建一個(gè)類LXZObject,繼承自NSObject律想,并為其加入一個(gè)testMethod方法猎莲,不做其他操作。因?yàn)樵诰幾g后objc_classbits對(duì)應(yīng)的是class_ro_t結(jié)構(gòu)體技即,所以我們打印一下結(jié)構(gòu)體的成員變量,看一下編譯后的class_ro_t是什么樣的樟遣。

struct class_ro_t {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f7a "LXZObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
}

經(jīng)過(guò)打印可以看出而叼,一個(gè)類的class_ro_t中只會(huì)包含當(dāng)前類的信息,不會(huì)包含其父類的信息豹悬,在LXZObject類中只會(huì)包含namebaseMethodList兩個(gè)字段葵陵,而baseMethodList中只有一個(gè)testMethod方法。由此可知瞻佛,class_rw_t結(jié)構(gòu)體也是一樣的脱篙。

類結(jié)構(gòu)體

初始化過(guò)程

下面是已經(jīng)初始化后的isa_t結(jié)構(gòu)體的布局,以及各個(gè)結(jié)構(gòu)體成員在結(jié)構(gòu)體中的位置伤柄。

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

union經(jīng)常配合結(jié)構(gòu)體使用绊困,第一次使用union就是對(duì)結(jié)構(gòu)體區(qū)域做初始化。在對(duì)象初始化時(shí)适刀,會(huì)對(duì)isa_tbits字段賦值為ISA_MAGIC_VALUE秤朗,這就是對(duì)union聯(lián)合體初始化的過(guò)程。

// 在objc-723中已經(jīng)沒有了
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

在對(duì)象通過(guò)initIsa()函數(shù)初始化時(shí)笔喉,會(huì)通過(guò)ISA_MAGIC_VALUE對(duì)isa進(jìn)行初始化取视。ISA_MAGIC_VALUE是一個(gè)16進(jìn)制的值,將其轉(zhuǎn)換為二進(jìn)制后常挚,會(huì)發(fā)現(xiàn)ISA_MAGIC_VALUE是對(duì)nonpointermagic做初始化作谭。

nonpointer是對(duì)之前32位處理器的兼容。在訪問(wèn)對(duì)象所屬的類時(shí)奄毡,如果是32位則返回之前的isa指針地址折欠,否則表示是64位處理器,則返回isa_t結(jié)構(gòu)體。

# define ISA_MAGIC_VALUE 0x000001a000000001ULL
二進(jìn)制:11010000000000000000000000000000000000001
補(bǔ)全二進(jìn)制:23個(gè)零+11010000000000000000000000000000000000001

隨后會(huì)通過(guò)位域怨酝,對(duì)has_cxx_dtorshiftcls做初始化傀缩,這時(shí)候就已經(jīng)有四個(gè)字段被初始化了。has_cxx_dtor表示是否有C++OC的析構(gòu)方法农猬,在打印方法列表時(shí)赡艰,經(jīng)常能看到一個(gè)名為.cxx_destruct的方法,就和這個(gè)字段有關(guān)系斤葱。

在計(jì)算機(jī)中為了對(duì)存儲(chǔ)區(qū)(Memory or Disk)讀取方便慷垮,所以在寫入和讀取時(shí),會(huì)對(duì)內(nèi)存有對(duì)其操作揍堕。一般是以字節(jié)為單位進(jìn)行對(duì)其料身,這樣也是對(duì)讀寫速度的優(yōu)化。在對(duì)shiftcls進(jìn)行賦值時(shí)衩茸,對(duì)Class的指針進(jìn)行了位移操作芹血,向右位移三位。這是因?yàn)轭愔羔槥榱藘?nèi)存對(duì)其楞慈,將最后三位用0填充幔烛,所以這三位是沒有意義的。

isa結(jié)構(gòu)體
0000000001011101100000000000000100000000001110101110000011111001
0x5d8001003ae0f8

類對(duì)象地址
100000000001110101110000011111000
0x1003ae0f8

將類對(duì)象地址右移三位為100000000001110101110000011111囊蓝,正好符合isa_t地址中shiftcls的部分饿悬,前面不足補(bǔ)零。

外界獲取Class時(shí)聚霜,應(yīng)該通過(guò)ISA()函數(shù)狡恬,而不是像之前一樣直接訪問(wèn)isa指針。在ISA()函數(shù)中蝎宇,是對(duì)isa_t的結(jié)構(gòu)體做與運(yùn)算弟劲,是通過(guò)ISA_MASK宏進(jìn)行的,轉(zhuǎn)換為二進(jìn)制的話夫啊,正好是把shiftcls的地址取出來(lái)函卒。

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

#define ISA_MASK 0x0000000ffffffff8ULL
111111111111111111111111111111111000

Tagged Pointer

iPhone5s開始,iOS設(shè)備開始引入了64位處理器撇眯,之前的處理器一直都是32位的报嵌。

但是在64位處理器中,指針長(zhǎng)度以及一些變量所占內(nèi)存都發(fā)生了改變熊榛,32位一個(gè)指針占用4字節(jié)锚国,但64位一個(gè)指針占用8字節(jié);32位一個(gè)long占用4字節(jié)玄坦,64位一個(gè)long占用8字節(jié)等血筑,所以在64位上內(nèi)存占用會(huì)多出很多绘沉。

蘋果為了優(yōu)化這個(gè)問(wèn)題,推出了Tagged Pointer新特性豺总。之前一個(gè)指針指向一個(gè)地址车伞,而Tagged Pointer中一個(gè)指針就代表一個(gè)值,以NSNumber為例喻喳。

NSNumber *number1 = @1;
NSNumber *number2 = @3;
NSNumber *number3 = @54;

// 輸出
(lldb) p number1
(__NSCFNumber *) $3 = 0xb000000000000012 (int)1
(lldb) p number2
(__NSCFNumber *) $4 = 0xb000000000000032 (int)3
(lldb) p number3
(__NSCFNumber *) $5 = 0xb000000000000362 (int)54

通過(guò)上面代碼可以看出另玖,使用了Tagged Pointer新特性后,指針中就存儲(chǔ)著對(duì)象的值表伦。例如一個(gè)值為1NSNumber谦去,指針就是0xb000000000000012,如果拋去前面的0xb和后面的2蹦哼,中間正好就是16進(jìn)制的值鳄哭。

蘋果通過(guò)Tagged Pointer的特性,明顯的提升了執(zhí)行效率并節(jié)省了很多內(nèi)存纲熏。在64位處理器下妆丘,內(nèi)存占用減少了將近一半,執(zhí)行效率也大大提升局劲。由于通過(guò)指針來(lái)直接表示數(shù)值飘痛,所以沒有了mallocfree的過(guò)程,對(duì)象的創(chuàng)建和銷毀速度提升幾十倍容握。

isa_t

對(duì)于對(duì)象指針也是一樣,在OC1.0時(shí)代isa是一個(gè)真的指針车柠,指向一個(gè)堆區(qū)的地址剔氏。而OC2.0時(shí)代,一個(gè)指針長(zhǎng)度是八字節(jié)也就是64位竹祷,在64位中直接存儲(chǔ)著對(duì)象的信息谈跛。當(dāng)查找對(duì)象所屬的類時(shí),直接在isa指針中進(jìn)行位運(yùn)算即可塑陵,而且由于是在棧區(qū)進(jìn)行操作感憾,查找速度是非常快的令花。

struct {
    uintptr_t nonpointer        : 1;
    uintptr_t has_assoc         : 1;
    uintptr_t has_cxx_dtor      : 1;
    uintptr_t shiftcls          : 33;
    uintptr_t magic             : 6;
    uintptr_t weakly_referenced : 1;
    uintptr_t deallocating      : 1;
    uintptr_t has_sidetable_rc  : 1;
    uintptr_t extra_rc          : 19;
};

例如isa_t本質(zhì)上是一個(gè)結(jié)構(gòu)體阻桅,如果創(chuàng)建結(jié)構(gòu)體再用指針指向這個(gè)結(jié)構(gòu)體,內(nèi)存占用是很大的兼都。但是Tagged Pointer特性中嫂沉,直接把結(jié)構(gòu)體的值都存儲(chǔ)到指針中,這就相當(dāng)節(jié)省內(nèi)存了扮碧。

蘋果不允許直接訪問(wèn)isa指針趟章,和Tagged Pointer也是有關(guān)系的杏糙。因?yàn)樵?code>Tagged Pointer的情況下,isa并不是一個(gè)指針指向另一塊內(nèi)存區(qū)蚓土,而是直接表示對(duì)象的值宏侍,所以通過(guò)直接訪問(wèn)isa獲取到的信息是錯(cuò)誤的。

Tagged Pointer


簡(jiǎn)書由于排版的問(wèn)題蜀漆,閱讀體驗(yàn)并不好谅河,布局、圖片顯示嗜愈、代碼等很多問(wèn)題旧蛾。所以建議到我Github上,下載Runtime PDF合集蠕嫁。把所有Runtime文章總計(jì)九篇锨天,都寫在這個(gè)PDF中,而且左側(cè)有目錄剃毒,方便閱讀病袄。

Runtime PDF

下載地址:Runtime PDF
麻煩各位大佬點(diǎn)個(gè)贊,謝謝赘阀!??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末益缠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子基公,更是在濱河造成了極大的恐慌幅慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轰豆,死亡現(xiàn)場(chǎng)離奇詭異胰伍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)酸休,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門骂租,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人斑司,你說(shuō)我怎么就攤上這事渗饮。” “怎么了宿刮?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵互站,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我糙置,道長(zhǎng)云茸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任谤饭,我火速辦了婚禮标捺,結(jié)果婚禮上懊纳,老公的妹妹穿的比我還像新娘。我一直安慰自己亡容,他們只是感情好嗤疯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闺兢,像睡著了一般茂缚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屋谭,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天脚囊,我揣著相機(jī)與錄音,去河邊找鬼桐磁。 笑死悔耘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的我擂。 我是一名探鬼主播衬以,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼校摩!你這毒婦竟也來(lái)了看峻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衙吩,失蹤者是張志新(化名)和其女友劉穎互妓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坤塞,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡车猬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尺锚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惜浅,死狀恐怖瘫辩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坛悉,我是刑警寧澤伐厌,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站裸影,受9級(jí)特大地震影響挣轨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轩猩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一卷扮、第九天 我趴在偏房一處隱蔽的房頂上張望荡澎。 院中可真熱鬧,春花似錦晤锹、人聲如沸摩幔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)或衡。三九已至,卻和暖如春车遂,著一層夾襖步出監(jiān)牢的瞬間封断,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工舶担, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坡疼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓柄沮,卻偏偏與公主長(zhǎng)得像回梧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祖搓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345