runtime源碼中的類和對象

本文基于objc4-709源碼進行分析。關(guān)于源碼編譯:objc - 編譯Runtime源碼objc4-706

objc中的類和對象

1.類和對象的結(jié)構(gòu)概要

NSObject是所有類的基類,NSObject在源碼中的定義:

在NSObject.mm文件中找到

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject類的第一個成員變量就是Class類型的isa。

Object.mm文件:

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

@interface Object { 
    Class isa; 
} 

Class就是c語言定義的objc_class結(jié)構(gòu)體類型的指針,objc中的類實際上就是objc_class。

而id類型就是objc_object結(jié)構(gòu)體類型的指針(就是我們平時經(jīng)常用到的id類型),我們平時用的id可以用來聲明一個對象,說明objc中的對象實際上就是objc_object。

objc-runtime-new.h

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;           //父類的指針
    cache_t cache;             // formerly cache pointer and vtable 方法緩存
    class_data_bits_t bits;    // class
    ...
}

objc_class繼承于objc_object,objc中的類也是一個對象。

objc-private.h

struct objc_object {
private:
    isa_t isa;//objc_object唯一成員變量
public:
    Class ISA();
    Class getIsa();
    void initIsa(Class cls /*nonpointer=false*/);
    
    ...
private:
    void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
}
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    
    ...
}

objc_object是objc中對象的定義。isa 是 objc_object的唯一成員變量。我們經(jīng)常說,所有的objc對象都包含一個isa指針,從源碼上來看,現(xiàn)在準確說應(yīng)該是isa_t結(jié)構(gòu)體(isa指針應(yīng)該是isa_t聯(lián)合體中的Class cls指針)。當objc為一個對象分配內(nèi)存,初始化實例變量后,在這些對象的實例變量的結(jié)構(gòu)體中的第一個就是isa。

為了方便閱讀,我把objc_class寫成:

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;           //父類的指針
    cache_t cache;             // formerly cache pointer and vtable 方法緩存
    class_data_bits_t bits;    // class
    ...
}

在objc中,對象的方法的實現(xiàn)不會存儲在每個對象的結(jié)構(gòu)體中,而是在相應(yīng)的類里(如果每一個對象都要維護一個實例方法列表,那么開銷太大了)。當一個實例方法被調(diào)用時,會通過對象的isa,在對應(yīng)的類中找到方法的實現(xiàn)(具體是在class_data_bits_t結(jié)構(gòu)體中查找,里面有一個方法列表)。

同時我們還從源碼中看到,objc_class結(jié)構(gòu)體中還有一個Class類型的superclass成員變量,指向了父類。通過這個指針可以查找從父類繼承的方法。

然而,對于類對象來說,它的isa又是什么呢?objective-c里面有一種叫做meta class元類的東西。


對象、類、元類關(guān)系圖

為了讓我們能夠調(diào)用類方法,類的isa“指針”必須指向一個類結(jié)構(gòu),并且該類結(jié)構(gòu)必須包含我們可以調(diào)用的類方法列表。這就導(dǎo)致了元類的定義:元類是類對象的類。

類方法調(diào)用時,通過類的isa“指針”在元類中獲取方法的實現(xiàn)。元類中存儲了一個類的所有類方法。

從上圖中總結(jié)以下幾個信息:

  • root class(class)就是NSObject,由于它是基類,所以它沒有父類。
  • 實例對象的isa指向其類,類的isa指向其元類。每個元類的isa都指向根元類root class(meta),根元類的isa指向自己。
  • 根元類的父類指針指向基類(NSObject)。

What is a meta-class in Objective-C?

ps:isa指針并不總是指向?qū)嵗龑ο笏鶎俚念悾荒芤揽克鼇泶_定類型,而應(yīng)用class方法。(KVO的實現(xiàn)中,將被觀察的對象的isa指針指向一個中間類而非真實的類)。

2.isa_t結(jié)構(gòu)體的分析

通過源碼,我們可以知道isa_t實際上是一個union聯(lián)合體。其中的方法、成員變量、結(jié)構(gòu)體公用一塊空間。取決于其中的結(jié)構(gòu)體,最終isa_t共占64位內(nèi)存空間

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

    Class cls;//指針
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

//-------------------arm64上的實現(xiàn)
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL//ISA_MAGIC_MASK 和 ISA_MASK 分別是通過掩碼的方式獲取MAGIC值 和 isa類指針
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;    //0:普通isa指針,1:優(yōu)化的指針用于存儲引用計數(shù)
        uintptr_t has_assoc         : 1;    //表示該對象是否包含associated object,如果沒有,析構(gòu)時會更快
        uintptr_t has_cxx_dtor      : 1;    //表示對象是否含有c++或者ARC的析構(gòu)函數(shù),如果沒有,析構(gòu)更快
        uintptr_t shiftcls          : 33;   // MACH_VM_MAX_ADDRESS 0x1000000000 類的指針
        uintptr_t magic             : 6;    //用于在調(diào)試時分辨對象是否未完成初始化(用于調(diào)試器判斷當前對象是真的對象還是沒有初始化的空間)
        uintptr_t weakly_referenced : 1;    //對象是否有過weak對象
        uintptr_t deallocating      : 1;    //是否正在析構(gòu)
        uintptr_t has_sidetable_rc  : 1;    //該對象的引用計數(shù)值是否過大無法存儲在isa指針
        uintptr_t extra_rc          : 19;   //存儲引用計數(shù)值減一后的結(jié)果。對象的引用計數(shù)超過 1,會存在這個這個里面,如果引用計數(shù)為 10,extra_rc的值就為 9。
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

//-------------------__x86_64__上的實現(xiàn)
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        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;  //值為0x3b
        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)
    };

# else
#   error unknown architecture for packed isa
# endif

#endif

...略

由于源碼要在mac os下才能編譯,因此接下來都基于x86_64分析,arm64其實大同小異。

源碼中遇到的編譯宏的說明:

SUPPORT_PACKED_ISA:表示平臺是否支持在 isa 指針中插入除 Class 之外的信息。如果支持就會將 Class 信息放入 isa_t 定義的 struct 內(nèi)(shiftcls),并附上一些其他信息,比如引用計數(shù),析構(gòu)狀態(tài);如果不支持,那么不會使用 isa_t 內(nèi)定義的 struct,這時 isa_t 只使用 cls成員變量(Class 指針,經(jīng)常說的“isa指針”就是這個)。在 iOS 以及 MacOSX 上,SUPPORT_PACKED_ISA 定義為 1(支持)。

struct結(jié)構(gòu)體的成員含義:

參數(shù)                     含義
nonpointer      0 表示普通的 isa 指針,1 表示使用優(yōu)化,存儲引用計數(shù)
has_assoc       表示該對象是否包含 associated object 關(guān)聯(lián)對象,如果沒有,則析構(gòu)時會更快
has_cxx_dtor        表示該對象是否有 C++ 或 ARC 的析構(gòu)函數(shù),如果沒有,則析構(gòu)時更快
shiftcls        類的指針
magic           __x86_64__環(huán)境初始化值為 0x3b,用于在調(diào)試時分辨對象是否未完成初始化。
weakly_referenced   表示該對象是否有過 weak 對象,如果沒有,則析構(gòu)時更快
deallocating        表示該對象是否正在析構(gòu)
has_sidetable_rc    表示該對象的引用計數(shù)值是否過大無法存儲在 isa 指針
extra_rc        存儲引用計數(shù)值減一后的結(jié)果。19位將保存對象的引用計數(shù),這樣對引用計數(shù)的操作只需要原子的修改這個指針即可,如果引用計數(shù)超出19位,才會將引用計數(shù)保存到外部表,而這種情況往往是很少的,因此效率將會大大提高。

在初始化一節(jié)會對其中一些參數(shù)繼續(xù)談?wù)劇?/p>

初始化

在我們調(diào)用alloc來實例化對象時,會調(diào)用到objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 方法。方法的具體實現(xiàn):

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{     
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
//__x86_64__和arm64會進入這個條件分支
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

initInstanceIsa方法實際上調(diào)用了initIsa方法,并且傳入nonpointer=true參數(shù)。

由于isa_t是一個聯(lián)合體,所以newisa.bits = ISA_MAGIC_VALUE;把isa_t中的struct結(jié)構(gòu)體初始化為:

objc-isa-isat-bits.png

實際上只設(shè)置了nonpointer和magic的值。

nonpointer

nonpointer 表示是否對isa開啟指針優(yōu)化。

先介紹一種稱為Tagged Pointer的技術(shù),這是蘋果為64位設(shè)備提出的節(jié)省內(nèi)存和提高執(zhí)行效率的一種優(yōu)化方案。

假設(shè)我們要存儲一個NSNumber對象,其值是一個整數(shù)。正常情況下,如果這個整數(shù)只是一個NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān),在32位CPU下占4個字節(jié),在64位CPU下是占8個字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān),一個指針所占用的內(nèi)存在32位CPU下為4個字節(jié),在64位CPU下也是8個字節(jié)。

如果沒有Tagged Pointer對象,從32位機器遷移到64位機器中后,雖然邏輯沒有任何變化,但這種NSNumber、NSDate一類的對象所占用的內(nèi)存會翻倍。

為了改進上面提到的內(nèi)存占用和效率問題,蘋果提出了Tagged Pointer對象。由于NSNumber、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個字節(jié),拿整數(shù)來說,4個字節(jié)所能表示的有符號整數(shù)就可以達到20多億。所以我們可以將一個對象的指針拆成兩部分,一部分直接保存數(shù)據(jù),另一部分作為特殊標記,表示這是一個特別的指針,不指向任何一個地址。

技術(shù)詳細可以查看以下兩個鏈接。

深入理解Tagged Pointer

64位與Tagged Pointer

雖然Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate。但在isa指針優(yōu)化上用到了tagged Pointer的概念。

使用整個指針大小的內(nèi)存來存儲isa有些浪費,在arm64上運行的iOS只用了33位(和結(jié)構(gòu)體中shiftcls的33無關(guān),Mac OS用了47位),剩下的31位用于其他目的。

nonpointer = 0 ,raw isa,表示isa_t不使用struct結(jié)構(gòu)體,訪問對象的 isa 會直接返回 cls 指針,cls 會指向?qū)ο笏鶎俚念惖慕Y(jié)構(gòu)。這是在 iPhone 遷移到 64 位系統(tǒng)之前時 isa 的類型。

nonpointer = 1,isa_t使用了struct結(jié)構(gòu)體,此時 isa 不再是指針,不能直接訪問 objc_object 的 isa 成員變量,但是其中也有 cls 的信息,只是其中關(guān)于類的指針都是保存在 shiftcls 中。

bool hasNonpointerIsa();這個函數(shù)就是用來判斷當前對象的isa是否啟用tagged pointer的。

magic

用于判斷當前對象是否已經(jīng)完成初始化。

has_cxx_dtor

接著設(shè)置has_cxx_dtor,這一位表示當前對象是否有 C++ 或者 ObjC 的析構(gòu)器,如果沒有析構(gòu)器就會快速釋放內(nèi)存。

shiftcls

newisa.shiftcls = (uintptr_t)cls >> 3; 將對象對應(yīng)的類的指針存入結(jié)構(gòu)體的shiftcls成員中。

將cls(地址)右移三位的原因:字節(jié)對齊。類的指針按照8字節(jié)對齊。

為什么需要字節(jié)對齊

C語言字節(jié)對齊問題詳解

對象的內(nèi)存地址必須對齊到字節(jié)的倍數(shù),這樣可以提高代碼運行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對齊的最后三位比特為 000,我們只會用其中的 30 位來表示對象的地址。

將cls右移三位,可以將地址中無用的后三位清除減小內(nèi)存的消耗。

回過頭來看,類的isa的shiftcls就指向元類了。

ISA()

由于開啟了指針優(yōu)化后,isa不再是指針,要獲取類指針就要用到 ISA() 方法。

#define ISA_MASK 0x00007ffffffffff8ULL

inline Class 
objc_object::ISA() 
{
#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); //按位與運算獲取類指針,__x86_64__是44位
#endif
}

3.cache_t 結(jié)構(gòu)體的分析

typedef unsigned int uint32_t;
typedef uint32_t mask_t; 
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    ......
};

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
    ......
};

cache 存放著實例方法的緩存,提高方法調(diào)用效率。當一個對象調(diào)用一個方法時,它會先從緩存里面找這個方法,如果沒有找到才會去類的方法列表中去找。

如果一個對象調(diào)用一個方法,首先根據(jù)對象的isa找到對應(yīng)的類,再在類的方法列表中尋找這個方法,如果找不到就到父類中的方法列表查找,一旦找到就調(diào)用。如果沒有找到,有可能轉(zhuǎn)發(fā)消息,也可能忽略它。但這樣效率太低了,有些方法會經(jīng)常用到,那么每次調(diào)用都要走一遍以上流程,是不是很慢?用cache來緩存方法,優(yōu)先在 cache 中查找,找到就調(diào)用沒找到再走正常路子。
cache_t中存儲了一個bucket_t結(jié)構(gòu)體指針和兩個無符號整形變量。

bucket_t結(jié)構(gòu)體中存儲了IMP函數(shù)指針和unsigned long。_buckets 用來存儲Method的鏈表。

_mask 分配用來緩存bucket的總數(shù)。

_occupied 當前占用的bucket數(shù)。

4.class_data_bits_t

之前說過,實例方法被調(diào)用時,會通過其持有 isa 指針尋找對應(yīng)的類,然后在其中的 class_data_bits_t 中查找對應(yīng)的方法。相對來說 class_data_bits_t 會有點復(fù)雜,包含了 class_rw_t 、class_ro_t 等重要的結(jié)構(gòu)體。這一節(jié)會對 class_data_bits_t 進行分析,介紹方法是如何在objc中存取的。

struct class_data_bits_t {

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

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() { 
        return bits.data();
    }
    ......
};

class_data_bits_t 結(jié)構(gòu)體只含有一個64bit的bits變量,它存儲了類相關(guān)的信息,和不同的以'FAST_'開頭的flag掩碼做按位與運算。

uintptr_t bits存儲了一個指向 class_rw_t 結(jié)構(gòu)體的指針和三個標志位。bits在64位兼容版中的內(nèi)容:

// 當前類是swift類
#define FAST_IS_SWIFT           (1UL<<0)
//當前類或者父類含有默認的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// 當前類的實例需要 raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// 數(shù)據(jù)指針
#define FAST_DATA_MASK          0x00007ffffffffff8UL
class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

在 objc_class 的 data 方法中,直接調(diào)用并返回了 class_data_bits_t 的data方法。bits 和 FAST_DATA_MASK做按位與運算,轉(zhuǎn)換為 class_rw_t *并返回。

Mac OS X 只使用 47 位內(nèi)存地址,所以bits前 17 位空余出來。bits中最大的一塊存儲區(qū)域用于存儲class_rw_t 指針,在 FAST_DATA_MASK 對應(yīng)的[3,46]位數(shù)據(jù)段中。[3,46]一共是44位,有沒有覺得44很眼熟?由于字節(jié)對齊,所以47位的后三位為0,可以用來做標志位,所以就有了另外三個標志位。

除了 FAST_DATA_MASK 是用一段空間做存儲外,其他宏都是用1bit。這些數(shù)據(jù)都有對應(yīng)的getter、setter封裝函數(shù)。比如:

bool isSwift() {return getBit(FAST_IS_SWIFT);}

void setIsSwift() {setBits(FAST_IS_SWIFT);}

bool hasDefaultRR() {return getBit(FAST_HAS_DEFAULT_RR);}

void setHasDefaultRR() {setBits(FAST_HAS_DEFAULT_RR);}

在64非兼容版本下還有更多的宏定義,位于objc-runtime-new.h文件中,如有需要請自行閱讀源碼。

class_rw_t

從上面的分析中我們知道,class_data_bits_t 用了大段空間存儲了 class_rw_t 指針。class_rw_t 提供了運行時對類擴展的能力,class_rw_t 結(jié)構(gòu)體中保存了 objc 中的屬性、方法、協(xié)議等信息。

在 objc_class結(jié)構(gòu)體中的注釋寫到 class_data_bits_t相當于 class_rw_t指針加上 rr/alloc 的標志。

class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods; //方法
    property_array_t properties;//屬性
    protocol_array_t protocols;//協(xié)議

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;//是計算機語言用于解決實體名稱唯一性的一種方法,做法是向名稱中添加一些類型信息,用于從編譯器中向連接器傳遞更多語義信息。
    
    ......
};

關(guān)于32位 flags 標志,在源碼中有這樣一段注釋:

Values for class_rw_t->flags
These are not emitted by the compiler and are never used in class_ro_t.
Their presence should be considered in future ABI versions.

它的值不是由編譯器設(shè)置的,并且從不在class_ro_t中使用。未來的ABI版本應(yīng)考慮到他們的存在。

flags 標記了類的一些狀態(tài),與生命周期、內(nèi)存管理有關(guān),有些位目前還沒有定義。


class_rw_t 中的 methods、properties、protocols 存放了和類的方法、屬性、協(xié)議有關(guān)的信息。

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

class method_array_t : public list_array_tt<method_t, method_list_t> 
class property_array_t : public list_array_tt<property_t, property_list_t> 
class protocol_array_t : public list_array_tt<protocol_ref_t, protocol_list_t> 

method_array_t、property_array_t、protocol_array_t這三種類都繼承自 list_array_tt<Element, List>這種結(jié)構(gòu)。

template <typename Element, typename List>
class list_array_tt {
    struct array_t {
        uint32_t count;
        List* lists[0];//長度為0的數(shù)組,c99的寫法,允許在運行期動態(tài)申請內(nèi)存
    };
}

list_array_tt 存儲一些元數(shù)據(jù),是通過c++模板定義的容器類,提供了一些諸如count、迭代器iterator的方法和類。Element表示元數(shù)據(jù)的類型,比如 method_t 、 property_t 、 protocol_ref_t;List表示存儲元數(shù)據(jù)的容器,理解成是用于存儲元數(shù)據(jù)的一維數(shù)組,比如 method_list_t 、 property_list_t 。由于list_array_tt 存儲了List指針數(shù)組,所以list_array_tt實際上可以看做是元數(shù)據(jù)的二維數(shù)組。list_array_tt 有三種狀態(tài):

  • 自身為空
  • List指針數(shù)組只有一個指向元數(shù)據(jù)數(shù)組的指針
  • List指針數(shù)組有多個指針

一個類創(chuàng)建之初可能處于前兩個狀態(tài),如果用category或者class_addMethod來添加方法,就變成第三個狀態(tài),而且是不可逆的回不去前兩個狀態(tài)

可以對list_array_tt不斷進行擴張。比如在通過category添加方法時,就調(diào)用到這個方法,把新的方法列表(相當于是裝有一個category所有方法的容器)添加到二維數(shù)組中:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

     //調(diào)用realloc將原空間擴展,把原數(shù)組復(fù)制到后面,新數(shù)組復(fù)制到前面
    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    
    //List指針數(shù)組只有一個指針時
    //malloc重新申請內(nèi)存,最后一個位置留給原來元數(shù)據(jù)數(shù)組的指針
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

無論是哪一種邏輯,新加的方法列表都會添加到二維數(shù)組前面。

有一個名字很類似的結(jié)構(gòu)體 class_ro_t,'rw' 和 ro' 相信很容易就理解是 'readwrite' 和 'readonly'的意思吧。

class_rw_t 的結(jié)構(gòu)體成員中還有一個指向常量的指針class_ro_t *ro。因此在編譯期間類的結(jié)構(gòu)中的 class_data_bits_t *data 指向的是一個 class_ro_t * 指針。class_ro_t也是一個結(jié)構(gòu)體,下一節(jié)我們來講講它。

class_ro_t

class_rw_t 的內(nèi)容可以在運行時動態(tài)修改,而 class_ro_t 存儲了在類編譯時就確定的信息,比如屬性、方法、協(xié)議、成員變量。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//類名
    method_list_t * baseMethodList;//方法列表
    protocol_list_t * baseProtocols;//協(xié)議列表
    const ivar_list_t * ivars;//ivar列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//屬性列表

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

在類進行初始化之前,通過object_class 的data()方法得到的實際是一個指向 class_ro_t 結(jié)構(gòu)體的指針。

instanceStart、instanceSize 兩個成員變量的存在保證了objc2.0的ABI穩(wěn)定性。Non Fragile ivars

method_list_t、ivar_list_t、property_list_t 結(jié)構(gòu)體都繼承自 entsize_list_tt<Element, List, FlagMask> , protocol_list_t 的結(jié)構(gòu)則相對簡單很多。entsize_list_tt 是通過c++模板定義的容器類,提供了一些諸如count、get、迭代器iterator的方法和類,通過這些方法和類可以方便地遍歷并獲取容器內(nèi)的數(shù)據(jù)。

template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;//總大小
    uint32_t count;//個數(shù)
    Element first;//第一個元數(shù)據(jù)
};

而在類進行初始化之時,會調(diào)用到一個 static Class realizeClass(Class cls) 方法(這個之后分析oc中的消息發(fā)送時會具體分析,這里只要知道會調(diào)用到這個方法即可)。

realizeClass的實現(xiàn)節(jié)選:

ro = (const class_ro_t *)cls->data();//強制轉(zhuǎn)換
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);//初始化一個 class_rw_t 結(jié)構(gòu)體,分配可讀寫數(shù)據(jù)空間
    rw->ro = ro;//設(shè)置結(jié)構(gòu)體 ro 的值以及 flag
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);//最后設(shè)置正確的 data
}

這里主要做了幾個事:

  • 調(diào)用 object_class 的 data 方法,把得到的 class_rw_t 指針強制轉(zhuǎn)換成 class_ro_t 指針。
  • 新初始化一個 class_rw_t ,分配可讀寫數(shù)據(jù)空間。
  • 將 class_ro_t 指針賦值給 class_rw_t->ro ,并設(shè)置 class_rw_t->flag 。
  • 設(shè)置 object_class 的正確 data 。

也即由以下圖1變成了圖2:圖片來源深入解析 ObjC 中方法的結(jié)構(gòu)


但是此時 class_rw_t 中的方法,屬性以及協(xié)議列表均為空。最后在 realizeClass 方法中 調(diào)用了 methodizeClass 方法來將類 class_ro_t 的方法、屬性和遵循的協(xié)議加載到 methods、 properties 和 protocols 列表中(用到了在 class_rw_t 一節(jié)最后提到的 attachLists 方法),但是class_ro_t其中的 ivar 不會被拷貝(不能在運行時給已有的類增加實例變量)。methodizeClass 方法節(jié)選:

// Install methods and properties that the class implements itself.
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);
}

可以這樣說,經(jīng)過realizeClass函數(shù)處理的類才成為了真正的類。

5.驗證一下

一下這part的內(nèi)容基于Draveness大大《深入解析 ObjC 中方法的結(jié)構(gòu)》一文中驗證部分進行復(fù)現(xiàn),分析運行時初始化過程的內(nèi)存變化,作為自己敲一下代碼所做的記錄。

#import <Foundation/Foundation.h>
@interface XXObject : NSObject
@property (nonatomic, assign) CGFloat test;

- (void)hello;
@end


#import "XXObject.h"
@implementation XXObject
- (void)hello {
    NSLog(@"Hello");
}
@end

主程序代碼:
#import <Foundation/Foundation.h>
#import "XXObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class cls = [XXObject class];
        NSLog(@"%p", cls);
    }
    return 0;
}

由于類在內(nèi)存中的位置是編譯期就確定的,在之后修改代碼,也不會改變內(nèi)存中的位置。所以先跑一次代碼獲取XXObject 在內(nèi)存中的地址。地址為:0x1000011e0

在運行時初始化之前加入斷點:


然后通過lldb驗證一下:

(lldb) p (objc_class *)0x1000011e0
(objc_class *) $0 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x100001200 //偏移32為獲取class_data_bits_t指針
(class_data_bits_t *) $1 = 0x0000000100001200
(lldb) p $1->data()
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(class_rw_t *) $2 = 0x0000000100001158
(lldb) p (class_ro_t *)$2   //強制轉(zhuǎn)換類型
(class_ro_t *) $3 = 0x0000000100001158
(lldb) p *$3
(class_ro_t) $4 = {
  flags = 128
  instanceStart = 8
  instanceSize = 16
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f6e "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001118
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001140
}

可以看到name、baseMethodList、ivars、baseProperties是有值的,對于后兩者有值的原因:因為XXObject類中我添加了@property (nonatomic, assign) CGFloat test;屬性,而屬性也在編譯期自動生成了一個對應(yīng)的ivar(帶下劃線前綴的原屬性名的成員變量)。

//獲取ivars
(lldb) p $4.ivars
(const ivar_list_t *) $6 = 0x0000000100001118
(lldb) p *$6
//顯示count=1只有一個ivar
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 1
    first = {
      offset = 0x00000001000011b0
      name = 0x0000000100000f8b "_test"
      type = 0x0000000100000fb2 "d"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x00000001000011b0
  name = 0x0000000100000f8b "_test"
  type = 0x0000000100000fb2 "d"
  alignment_raw = 3
  size = 8
}

//獲取property
(lldb) p $4.baseProperties
(property_list_t *) $10 = 0x0000000100001140
(lldb) p *$10
(property_list_t) $11 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "test", attributes = "Td,N,V_test")
  }
}

查看 baseMethodList 中的內(nèi)容:

(lldb) p $4.baseMethodList
(method_list_t *) $14 = 0x00000001000010c8
(lldb) p *$14
(method_list_t) $15 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 3
    first = {
      name = "hello"
      types = 0x0000000100000f97 "v16@0:8"
      imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
    }
  }
}
(lldb) p $15.get(0)
(method_t) $16 = {
  name = "hello"
  types = 0x0000000100000f97 "v16@0:8"
  imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
}
(lldb) p $15.get(1)
(method_t) $17 = {
  name = "test"
  types = 0x0000000100000f9f "d16@0:8"
  imp = 0x0000000100000e50 (debug-objc`-[XXObject test] at XXObject.h:11)
}
(lldb) p $15.get(2)
(method_t) $18 = {
  name = "setTest:"
  types = 0x0000000100000fa7 "v24@0:8d16"
  imp = 0x0000000100000e70 (debug-objc`-[XXObject setTest:] at XXObject.h:11)
}

結(jié)果顯示 baseMethodList 中有三個方法,打印出來看,第一個是 XXObject 的 hello 方法,其余兩個是 test 屬性的對應(yīng)getter和setter方法。所以也就不難理解為什么objc中的property = ivar + getter + setter了。

關(guān)于"v16@0:8""d16@0:8""v24@0:8d16" 請見Type Encoding

以上這些都是在編譯器就生成了的。

接著來看一下如果沒有把class_rw_t強轉(zhuǎn)成class_ro_t時的情形:先打印出來留到后面與初始化之后進行對比。

(lldb) p *$2
(class_rw_t) $5 = {
  flags = 128
  version = 8
  ro = 0x0000000000000010
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100000f6e
        arrayAndFlag = 4294971246
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x00000001000010c8
        arrayAndFlag = 4294971592
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100001118
  demangledName = 0x0000000000000000 <no value available>
}

接著在 realizeClass 方法中的這個地方打一個斷點:(直接和地址值進行比較是因為類在編譯期就確定了在內(nèi)存中的地址)


判斷當前類是否XXObject,此時還沒有進行初始化,類結(jié)構(gòu)體中的布局依舊是上面那樣沒有變化的。

[圖片上傳失敗...(image-5a0ad-1517664314598)]
當這段代碼運行完后,再來lldb打印一下:

(lldb) p (objc_class *)0x1000011e0
(objc_class *) $19 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x0000000100001200
(class_data_bits_t *) $20 = 0x0000000100001200
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000100f05030
(lldb) p *$21
(class_rw_t) $22 = {
  flags = 2148007936
  version = 0
  ro = 0x0000000100001158
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = nil
  demangledName = 0x0000000000000000 <no value available>
}
(lldb) p $22.ro
(const class_ro_t *) $23 = 0x0000000100001158
(lldb) p *$23
(const class_ro_t) $24 = {
  flags = 128
  instanceStart = 8
  instanceSize = 16
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f6e "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001118
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001140
}

可以看到,$3class_ro_t * 已經(jīng)被設(shè)置成$22 class_rw_t中的 ro 成員了,但 class_rw_t 的 methods、properties、protocols 成員
仍然為空。

等到執(zhí)行完 methodizeClass 方法:


(lldb) p *$21
(class_rw_t) $25 = {
  flags = 2148007936
  version = 0
  ro = 0x0000000100001158
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000010c8
        arrayAndFlag = 4294971592
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100001140
        arrayAndFlag = 4294971712
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fffcd723f50
  demangledName = 0x0000000000000000 <no value available>
}

class_ro_t 中 baseMethodList 中的方法被添加到 class_rw_t 中的 methods 數(shù)組中,class_ro_t 中 baseProperties 被添加到 class_rw_t 中的 properties。核心都是通過在 methodizeClass 方法中執(zhí)行 attachLists 方法添加類的方法、屬性、協(xié)議:

auto rw = cls->data();
auto ro = rw->ro;

// Install methods and properties that the class implements itself.
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);
}

總結(jié)

  • 類的方法、屬性以及協(xié)議在編譯期間存放到了“錯誤”的位置,直到 realizeClass 執(zhí)行之后,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t,這樣我們即可以在運行時為 class_rw_t 添加方法,也不會影響類的只讀結(jié)構(gòu)。

  • 在 class_ro_t 中的屬性在運行期間就不能改變了,再添加方法時,會修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethodList

6.對象的初始化

創(chuàng)建一個 NSObject 對象的代碼:[[NSObject alloc] init];

alloc 方法的實現(xiàn):

+ (id)alloc {
    return _objc_rootAlloc(self);
}

僅僅調(diào)用了一個私有方法_objc_rootAlloc

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    // 檢查 cls 信息是否為 nil,如果為 nil,則無法創(chuàng)建新對象,返回 nil。
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    //是否有自定義的allocWithZone方法
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        //沒有alloc / allocWithZone實現(xiàn)。
        
        //是否可以快速分配內(nèi)存。這里好像是寫死了返回false
        if (fastpath(cls->canAllocFast())) {
            bool dtor = cls->hasCxxDtor();//是否有析構(gòu)器
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            id obj = class_createInstance(cls, 0);// 創(chuàng)建對象的關(guān)鍵函數(shù)
            if (slowpath(!obj)) return callBadAllocHandler(cls);// 分配失敗
            return obj;
        }
    }
#endif

    // callAlloc 傳入 allocWithZone = true
    if (allocWithZone) return [cls allocWithZone:nil];// 這里 cls 的 allocWithZone 方法里也是調(diào)用了 class_createInstance。
    return [cls alloc];
}

關(guān)于slowpathfastpath兩個宏,實際上是__builtin_expect(),一個GCC的一個內(nèi)建函數(shù),用于分支預(yù)測優(yōu)化。__builtin_expect — 分支預(yù)測優(yōu)化。只需要知道if(fastpath(x))if(x)是相同意思就可以了。

實際上我在執(zhí)行[[XXObject alloc] init]這句代碼時,if (fastpath(!cls->ISA()->hasCustomAWZ()))并沒有進入這個條件分支:

直接跳到這一步了

所以也即相當于:

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    return [cls allocWithZone:nil];
}

但很神奇的是,接下來并沒有執(zhí)行allocWithZone方法(打了斷點但無事發(fā)生),這是一個讓我覺得很迷的地方。最后落入的是這兩個方法:

id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();// 是否有構(gòu)造函數(shù)
    bool hasCxxDtor = cls->hasCxxDtor();// 是否有析構(gòu)函數(shù)
    bool fast = cls->canAllocNonpointer();// 是否使用原始 isa 格式

    size_t size = cls->instanceSize(extraBytes);// 需要分配的空間大小,從 instanceSize 實現(xiàn)可以知道對象至少16字節(jié)
        if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    
    if (!zone  &&  fast) {//跑代碼時會執(zhí)行這個分支
        obj = (id)calloc(1, size);//分配空間
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);//看到了一個熟悉的方法~
    } 
    else { //這里不執(zhí)行
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

在上面函數(shù)的實現(xiàn)中,執(zhí)行了 objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 方法。在第二部分講過,這個方法就是用來初始化isa_t結(jié)構(gòu)體的。

初始化 isa 之后,[NSObject alloc] 的工作算是做完了,下面就是 init 相關(guān)邏輯:

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}

可以看到, init 實際上只是返回了當前對象。

總結(jié)一下對象的初始化過程主要做了兩個事:1.分配需要的內(nèi)存空間 2.初始化isa_t結(jié)構(gòu)體。

參考文章:

從 NSObject 的初始化了解 isa

深入解析 ObjC 中方法的結(jié)構(gòu)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容