iOS進(jìn)階補(bǔ)完計劃--通讀runtime(.h)

說到runtime乍构。所有iOS的開發(fā)者無不知曉甜无。運(yùn)行時、swizzle哥遮、黑魔法等等岂丘。
不過用的時候是copy代碼、還是真正理解了runtime以及OC中類眠饮、對象奥帘、方法的本質(zhì)結(jié)構(gòu)。
起碼就我而言仪召、很長一段時間(以年來計算)寨蹋。都是前者。

所以這篇文章不屬于教學(xué)貼扔茅。希望借此能在runtime已旧、以及OC的本質(zhì)方面更深一步。

這里有一篇很不錯的入門文章召娜、內(nèi)部對runtime進(jìn)行了蠻詳細(xì)的歸納總結(jié)运褪。并且還列舉了很多runtime的實(shí)用帖子鏈接。
iOS 模塊詳解—「Runtime面試萤晴、工作」看我就 ?? 了 _

目錄

  • 類和對象
    • 實(shí)例對象(id)
    • 類對象(Class)
    • 元類(Meta Class)
    • 類與對象的總結(jié)
  • runtime正題
  • runtime方法的前綴
  • 對象操作方法(object_)
  • 全局操作方法(objc_)
  • 類操作方法(class_)
  • 動態(tài)創(chuàng)建類和對象
    • 動態(tài)創(chuàng)建類
    • 動態(tài)創(chuàng)建對象
  • 方法操作(method_)
  • 實(shí)例變量操作(ivar_)
    • 通過偏移量ivar_offset吐句、獲取/修改任意一個成員變量。
  • 屬性操作(property_)
    • 屬性的動態(tài)添加與獲取
  • 協(xié)議操作(protocol_)
    • 動態(tài)的創(chuàng)建一個協(xié)議
  • 庫操作
  • 選擇器操作(sel_)
  • 語言特性
  • 關(guān)聯(lián)策略
  • 編碼類型

類和對象

為什么runtime的博客店读、要聊究類和對象嗦枢?
或許寫出一段swizzle代碼更直觀

- (void)runtime_swizzle_test {
    Method method1 = class_getInstanceMethod([self class], @selector(func1));
    Method method2 = class_getInstanceMethod([self class], @selector(func2));
    method_exchangeImplementations(method1, method2);
}

所有的runtime方法、只要與方法操作相關(guān)屯断。
無一例外都需要使用Method或者其結(jié)構(gòu)體內(nèi)部的IMP文虏、SEL等指針。
而這個Method恰恰存放在的結(jié)構(gòu)體中殖演。

除此之外:

  • class_getInstanceMethod(獲取Method)氧秘、class_copyIvarList(獲取屬性列表)。也都是建立在class_也就是類操作之下趴久。
  • object_setIvar(屬性賦值)丸相。則是建立在object_、也就是對象操作之下彼棍。

所以灭忠、大概可以知道了解類和對象對理解runtime有多么大的幫助了膳算。

我們每天都在創(chuàng)建類、或者類的實(shí)例對象弛作。但類和實(shí)例對象到底是什么涕蜂?

[XXX new]或者[[XXX alloc]init]就是實(shí)例對象?
說得對、但是這淺顯了映琳。相信如果面試官如此提問机隙、你絕壁不敢這么回答的。

  • 實(shí)例對象(id)
id obj = self;

這就是我們的OC對象萨西。

/// A pointer to an instance of a class.
/// 指向一個實(shí)例的指針
typedef struct objc_object *id;


/// Represents an instance of a class.
/// 表示類的實(shí)例有鹿。
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
  • 實(shí)例對象在被創(chuàng)建的過程中會拷貝實(shí)例所屬的類的成員變量、但并不拷貝類定義的方法原杂。
  • isa
    整個id對象的結(jié)構(gòu)體內(nèi)部印颤、只有一個isa指針您机、指向了其所屬的Class穿肄。
  • 調(diào)用實(shí)例方法時。
    系統(tǒng)會根據(jù)實(shí)例的isa指針去類的方法列表及父類的方法列表中尋找與消息對應(yīng)的selector指向的方法际看。
  • 類對象(Class)
Class class = [self class];

這就是我們每天都在使用的Class咸产、也就是類。點(diǎn)進(jìn)去

#import <objc/objc.h>
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到Class本質(zhì)是一個objc_class的結(jié)構(gòu)體仲闽。繼續(xù)
這里為了看著舒服脑溢、我把一些標(biāo)注的宏刪掉了。

struct objc_class {
    Class  isa ;  // 指向所屬類的指針(_Nonnull)
    Class super_class;  // 父類(_Nullable)
    const char *  name; // 類名(_Nonnull)
    long version;  // 類的版本信息(默認(rèn)為0)
    long info;  // 類信息(供運(yùn)行期使用的一些位標(biāo)識)
    long instance_size;  // 該類的實(shí)例變量大小
    struct objc_ivar_list * ivars;  // 該類的成員變量鏈表(_Nullable)
    struct objc_method_list ** methodLists ;  // 方法定義的鏈表(_Nullable)
    struct objc_cache * cache  // 方法緩存(_Nonnull)
    struct objc_protocol_list * protocols;  // 協(xié)議鏈表(_Nullable)
} ;
  • isa
    Class的結(jié)構(gòu)體中也有isa指針赖欣、但和對象不同的是屑彻。Classisa指向的是Class所屬的類、即元類亲茅。
  • 調(diào)用類方法時
    系統(tǒng)會通過該isa指針從元類中尋找方法對應(yīng)的函數(shù)指針横堡。
  • super_class
    父類指針出牧、如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy)、則 super_class為NULL搏恤。
  • ivars
    objc_ivar_list類型的指針,用來存儲這個類中所有成員變量的信息湃交。
//變量列表
struct objc_ivar_list {
    int ivar_count ; 
#ifdef __LP64__
    int space ;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1] ;
}

//單個變量信息
struct objc_ivar {
    char * _Nullable ivar_name ;
    char * _Nullable ivar_type ;
    int ivar_offset ; //基地址偏移字節(jié)
#ifdef __LP64__
    int space ;
#endif
}             
  • methodLists
//方法列表
struct objc_method_list {
    struct objc_method_list * _Nullable obsolete ;

    int method_count  ;
#ifdef __LP64__
    int space ;
#endif
    /* variable length structure */
    struct objc_method method_list[1]  
}   
//單個方法的信息
struct objc_method {
    SEL _Nonnull method_name ;
    char * _Nullable method_types ;
    IMP _Nonnull method_imp  ;
} 
  • objc_cache
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

用于緩存最近使用的方法熟空。系統(tǒng)在調(diào)用方法時會先去cache中查找、在沒有查找到時才會去methodLists中遍歷獲取需要的方法搞莺。

  • 元類(Meta Class)

上面我們看到了息罗。在objc_class中也有一個isa指針、這說明Class類本身也是一個對象才沧。為了處理類和對象的關(guān)系迈喉、Runtime 庫創(chuàng)建了一種叫做Meta Class(元類) 的東西俏扩、類對象所屬的類就叫做元類。Meta Class表述了類對象本身所具備的元數(shù)據(jù)弊添。

  • 元類用來儲存類方法录淡。
  • 元類也是一個對象、所以才能調(diào)用他的方法油坝。
  • 元類的元類嫉戚、為根元類。
  • 根元類的元類澈圈、是其本身彬檀。

更多的關(guān)于元類的就暫且打住~畢竟對于runtime。了解這些元類和類的關(guān)系已經(jīng)夠用了瞬女。

  • 類與對象的總結(jié)
類與對象

還有一張?zhí)貏e經(jīng)典的圖


類關(guān)系示意圖
  • 規(guī)則一: 實(shí)例對象的isa指向該類窍帝、類的isa指向元類(metaClass)。

  • 規(guī)則二: 類的superClass指向其父類诽偷、如果該類為根類則值為nil坤学。

  • 規(guī)則三: 元類的isa指向根元類、如果該元類是根元類則指向自身报慕。

  • 規(guī)則四: 元類的superClass指向父元類深浮、若根元類則指向該根類。


runtime正題

runtime方法的前綴

runtime方法有很多眠冈、最方便歸納的方式就是通過前綴來確定其作用對象飞苇。

對象操作方法(object_)

包含了對對象的所有操作。
拷貝蜗顽、釋放布卡、獲取所屬類(isa)、所屬成員變量的操作(初始化的時候會copy成員變量列表)

/** 
 * 返回給定對象的副本雇盖。
 */
id object_copy(id _Nullable obj, size_t size)
/** 
 * 釋放對象內(nèi)存
 */
id object_dispose(id _Nullable obj)
/** 
 * 返回對象的類--也就是上文說的isa指針?biāo)傅念? */
Class object_getClass(id _Nullable obj) 
/** 
 * 為對象設(shè)置一個新的類
 * 其實(shí)就等于 NSArray  *arr = [NSMutableArray new];
 * 這個arr依舊是_M類型忿等、但是已經(jīng)不能使用addobj方法了、起碼在編譯階段是刊懈。
 */
Class object_setClass(id _Nullable obj, Class _Nonnull cls) 
/** 
 * 判斷一個對象是否為Class類型的對象
 */
BOOL object_isClass(id _Nullable obj)
/** 
 * 返回一個對象中指定實(shí)例變量(Ivar)的值
 */
id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 
/*
* 為對象設(shè)置一個實(shí)例變量的值
*/
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
/*
*  10.0以后多了一個方法
*/
void object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar,
                                id _Nullable value) 
/*
* 非ARC下的兩個set方法
*/
void object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable value);
void object_setInstanceVariableWithStrongDefault(id _Nullable obj,
                                            const char * _Nonnull name,
                                            void * _Nullable value)
/*
* 非ARC下的get方法
*/
Ivar object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable * _Nullable outValue)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
其中有比較特殊的:
  • Class object_getClass(id _Nullable obj)
    這個方法可以獲得對象結(jié)構(gòu)體內(nèi)部isa指針?biāo)刚饣 ⒁簿褪菍ο笏鶎俚念悺?具體是啥可以翻回去看)。
    但需要注意的是虚汛、這個和我們平時用的[self class]方法匾浪、并不完全等價。
+ (Class)class {
    return self; // 返回自身指針
}
- (Class)class {
    return object_getClass(self); // 調(diào)用'object_getClass'返回isa指針
}

也就是說卷哩、通過[Class object_getClass]是可以獲取元類的蛋辈、但是通過[Class class]則不行。

  • void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
    為對象設(shè)置一個實(shí)例變量的值
    但依舊不能為不存在的變量賦值
    當(dāng)獲取一個不存在實(shí)例變量時、Ivar將等于0x0000000000000000冷溶。自然沒辦法對其進(jìn)行賦值渐白。

全局操作方法(objc_)

這里的方法并不基于對象、也不需要以對象作為參數(shù)逞频。
基本都是一些類相關(guān)的獲取纯衍。

/*
* 返回指定的Class
* 如果未注冊命名類的定義,則該函數(shù)調(diào)用類處理程序苗胀。
* 然后再次檢查是否注冊了類襟诸。
*/
Class  objc_getClass(const char * _Nonnull name)
/*
* 返回指定的元類Class
*/
Class objc_getMetaClass(const char * _Nonnull name)
/*
* 返回指定的Class
* 不調(diào)用類處理程序回調(diào)。
*/
Class objc_lookUpClass(const char * _Nonnull name)
/*
* 返回指定的Class
* 如果沒有找到類基协,就會殺死進(jìn)程歌亲。
*/
Class objc_getRequiredClass(const char * _Nonnull name)
/*
* 獲取所有已經(jīng)注冊類的列表
*/
int objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
/*
* 獲取所有已經(jīng)注冊類的實(shí)例列表
*/
Class *objc_copyClassList(unsigned int * _Nullable outCount)
其中有比較特殊的:
  • 獲取所有已經(jīng)注冊的類--數(shù)量.
int objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
int numClasses = 0, newNumClasses = objc_getClassList(NULL, 0);
Class *classes = NULL;
while (numClasses < newNumClasses) {
    numClasses = newNumClasses;
    classes = (Class *)realloc(classes, sizeof(Class) * numClasses);
    newNumClasses = objc_getClassList(classes, numClasses);
    
    for (int i = 0; i < numClasses; i++) {
        const char *className = class_getName(classes[i]);
        if (class_getSuperclass(classes[i]) == [UIScrollView class]) {
            //打印所有UIScrollView的子類
            NSLog(@"subclass of UIScrollView : %s", className);
        }
    }
}
free(classes);
  • 獲取所有已經(jīng)注冊的類--實(shí)例列表.
Class *objc_copyClassList(unsigned int * _Nullable outCount)
unsigned int outCount;
Class *classes = objc_copyClassList(&outCount);
for (int i = 0; i < outCount; i++) {
    if (class_getSuperclass(classes[i]) == [UIScrollView class]) {
        //打印所有UIScrollView的子類
        NSLog(@"subclass of UIScrollView : %s", class_getName(classes[i]));
    }
}
free(classes);

類對象操作方法(class_)

這里的所有操作都針對類對象

/** 
 * 返回類的名稱
 */
char * class_getName(Class _Nullable cls) 
/** 
 * 判斷該類對象是否為元類
 */
BOOL class_isMetaClass(Class _Nullable cls) 
/** 
 * 獲取結(jié)構(gòu)體中的super_class、也就是父類對象指針
 */
Class class_getSuperclass(Class _Nullable cls) 
/** 
 * 給一個類對象設(shè)置新的父類指針
 * 返回舊的父類
 * 這個方法已經(jīng)被棄用并且apple明確指出不應(yīng)該使用澜驮。
 */
Class class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) 
/** 
 * 獲取類對象的版本號
 * 沒發(fā)現(xiàn)啥用處陷揪、不過測試起來
 * 所有的元類都為7
 * 普通類NSDateFormatter最高(41)、并且大于5的只有它一個
 */
int class_getVersion(Class _Nullable cls)
/** 
 * 為一個類設(shè)置版本號
 */
void class_setVersion(Class _Nullable cls, int version)
/** 
 * 獲取一個類的實(shí)例大小
 */
size_t class_getInstanceSize(Class _Nullable cls)
/** 
 * 獲取類中指定名稱實(shí)例成員變量的信息(Ivar)
 */
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
/** 
 * 獲取類"成員變量"的信息(Ivar)
 * 但是好像OC中沒有這種東西
 */
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) 
/** 
 * 獲取全部成員變量列表
 * 但是不包括父類
 */
Ivar * class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 查找某個實(shí)例方法
 */
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
/** 
 * 查找某個類方法
 */
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
/** 
 * 查找某個實(shí)例方法的實(shí)現(xiàn)(IMP)
 */
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
/** 
 * 查找某個實(shí)例方法的實(shí)現(xiàn)(IMP)
 * 不過在“ arm64”也就是現(xiàn)在的機(jī)器上報報紅
 */
IMP class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) 
/** 
 * 查找某個實(shí)例方法的實(shí)現(xiàn)(IMP)
 * 不過在“ arm64”也就是現(xiàn)在的機(jī)器上報報紅
 */
BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) 
/** 
 * 返回該類所有方法(結(jié)構(gòu)體中的objc_method_list)
 */
Method * class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 判斷類是否實(shí)現(xiàn)了某個協(xié)議
 * 其實(shí)就是[self conformsToProtocol:@protocol(UITableViewDelegate)]的底層
 */
BOOL class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) 
/** 
 * 返回該類的協(xié)議列表(結(jié)構(gòu)體中的objc_protocol_list)
 */
Protocol * * class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 返回該類的指定屬性(注意不是成員變量)
 */
objc_property_t class_getProperty(Class _Nullable cls, const char * _Nonnull name)
/** 
 * 返回該類的屬性列表
 */
objc_property_t * class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
* 返回該類成員變量的布局 
*/
uint8_t * class_getIvarLayout(Class _Nullable cls)
/** 
* 返回該類weak成員變量的布局 
*/
uint8_t * class_getWeakIvarLayout(Class _Nullable cls)
/** 
* 添加方法 如果返回YES則添加成功
*/
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
/** 
* 替換方法實(shí)現(xiàn)(IMP)
* 如果之前存在該方法--直接替換
* 如果之前不存在該方法--添加方法
*/
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
/** 
* 添加成員變量
* 這個函數(shù)只能在objc_allocateClassPair和objc_registerClassPair之前調(diào)用杂穷。
* 也間接的說明悍缠、已經(jīng)注冊的類不能動態(tài)的添加成員變量
*/
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
/** 
* 添加協(xié)議
*/
BOOL class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) 
/** 
* 添加屬性
* 注意這里屬性的意義。和Category中的一樣亭畜、并不包含成員變量和setget方法
*/
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
/** 
* 替換屬性
* 注意這里屬性的意義扮休。和Category中的一樣、并不包含成員變量和setget方法
*/
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)
/** 
* 設(shè)置變量布局
*/
void class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
/** 
* 設(shè)置weak變量布局
*/
void class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
/** 
* 由CoreFoundation的toll-free橋接使用拴鸵。
* 不能主動調(diào)用
*/
Class objc_getFutureClass(const char * _Nonnull name) 
其中有比較特殊的:
  • class_getSuperclass[self superclass]
    class_getSuperclass等價于[self superclass]都直接獲取指向父類對象的指針。
    并且由之前的結(jié)構(gòu)圖(類關(guān)系示意圖)可知
    1蜗搔、元類的元類為NSObject
    2劲藐、元類的父類為正常結(jié)構(gòu)的父類
    可以驗(yàn)證一下
NSLog(@"類對象--%@",[self class]);
NSLog(@"父類對象--%@",[self superclass]);
NSLog(@"父類對象--%@",[[self superclass] superclass]);
NSLog(@"父類對象--%@",[[[self superclass] superclass] superclass]);
NSLog(@"父類對象--%@",[[[[self superclass] superclass] superclass] superclass]);

//獲取self的類 ==》 ViewController(類)  再獲取其元類 ViewController(元類)
Class class = object_getClass(object_getClass(self));
NSLog(@"元類對象--%@",class);
NSLog(@"如果再繼續(xù)獲取元類---%@",object_getClass(class));
NSLog(@"父元類對象--%@",class_getSuperclass(class));
NSLog(@"父元類對象--%@",class_getSuperclass(class_getSuperclass(class)));
NSLog(@"父元類對象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class))));
NSLog(@"父類對象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class)))));
NSLog(@"父類對象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class))))));

打印結(jié)果:

類對象--ViewController
父類對象--UIViewController
父類對象--UIResponder
父類對象--NSObject
父類對象--(null)
元類對象--ViewController
如果再繼續(xù)獲取元類---NSObject
父元類對象--UIViewController
父元類對象--UIResponder
父元類對象--NSObject
父類對象--NSObject
父類對象--(null)

動態(tài)創(chuàng)建類和對象

  • 動態(tài)創(chuàng)建類
/** 
*  創(chuàng)建一個新的類、以及其元類
*/
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
/** 
*  注冊一個類
*/
void objc_registerClassPair(Class _Nonnull cls) 
/** 
*  用于KVO
*  禁止主動調(diào)用
*/
Class objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
                    size_t extraBytes)
/** 
*  銷毀一個類
*  如果程序運(yùn)行中還存在類或其子類的實(shí)例樟凄,則不能調(diào)用針對類調(diào)用該方法
*/
void objc_disposeClassPair(Class _Nonnull cls) 

objc_allocateClassPair函數(shù):如果我們要創(chuàng)建一個根類聘芜,則superclass指定為Nil。extraBytes通常指定為0缝龄,該參數(shù)是分配給類和元類對象尾部的索引ivars的字節(jié)數(shù)汰现。
為了創(chuàng)建一個新類,我們需要調(diào)用objc_allocateClassPair叔壤。
然后使用諸如class_addMethod瞎饲,class_addIvar等函數(shù)來為新創(chuàng)建的類添加方法、實(shí)例變量和屬性等炼绘。
完成這些后嗅战,我們需要調(diào)用objc_registerClassPair函數(shù)來注冊類,之后這個新類就可以在程序中使用了。
有兩點(diǎn)需要注意:

  • 實(shí)例方法和實(shí)例變量應(yīng)該添加到類自身上驮捍,而類方法應(yīng)該添加到類的元類上疟呐。
  • class_addIvar必須使用在類生成后objc_allocateClassPair、注冊前objc_registerClassPair东且。因?yàn)槌绦虿辉试S一個已經(jīng)注冊启具、分配好內(nèi)存的類進(jìn)行擴(kuò)容。
    ※※※※這也是為什么類別珊泳、添加了屬性卻不能添加成員變量的原因

舉個例子:

Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
 
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
 
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
 
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
  • 動態(tài)創(chuàng)建對象
/** 
* 實(shí)例化一個類對象
* 基本等價于[XXX new]
* 第二個參數(shù)表示分配的額外字節(jié)數(shù)富纸。這些額外的字節(jié)可用于存儲在類定義中所定義的實(shí)例變量之外的實(shí)例變量。
*/
id class_createInstance(Class _Nullable cls, size_t extraBytes)
/** 
* 在指定位置實(shí)例化一個類對象
*/
id objc_constructInstance(Class _Nullable cls, void * _Nullable bytes) 
/** 
* 銷毀一個類的實(shí)例
* 但不會釋放并移除任何與其相關(guān)的引用
*/
void * _Nullable objc_destructInstance(id _Nullable obj) 

方法操作(method_)

大概我們應(yīng)該先了解一下Method

struct objc_method {
    SEL _Nonnull method_name;  //方法名旨椒、借此才能從方法列表鎖定單個Method
    char * _Nullable method_types; //方法返回值以及參數(shù)的描述
    IMP _Nonnull method_imp; //方法實(shí)現(xiàn).(方法實(shí)現(xiàn)所在的位置)
} 

Method包含了一個方法所需要的全部要素晓褪。并且可以自由獲取任意其一。

/** 
* 返回方法的名稱
*/
SEL method_getName(Method _Nonnull m) 
/** 
* 返回方法的實(shí)現(xiàn)(IMP)
*/
IMP method_getImplementation(Method _Nonnull m) 
/** 
* 返回描述方法參數(shù)和返回類型的字符串
*/
char * method_getTypeEncoding(Method _Nonnull m) 
/** 
* 返回方法參數(shù)個數(shù)
* 參數(shù)最低為兩個(每個方法都有self以及_cmd兩個隱性參數(shù))
*/
int method_getNumberOfArguments(Method _Nonnull m)
/** 
* 返回方法返回類型
* 類型會傳遞給第二個參數(shù)"dst"
*/
void method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len)
/** 
* 返回方法返回類型
* 第二個參數(shù)index為參數(shù)位置综慎、類型會傳遞給第三個參數(shù)"dst"(越位會返回'\0')
*/
void method_getArgumentType(Method _Nonnull m, unsigned int index, 
                       char * _Nullable dst, size_t dst_len) 
/** 
* 為一個Method設(shè)置具體實(shí)現(xiàn)(IMP)
*/
IMP method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 
/** 
* ※※※大名鼎鼎的swizzle※※※
*/
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

能說的不多:

  • swizzle的本質(zhì)?

method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)的注釋里說很明白了.

 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);

本質(zhì)就是交換了兩個方法的實(shí)現(xiàn)涣仿。
所以說用method_setImplementation設(shè)置IMP的話、也可以實(shí)現(xiàn)一個IMP實(shí)現(xiàn)對應(yīng)多個SEL示惊。

實(shí)例變量操作(ivar_)

Method一樣好港。我們也來看看ivar的結(jié)構(gòu)體、看看ivar能做什么米罚。


/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name; //變量名
    char * _Nullable ivar_type; //變量類型
    int ivar_offset  //基地址偏移字節(jié)
#ifdef __LP64__
    int space ; //占用空間
#endif
}       

實(shí)例變量操作的API特別少(因?yàn)楂@取/設(shè)置變量的值都由ojbect_進(jìn)行)

/** 
* 返回實(shí)例變量的名稱
*/
char * _Nullable ivar_getName(Ivar _Nonnull v) 
/** 
* 返回實(shí)例變量的類型
*/
char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) 
/** 
* 返回實(shí)例變量的偏移量
*/
ptrdiff_t ivar_getOffset(Ivar _Nonnull v) 
  • 基地址偏移字節(jié)ivar_offset
    • 先聊聊類的布局
      在編譯我們的類時钧汹、編譯器生成了一個 ivar布局、顯示了在類中從哪可以訪問我們的 ivars 录择。如下圖:

      我們增加了父類的ivar拔莱、這個時候布局就出錯了。

      雖然runtime可以在類加載的時候通過Non Fragile ivars進(jìn)行重新布局隘竭。
但是L燎亍!动看!尊剔、對已經(jīng)加載完成(注冊好)的類、增加成員變量一樣會導(dǎo)致布局出錯菱皆。

這也是為什么類別不能添加屬性的根本原因须误。

  • 通過偏移量ivar_offset、獲取/修改任意一個成員變量仇轻。

我們可以通過對象地址 + ivar偏移字節(jié)的方法京痢、獲取任意一個成員變量。

@interface Person : NSObject
{
    @private int age_int;
    @private NSString * age_str;
    @public int age_public;
}
@end

@implementation Person
- (instancetype)init
{
    self = [super init];
    if (self) {
        age_public = 1;
        age_int = 1;
        age_str = @"1";
    }
    return self;
}

- (NSString *)description
{
    NSLog(@"person pointer==%p", self);
    NSLog(@"age_int pointer==%p", &age_int);
    return [NSString stringWithFormat:@"age_int==%d", age_int];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        //私有成員變量--基本類型拯田、可以使用偏移量
        Person * person = [Person new];
        Ivar age_int_ivar = class_getInstanceVariable(object_getClass(person), "age_int");
        int *age_int_pointer = (int *)((__bridge void *)(person) + ivar_getOffset(age_int_ivar));
        
        
        NSLog(@"age_int offset==%td", ivar_getOffset(age_int_ivar));
        
        * age_int_pointer = 10;
        NSLog(@"%@",person);
    }
    return 0;
}

打印結(jié)果:

age_int offset==8
person pointer==0x10061b070
age_int pointer==0x10061b078
age_int==10

屬性操作(property_)

/** 
 * 返回屬性名
 */
char * _Nonnull property_getName(objc_property_t _Nonnull property) 
/** 
 * 返回屬性描述(也就是屬性的屬性)
 * @property (nonatomic ,readonly ,strong) NSObject * obj;
 * "T@\"NSObject\",R,N,V_obj"
 */
char * _Nullable property_getAttributes(objc_property_t _Nonnull property) 
/** 
 * 返回屬性描述數(shù)組
 */
objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
                           unsigned int * _Nullable outCount)
/** 
 * 返回屬性描述(也就是屬性的屬性)
 */
char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property,
                            const char * _Nonnull attributeName)
  • 屬性的動態(tài)添加與獲取
    注意這里的添加和category一樣历造、有名無實(shí)。
objc_property_attribute_t type = { "T", "@\"NSString\"" };//屬性類型為NSString
objc_property_attribute_t ownership = { "C", "copy" }; // C = copy
objc_property_attribute_t backingivar  = { "V", "_littleName" };//_littleName為Person類的全局變量,這里是讓新屬性與之關(guān)聯(lián)
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty([Person class], "littleName", attrs, 3);
//驗(yàn)證是否添加成功
objc_property_t pt =  class_getProperty([Person class], "littleName");
NSLog(@"property's name: %s", property_getName(pt));
NSLog(@"property'attribute:%s",property_getAttributes(pt));
NSLog(@"property'release:%s",property_copyAttributeValue(pt, "C"));
NSLog(@"property'type:%s",property_copyAttributeValue(pt, "T"));
NSLog(@"property'value:%s",property_copyAttributeValue(pt, "V"));

打印結(jié)果:

property's name: littleName
property'attribute:T@"NSString",Ccopy,V_littleName
property'release:copy
property'type:@"NSString"
property'value:_littleName

協(xié)議操作(protocol_)

/** 
 * 返回一個協(xié)議
 */
Protocol * _Nullable objc_getProtocol(const char * _Nonnull name)
/** 
 * 返回runtime已知的所有協(xié)議的數(shù)組
 */
Protocol ** _Nullable objc_copyProtocolList(unsigned int * _Nullable outCount)
/** 
 * 判斷一個協(xié)議是否遵循了另一個協(xié)議
 */
BOOL protocol_conformsToProtocol(Protocol * _Nullable proto,
                            Protocol * _Nullable other)
/** 
 * 判斷兩個協(xié)議是否相同
 */
BOOL protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other)
/** 
 * 返回協(xié)議的名稱
 */
char * _Nonnull protocol_getName(Protocol * _Nonnull proto)
/** 
 * 返回協(xié)議中指定方法的描述
 */
struct objc_method_description protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel,
                              BOOL isRequiredMethod, BOOL isInstanceMethod)
/** 
 * 返回協(xié)議中指定規(guī)則的所有方法描述
 */
struct objc_method_description * _Nullable protocol_copyMethodDescriptionList(Protocol * _Nonnull proto,
                                   BOOL isRequiredMethod,
                                   BOOL isInstanceMethod,
                                   unsigned int * _Nullable outCount)
/** 
 * 返回協(xié)議的指定屬性
 */
objc_property_t protocol_getProperty(Protocol * _Nonnull proto,
                     const char * _Nonnull name,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 返回協(xié)議中所有屬性的列表
 */
objc_property_t * protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
/** 
 * 返回協(xié)議中所有指定規(guī)則屬性的列表
 */
objc_property_t * protocol_copyPropertyList2(Protocol * _Nonnull proto,
                           unsigned int * _Nullable outCount,
                           BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 返回協(xié)議所遵循的協(xié)議列表(比如`NSPortDelegate`遵循了`NSObject`)
 */
Protocol ** protocol_copyProtocolList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
/** 
 * 創(chuàng)建一個協(xié)議(未注冊)
 */
Protocol * objc_allocateProtocol(const char * _Nonnull name) 
/** 
 * 向協(xié)議中添加方法
 */
void protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                              const char * _Nullable types,
                              BOOL isRequiredMethod, BOOL isInstanceMethod) 
/** 
 * 向協(xié)議中添加依賴協(xié)議
 */
void protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) 
/** 
 * 向協(xié)議中添屬性
 */
void protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
                     const objc_property_attribute_t * _Nullable attributes,
                     unsigned int attributeCount,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 注冊一個協(xié)議
 */
void objc_registerProtocol(Protocol * _Nonnull proto) 
  • 動態(tài)的創(chuàng)建一個協(xié)議
Protocol * protocol = objc_getProtocol("KTDelegate");
NSLog(@"%@",protocol);

if (!protocol) {
    //創(chuàng)建
    protocol = objc_allocateProtocol("KTDelegate");
    protocol_addMethodDescription(protocol, @selector(protocol_func), "desc", YES, YES);
    protocol_addProtocol(protocol, objc_getProtocol("NSObject"));
    
    
    objc_property_attribute_t type = { "T", "@\"NSString\"" };//屬性類型為NSString
    objc_property_attribute_t ownership = { "C", "copy" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_littleName" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    
    protocol_addProperty(protocol, "_littleName", attrs, 3, YES, YES);
    //注冊
    objc_registerProtocol(protocol);
    NSLog(@"register_end");
}

protocol = objc_getProtocol("KTDelegate");
NSLog(@"protocol == %@",protocol);
NSLog(@"protocol_name == %s",protocol_getName(protocol));
NSLog(@"protocol_desc == %s",protocol_getMethodDescription(protocol, @selector(protocol_func), YES, YES));
NSLog(@"protocol_property == %s",property_getName(protocol_getProperty(protocol, "_littleName", YES, YES)));

打印結(jié)果:

(null)
register_end
protocol == <Protocol: 0x100462f80>
protocol_name == KTDelegate
protocol_desc == protocol_func
protocol_property == _littleName

庫操作

/** 
 * 返回所有加載在Objective-C框架和動態(tài)庫上的名稱
 * char ** arr = objc_copyImageNames(&num);
 */
char ** objc_copyImageNames(unsigned int * _Nullable outCount) 
/** 
 * 返回一個類所屬的動態(tài)庫名稱
 */
char * class_getImageName(Class _Nullable cls) 
/** 
 * 返回一個庫中所有類的名稱
 */
char ** objc_copyClassNamesForImage(const char * _Nonnull image,
                            unsigned int * _Nullable outCount) 

簡單舉個例子

char * libName = class_getImageName(objc_getClass("NSString"));
int num = 0;
char ** libarr = objc_copyClassNamesForImage(libName, &num);
for (int i = 0; i < num; i ++) {
    NSLog(@"%s",libarr[i]);
}

選擇器操作(sel_)

/** 
 * 返回一個選擇器所指定的方法名稱
 */
char * sel_getName(SEL _Nonnull sel)
/** 
 * 注冊一個方法選擇器
 */
SEL sel_registerName(const char * _Nonnull str)
/** 
 * 比較兩個選擇器
 * SEL sel1 = @selector(fun1:);
 * SEL sel2 = sel_registerName("fun1:");
 * BOOL b = sel_isEqual(sel1, sel2);
 */
BOOL sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) 

語言特性

/** 
 * 當(dāng)一個obj突變發(fā)生時吭产,這個函數(shù)會拋出異常
 */
void objc_enumerationMutation(id _Nonnull obj) 
/** 
 * 突變處理回調(diào)
 */
void objc_setEnumerationMutationHandler(void (*_Nullable handler)(id _Nonnull )) 
/** 
 * objc_msgForward方法的調(diào)用
 */
void objc_setForwardHandler(void * _Nonnull fwd, void * _Nonnull fwd_stret) 
/** 
 * 就是直接把一個block作為IMP
 */
IMP imp_implementationWithBlock(id _Nonnull block)
/** 
 * 返回(使用imp_implementationWithBlock創(chuàng)建的)IMP對應(yīng)的block
 * 舉個例子:
    void (^block)(id ,SEL) = ^(id self ,SEL _sel){
         NSLog(@"%s",sel_getName(_sel));
    };
    IMP imp = imp_implementationWithBlock(block);
    class_addMethod([Person class], @selector(imp_fun), imp, "v@:@");
    [[Person new] performSelector:@selector(imp_fun)];
    void (^block2)(id ,SEL)= imp_getBlock(imp);
    block2([Person new],@selector(fun_block2));

    輸出:
    imp_fun
    fun_block2
 */
id imp_getBlock(IMP _Nonnull anImp);
/** 
 *   移除(使用imp_implementationWithBlock創(chuàng)建的)IMP對應(yīng)的block
 */
BOOL imp_removeBlock(IMP _Nonnull anImp)
/** 
 *   在表達(dá)式中使用__weak變量時將自動使用該函數(shù)
 *   該函數(shù)加載一個弱指針引用的對象侣监,并在對其做retain和autoreleasing操作后返回它。這樣臣淤,對象就可以在調(diào)用者使用它時保持足夠長的生命周期
    UIView * view = [UIView new];
    __weak UIView * w_view = view;
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
    objc_loadWeak(&w_view);
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
    __strong UIView * s_view = w_view;
    __weak UIView * ww_view = w_view;
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
 */
id objc_loadWeak(id _Nullable * _Nonnull location)
/** 
 *   可以用來為__weak變量賦值
 */
id objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) 

關(guān)聯(lián)策略

// 關(guān)聯(lián)策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {

    OBJC_ASSOCIATION_ASSIGN = 0,           //@property(assign)橄霉。
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //@property(strong, nonatomic)
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  //@property(copy, nonatomic)
    OBJC_ASSOCIATION_RETAIN = 01401,       //@property(strong,atomic)
    OBJC_ASSOCIATION_COPY = 01403         //@property(copy, atomic)
};

/** 
 *   將一個key&&value關(guān)聯(lián)到另一個對象上、并且設(shè)置關(guān)聯(lián)策略
 */
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
/** 
 *   將關(guān)聯(lián)的對象取出
 */
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/** 
 *   移除關(guān)聯(lián)所有對象
 */
void objc_removeAssociatedObjects(id _Nonnull object)

這個就是我們經(jīng)常用的邑蒋、給類別綁定屬性的

static char *key = "key";
objc_setAssociatedObject(self, &key, @"hello world", OBJC_ASSOCIATION_COPY_NONATOMIC);
NSString *associated = objc_getAssociatedObject(self, &key);
NSLog(@"objc_getAssociatedObject===%@",associated);

編碼類型

#define _C_ID       '@' // 代表對象類型
#define _C_CLASS    '#' // 代表類對象 (Class)
#define _C_SEL      ':' // 代表方法selector (SEL)
#define _C_CHR      'c' // 代表char類型
#define _C_UCHR     'C' // 代表unsigned char類型
#define _C_SHT      's' // 代表short類型
#define _C_USHT     'S' // 代表unsigned short類型
#define _C_INT      'i' // 代表int類型
#define _C_UINT     'I' // 代表unsigned int類型
#define _C_LNG      'l' // 代表long類型姓蜂,在64位處理器上也是按照32位處理
#define _C_ULNG     'L' // 代表unsigned long類型
#define _C_LNG_LNG  'q' // 代表long long類型
#define _C_ULNG_LNG 'Q' // 代表unsigned long long類型
#define _C_FLT      'f' // 代表float類型
#define _C_DBL      'd' // 代表double類型
#define _C_BFLD     'b' //
#define _C_BOOL     'B' // 代表C++中的bool或者C99中的_Bool
#define _C_VOID     'v' // 代表void類型
#define _C_UNDEF    '?' // 代表未知類型
#define _C_PTR      '^' // 代表指針類型
#define _C_CHARPTR  '*' // 代表字符串類型 (char *)
#define _C_ATOM     '%' //
#define _C_ARY_B    '[' // 代表array
#define _C_ARY_E    ']'
#define _C_UNION_B  '(' // 代表UNION
#define _C_UNION_E  ')'
#define _C_STRUCT_B '{' // 代表結(jié)構(gòu)體
#define _C_STRUCT_E '}'
#define _C_VECTOR   '!' // 代表矢量類型
#define _C_CONST    'r' // 代表常量類型

最后

本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏医吊、萬望留言斧正钱慢。如果不吝賜教小弟更加感謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卿堂,一起剝皮案震驚了整個濱河市束莫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌草描,老刑警劉巖览绿,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異穗慕,居然都是意外死亡饿敲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門逛绵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀各,“玉大人,你說我怎么就攤上這事暑脆∏。” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵添吗,是天一觀的道長。 經(jīng)常有香客問我份名,道長碟联,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任僵腺,我火速辦了婚禮鲤孵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辰如。我一直安慰自己普监,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凯正,像睡著了一般毙玻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上廊散,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天桑滩,我揣著相機(jī)與錄音,去河邊找鬼允睹。 笑死运准,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缭受。 我是一名探鬼主播胁澳,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼米者!你這毒婦竟也來了韭畸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤塘雳,失蹤者是張志新(化名)和其女友劉穎陆盘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體败明,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隘马,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妻顶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酸员。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讳嘱,靈堂內(nèi)的尸體忽然破棺而出幔嗦,到底是詐尸還是另有隱情,我是刑警寧澤沥潭,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布邀泉,位于F島的核電站,受9級特大地震影響钝鸽,放射性物質(zhì)發(fā)生泄漏汇恤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一拔恰、第九天 我趴在偏房一處隱蔽的房頂上張望因谎。 院中可真熱鬧,春花似錦颜懊、人聲如沸财岔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠璧。三九已至桐款,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間患朱,已是汗流浹背鲁僚。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裁厅,地道東北人冰沙。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像执虹,于是被迫代替她去往敵國和親拓挥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

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