iOS-runtime的理解

什么是runtime

官方描述:
The Objective-C language defers as many decisions as it can from compile time and link time to runtime.
“盡量將決定放到運(yùn)行的時(shí)候唬渗,而不是在編譯和鏈接過(guò)程”

runtime是一個(gè)C語(yǔ)言庫(kù)饮戳,包含了很多底層的純C語(yǔ)言API。 平時(shí)編寫的OC代碼中鹅颊,程序運(yùn)行譬淳,其實(shí)最終都是轉(zhuǎn)成了runtime的C語(yǔ)言代碼揩页,runtime算是OC的幕后工作者 茄蚯。

  • 特點(diǎn)
    OC與其他語(yǔ)言不同的一點(diǎn)就是镜硕,函數(shù)調(diào)用采用了消息轉(zhuǎn)發(fā)的機(jī)制茂浮,但直到程序運(yùn)行之前双谆,消息都沒(méi)有與任何方法綁定起來(lái)。只有在真正運(yùn)行的時(shí)候席揽,才會(huì)根據(jù)函數(shù)的名字來(lái)顽馋,確定該調(diào)用的函數(shù)。

runtime 是有個(gè)兩個(gè)版本的:
Objective-C 1.0使用的是legacy幌羞,在2.0使用的是modern寸谜。
現(xiàn)在一般來(lái)說(shuō)runtime都是指modern。

1. isa指針

首先要了解它底層的一些常用數(shù)據(jù)結(jié)構(gòu)属桦,比如isa指針熊痴。

當(dāng)創(chuàng)建一個(gè)新對(duì)象時(shí),會(huì)為它分配一段內(nèi)存聂宾,該對(duì)象的實(shí)例變量也會(huì)被初始化果善。第一個(gè)變量就是一個(gè)指向它的類的指針(isa)。
通過(guò)isa指針系谐,一個(gè)對(duì)象可以訪問(wèn)它的類巾陕,并通過(guò)它的類來(lái)訪問(wèn)所有父類讨跟。

  • 一個(gè)實(shí)例對(duì)象,在runtime中用結(jié)構(gòu)體表示
// 描述類中的一個(gè)方法
typedef struct objc_method *Method;

// 實(shí)例變量
typedef struct objc_ivar *Ivar;

// 類別Category
typedef struct objc_category *Category;

// 類中聲明的屬性
typedef struct objc_property *objc_property_t;

查看runtime源碼可以看到關(guān)于isa結(jié)構(gòu)鄙煤。

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

    Class cls;
    uintptr_t bits;
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
};

下面的代碼對(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)的位沟于。

define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;   //指針是否優(yōu)化過(guò)                                   \
      uintptr_t has_assoc         : 1;   //是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象咳胃,如果沒(méi)有植康,釋放時(shí)會(huì)更快                                   \
      uintptr_t has_cxx_dtor      : 1;   //是否有C++的析構(gòu)函數(shù)(.cxx_destruct)旷太,如果沒(méi)有,釋放時(shí)會(huì)更快                                     \
      uintptr_t shiftcls          : 33; //存儲(chǔ)著Class销睁、Meta-Class對(duì)象的內(nèi)存地址信息 \
      uintptr_t magic             : 6;  //用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化                                     \
      uintptr_t weakly_referenced : 1;  //是否有被弱引用指向過(guò)供璧,如果沒(méi)有,釋放時(shí)會(huì)更快                                     \
      uintptr_t deallocating      : 1;  //對(duì)象是否正在釋放                                     \
      uintptr_t has_sidetable_rc  : 1;  //引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中                                     \
      uintptr_t extra_rc          : 19 //里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
  • nonpointer
    0:代表普通的指針冻记,存儲(chǔ)著Class睡毒、Meta-Class對(duì)象的內(nèi)存地址。
    1:代表優(yōu)化過(guò)冗栗,使用位域存儲(chǔ)更多的信息演顾。
  • has_assoc
    是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象。如果沒(méi)有隅居,釋放時(shí)會(huì)更快钠至。
  • has_cxx_dtor
    是否有C++的析構(gòu)函數(shù).cxx_destruct如果沒(méi)有,釋放時(shí)會(huì)更快胎源。
  • shiftcls
    存儲(chǔ)著Class棉钧、Meta-Class對(duì)象的內(nèi)存地址信息
  • magic
    用于在調(diào)試時(shí),分辨對(duì)象是否未完成初始化
  • weakly_referenced
    是否有被弱引用指向過(guò)涕蚤。如果沒(méi)有宪卿,釋放時(shí)會(huì)更快
  • deallocating
    對(duì)象是否正在釋放
  • extra_rc
    里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
  • has_sidetable_rc
    引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中
    如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中

2. class結(jié)構(gòu)

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

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;   // 父類
    cache_t cache;    //方法緩存
    class_data_bits_t bits;    // 用于獲取具體的類的信息
}

查看源碼(只保留了主要代碼)

  • class_rw_t
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;
}

其中的methods万栅、properties佑钾、protocols是二維數(shù)組,是可讀可寫的烦粒,包含了類的初始內(nèi)容休溶、分類的內(nèi)容。

  • method_array_t
class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

方法列表 中存放著很多一維數(shù)組method_list_t,而每一個(gè)method_list_t中存放著method_t邮偎。method_t中是對(duì)應(yīng)方法的imp指針管跺、名字、類型等方法信息禾进。

  • method_t
struct method_t {
    SEL name; //函數(shù)名
    const char *types; //編碼(返回值類型豁跑,參數(shù)類型)
    MethodListIMP imp; //指向函數(shù)的指針(函數(shù)地址)

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

IMP:代表函數(shù)的具體實(shí)現(xiàn)
SEL:代表方法、函數(shù)名泻云,一般叫做選擇器艇拍。
types:包含了函數(shù)返回值、參數(shù)編碼的字符串

關(guān)于SEL:
可以通過(guò)@selector()sel_registerName()獲得
可以通過(guò)sel_getName()NSStringFromSelector()轉(zhuǎn)成字符串
不同類中相同名字的方法宠纯,所對(duì)應(yīng)的方法選擇器是相同的卸夕。即,不同類的相同SEL是同一個(gè)對(duì)象婆瓜。

  • class_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance對(duì)象占用的內(nèi)存空間
#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; //成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

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

class_ro_t里面的baseMethodList快集、baseProtocols、ivars廉白、baseProperties是一維數(shù)組个初,是只讀的,包含了類的初始內(nèi)容

3. Type Encoding

iOS中提供了一個(gè)叫做@encode的指令猴蹂,可以將具體的類型表示成字符串編碼院溺。比如:

+(int)testWithNum:(int)num{
    return num;
}

上面的方法可以用 i20@0:8i16來(lái)表示:

i表示返回值是int類型,20是參數(shù)總共20字節(jié)
@表示第一個(gè)參數(shù)是id類型磅轻,0表示第一個(gè)參數(shù)從第0個(gè)字節(jié)開(kāi)始
:表示第二個(gè)參數(shù)是SEL類型珍逸。8表示第二個(gè)參數(shù)從第8個(gè)字節(jié)開(kāi)始。
i表示第三個(gè)參數(shù)是int類型聋溜,16表示第三個(gè)參數(shù)從第16個(gè)字節(jié)開(kāi)始
第三個(gè)參數(shù)從第16個(gè)字節(jié)開(kāi)始谆膳,是Int類型,占用4字節(jié)勤婚∧×浚總共20字節(jié)

4. 方法緩存

用散列表來(lái)緩存曾經(jīng)調(diào)用過(guò)的方法,可以提高方法的查找速度馒胆。
結(jié)構(gòu)體 cache_t

struct cache_t {
    struct bucket_t *_buckets; // 散列表
    mask_t _mask; //散列表的長(zhǎng)度 -1
    mask_t _occupied; //已經(jīng)緩存的方法數(shù)量
}

// 其中的 散列表
struct bucket_t {
    MethodCacheIMP _imp; //函數(shù)的內(nèi)存地址
    cache_key_t _key;   //SEL作為Key
}
  • cache_t中如何查找方法
// 散列表中查找方法緩存
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

其中缨称,根據(jù)key和散列表長(zhǎng)度減1 mask 計(jì)算出下標(biāo) key & mask,取出的值如果key和當(dāng)初傳進(jìn)來(lái)的Key相同祝迂,就說(shuō)明找到了睦尽。否則,就不是自己要找的方法型雳,就有了hash沖突当凡,把i的值加1山害,繼續(xù)計(jì)算。如下代碼:

// 計(jì)算下標(biāo)
static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}


//hash沖突的時(shí)候
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
  • cache_t的擴(kuò)容
    當(dāng)方法緩存太多的時(shí)候沿量,超過(guò)了容量的3/4s時(shí)候浪慌,就需要擴(kuò)容了。擴(kuò)容是朴则,把原來(lái)的容量增加為2倍权纤。
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
            ...
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // 來(lái)到這里說(shuō)明,超過(guò)了3/4,需要擴(kuò)容
        cache->expand();
    }

         ...
}

// 擴(kuò)容
enum {
    INIT_CACHE_SIZE_LOG2 = 2,
    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2)
};

// cache_t的擴(kuò)容
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    // 擴(kuò)容為原來(lái)的2倍
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;

    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }

    reallocate(oldCapacity, newCapacity);
}

5. 消息轉(zhuǎn)發(fā)機(jī)制

OC方法調(diào)用的本質(zhì)是乌妒,消息轉(zhuǎn)發(fā)機(jī)制汹想。比如:
對(duì)象instance 調(diào)用dotest方法[instance1 dotest];
底層會(huì)轉(zhuǎn)化為:objc_msgSend(instance1, sel_registerName("dotest"));
OC中方法的調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用撤蚊。

實(shí)例對(duì)象中存放著 isa 指針以及實(shí)例變量古掏。由 isa 指針找到實(shí)例對(duì)象所屬的類對(duì)象 (類也是對(duì)象)。類中存放著實(shí)例方法列表侦啸。在這個(gè)列表中槽唾,方法的保存形式是SEL 作 key,IMP作value匹中。

這是在編譯時(shí)根據(jù)方法名夏漱,生成唯一標(biāo)識(shí)SELIMP其實(shí)就是函數(shù)指針 顶捷,指向最終的函數(shù)實(shí)現(xiàn)。

整個(gè) Runtime 的核心就是 objc_msgSend(receiver, @selector (message)) 函數(shù)屎篱,通過(guò)給類發(fā)送 SEL以傳遞消息服赎,找到匹配的IMP 再獲取最終的實(shí)現(xiàn)。

執(zhí)行流程可以分為3大階段:消息發(fā)送->動(dòng)態(tài)方法解析->消息轉(zhuǎn)發(fā)

  • 消息發(fā)送階段:
    首先判斷receiver是否為空
    如果不為空交播,從receiverClass的緩存中重虑,查找方法。(找到了就調(diào)用)
    如果沒(méi)找到秦士,就從receiverClass的class_rw_t中查找方法缺厉。(找到就調(diào)用,并緩存)
    如果沒(méi)找到隧土,就去receiverClassd的父類的緩存中查找提针。
    如果沒(méi)找到,就從父類的class_rw_t中查找方法曹傀。
    如果沒(méi)找到辐脖,就看是否還有父類,有就繼續(xù)查父類的緩存皆愉,方法列表嗜价。

由上述知道艇抠,去查緩存、方法列表久锥、查父類等這些操作之后家淤,都沒(méi)有找到這個(gè)方法的實(shí)現(xiàn),這時(shí)如果后面不做處理瑟由,必然拋出異常:
...due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[xxx xxxx]: unrecognized selector sent to instance 0x100f436c0’

如果沒(méi)有父類媒鼓,說(shuō)明消息發(fā)送階段結(jié)束,那么就進(jìn)入第二階段错妖,動(dòng)態(tài)方法解析階段绿鸣。

  • 動(dòng)態(tài)方法解析:
    在此,可以給未找到的方法暂氯,動(dòng)態(tài)綁定方法實(shí)現(xiàn)潮模。或者給某個(gè)方法重定向痴施。

源碼:

// 動(dòng)態(tài)方法解析
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) { //如果不是元類對(duì)象
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else { // 是元類對(duì)象
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

其中的resolveClassMethodresolveInstanceMethod默認(rèn)是返回NO

+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
  • 在動(dòng)態(tài)解析階段擎厢,可以重寫resolveInstanceMethod并添加方法的實(shí)現(xiàn)。
    假如辣吃,沒(méi)有找到run這個(gè)方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
     if (sel == @selector( run )) {
        // 獲取其他方法 實(shí)例方法 或類方法动遭,作為run的實(shí)現(xiàn)
        Method method = class_getInstanceMethod(self, @selector(test));

        // 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
                
       // 返回YES代表有動(dòng)態(tài)添加方法  其實(shí)這里返回NO,也是可以的神得,返回YES只是增加了一些打印
        return NO;
     }
     return [super resolveInstanceMethod:sel];
}

上面的代碼厘惦,就相當(dāng)于,調(diào)用run的時(shí)候哩簿,實(shí)際上調(diào)用的是test宵蕉。

如果前面消息發(fā)送 和動(dòng)態(tài)解析階段,都沒(méi)有對(duì)方法進(jìn)行處理节榜,我們還有最后一個(gè)階段羡玛。如下

  • 消息轉(zhuǎn)發(fā)

____forwarding___這個(gè)函數(shù)中,交代了消息轉(zhuǎn)發(fā)的邏輯宗苍。但是不開(kāi)源稼稿。

先判斷forwardingTargetForSelector的返回值。有讳窟,就向這個(gè)返回值發(fā)送消息让歼,讓它調(diào)用方法。
如果返回nil挪钓,就調(diào)用methodSignatureForSelector方法是越,有就調(diào)用forwardInvocation

其中的參數(shù)是一個(gè) NSInvocation 對(duì)象碌上,并將消息全部屬性記錄下來(lái)倚评。 NSInvocation對(duì)象包括了Selector浦徊、target以及其他參數(shù)。其中的實(shí)現(xiàn)僅僅是改變了 target指向天梧,使消息保證能夠調(diào)用盔性。

倘若發(fā)現(xiàn)本類無(wú)法處理,則繼續(xù)查找父類呢岗,直至 NSObject 冕香。如果methodSignatureForSelector方法返回nil,就調(diào)用doesNotRecognizeSelector:方法后豫。

應(yīng)用舉例:

場(chǎng)景1:

類Person只定義了方法run但沒(méi)有實(shí)現(xiàn)悉尾,另外有類Car實(shí)現(xiàn)了方法run。

現(xiàn)在Person中挫酿,重寫forwardingTargetForSelector返回Car對(duì)象

// 消息轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(run)) {
        return [[Car alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

這時(shí)构眯,當(dāng)person實(shí)例調(diào)用run方法時(shí),會(huì)變成car實(shí)例調(diào)用run方法早龟。

證明forwardingTargetForSelector返回值不為空的話惫霸,就向這個(gè)返回值發(fā)送消息,也就是objc_msgSend(返回值, SEL)葱弟。

場(chǎng)景2:

如果前面的forwardingTargetForSelector返回為空壹店。底層就會(huì)調(diào)用 methodSignatureForSelector獲取方法簽名后,再調(diào)用 forwardInvocation芝加。

因此:可以重寫這兩個(gè)方法:

// 方法簽名:返回值類型硅卢、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(run)) {
       return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
     return [super methodSignatureForSelector:aSelector];
}


- (void)forwardInvocation:(NSInvocation *)anInvocation{

   [anInvocation invokeWithTarget:[[Car alloc] init]];
}

這樣,依然可以調(diào)用到car的run方法妖混。

NSInvocation封裝了一個(gè)方法調(diào)用老赤,包括:方法調(diào)用者、方法名制市、方法參數(shù)
anInvocation.target 方法調(diào)用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]

補(bǔ)充:
1、消息轉(zhuǎn)發(fā)的forwardingTargetForSelector弊予、methodSignatureForSelector祥楣、forwardInvocation不僅支持實(shí)例方法,還支持類方法汉柒。不過(guò)系統(tǒng)沒(méi)有提示误褪,需要寫成實(shí)例方法,然后把前面的-改成+即可碾褂。

+(IMP)instanceMethodForSelector:(SEL)aSelector{
    
}

-(IMP)methodForSelector:(SEL)aSelector{
    
}

2兽间、只能向運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的類添加ivars,不能向已經(jīng)存在的類添加ivars正塌。 這是因?yàn)樵诰幾g時(shí)嘀略,只讀結(jié)構(gòu)體class_ro_t就被確定恤溶,在運(yùn)行時(shí)不可更改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帜羊,一起剝皮案震驚了整個(gè)濱河市咒程,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讼育,老刑警劉巖帐姻,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奶段,居然都是意外死亡饥瓷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門痹籍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呢铆,“玉大人,你說(shuō)我怎么就攤上這事词裤〈倘鳎” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵吼砂,是天一觀的道長(zhǎng)逆航。 經(jīng)常有香客問(wèn)我,道長(zhǎng)渔肩,這世上最難降的妖魔是什么因俐? 我笑而不...
    開(kāi)封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮周偎,結(jié)果婚禮上抹剩,老公的妹妹穿的比我還像新娘。我一直安慰自己蓉坎,他們只是感情好澳眷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蛉艾,像睡著了一般钳踊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勿侯,一...
    開(kāi)封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天拓瞪,我揣著相機(jī)與錄音,去河邊找鬼助琐。 笑死祭埂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兵钮。 我是一名探鬼主播蛆橡,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼舌界,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了航罗?” 一聲冷哼從身側(cè)響起禀横,我...
    開(kāi)封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粥血,沒(méi)想到半個(gè)月后柏锄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡复亏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年趾娃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缔御。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抬闷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耕突,到底是詐尸還是另有隱情笤成,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布眷茁,位于F島的核電站炕泳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏上祈。R本人自食惡果不足惜培遵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望登刺。 院中可真熱鬧籽腕,春花似錦、人聲如沸纸俭。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揍很。三九已至廊宪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間女轿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工壕翩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛉迹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓放妈,卻偏偏與公主長(zhǎng)得像北救,于是被迫代替她去往敵國(guó)和親荐操。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馏慨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 主要參考自:1诽表、iOS運(yùn)行時(shí)(Runtime)詳解+Demo2胳嘲、Objective-C Runtime3玫鸟、神經(jīng)病院...
    PierceDark閱讀 1,793評(píng)論 0 39
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門動(dòng)態(tài)語(yǔ)言躏啰,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢床嫌?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評(píng)論 0 7
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 763評(píng)論 0 1
  • 本文轉(zhuǎn)載自:http://southpeak.github.io/2014/10/25/objective-c-r...
    idiot_lin閱讀 935評(píng)論 0 4
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)护锤,它使得 Objective-C 如虎添翼送丰,具備了靈活的...
    lylaut閱讀 800評(píng)論 0 4