Objective-C runtime機(jī)制(3)——method swizzling

原文地址

方法替換淋肾,又稱(chēng)為method swizzling,是一個(gè)比較著名的runtime黑魔法龄恋。網(wǎng)上有很多的實(shí)現(xiàn)凶伙,我們這里直接講最正規(guī)的實(shí)現(xiàn)方式以及其背后的原理郭毕。

Method Swizzling

在進(jìn)行方法替換前,我們要考慮兩種情況:

  • 要替換的方法在target class中有實(shí)現(xiàn)
  • 要替換的方法在target class中沒(méi)有實(shí)現(xiàn)铣卡,而是在其父類(lèi)中實(shí)現(xiàn)

對(duì)于第一種情況偏竟,很簡(jiǎn)單,我們直接調(diào)用method_exchangeImplementations即可達(dá)成方法敞峭。

而對(duì)于第二種情況踊谋,我們要仔細(xì)想想了旋讹。
因?yàn)樵趖arget class中沒(méi)有對(duì)應(yīng)的方法實(shí)現(xiàn),方法實(shí)際上是在target class的父類(lèi)中實(shí)現(xiàn)的睦疫,因此當(dāng)我們要交換方法實(shí)現(xiàn)時(shí)鞭呕,其實(shí)是交換了target class父類(lèi)的實(shí)現(xiàn)。這樣當(dāng)其他地方調(diào)用這個(gè)父類(lèi)的方法時(shí)瓦糕,也會(huì)調(diào)用我們所替換的方法,這顯然使我們不想要的亥揖。

比如圣勒,我想替換UIViewController類(lèi)中的methodForSelector:方法,其實(shí)該方法是在其父類(lèi)NSObject類(lèi)中實(shí)現(xiàn)的胡控。如果我們直接調(diào)用method_exchangeImplementations旁趟,則會(huì)替換掉NSObject的方法锡搜。這樣當(dāng)我們?cè)趧e的地方,比如UITableView中再調(diào)用methodForSelector:方法時(shí)凡傅,其實(shí)會(huì)調(diào)用到父類(lèi)NSObject肠缔,而NSObject的實(shí)現(xiàn),已經(jīng)被我們替換了槽华。

為了避免這種情況趟妥,我們?cè)谶M(jìn)行方法替換前,需要檢查target class是否有對(duì)應(yīng)方法的實(shí)現(xiàn)亲雪,如果沒(méi)有疚膊,則要講方法動(dòng)態(tài)的添加到class的method list中。

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要特別注意你替換的方法到底是哪個(gè)性質(zhì)的方法
        // When swizzling a Instance method, use the following:
                Class class = [self class];

        // When swizzling a class method, use the following:
       // Class class = object_getClass((id)self);

        SEL originalSelector = @selector(systemMethod_PrintLog);
        SEL swizzledSelector = @selector(ll_imageName);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

這是網(wǎng)上的一段代碼例子灌砖,比較工整周崭。

這里我們用class_addMethod方法來(lái)檢查target class是否有方法實(shí)現(xiàn)。如果target class沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)方法的話美澳,則class_addMethod會(huì)返回true摸航,同時(shí),會(huì)將方法添加到target class中酱虎。如果target class已經(jīng)有對(duì)應(yīng)的方法實(shí)現(xiàn)的話读串,則class_addMethod調(diào)用失敗,返回false排监,這時(shí)杰捂,我們直接調(diào)用
method_exchangeImplementations方法來(lái)對(duì)調(diào)originalMethod和swizzledMethod即可。

這里有兩個(gè)細(xì)節(jié)挨队,一個(gè)是在class_addMethod方法中蒿往,我們傳入的SEL是originalSelector熄浓,而實(shí)現(xiàn)是swizzledMethodIMP省撑,這樣就等同于調(diào)換了方法。當(dāng)add method成功后娃惯,我們又調(diào)用

if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
 } 

class_replaceMethod方法其實(shí)在內(nèi)部會(huì)首先嘗試調(diào)用class_addMethod趾浅,將方法添加到class中,如果添加失敗浅侨,則說(shuō)明class已經(jīng)存在該方法证膨,這時(shí)央勒,會(huì)調(diào)用method_setImplementation來(lái)設(shè)置方法的IMP。

在if (didAddMethod)中稳吮,我們將swizzledMethod的IMP設(shè)置為了originalMethodIMP井濒,完成了方法交換瑞你。

第二個(gè)細(xì)節(jié)是這段注釋?zhuān)?/p>

+(void)load {
//要特別注意你替換的方法到底是哪個(gè)性質(zhì)的方法
        // When swizzling a Instance method, use the following:
                Class class = [self class];

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
...
}

結(jié)合+(void)load方法的調(diào)用時(shí)機(jī),它是由runtime在將class加載入內(nèi)存中所調(diào)用的類(lèi)方法撞蚕。因此过牙,我們一般會(huì)在這里面進(jìn)行方法交換寇钉,因?yàn)闀r(shí)機(jī)是很靠前的。

這里要注意谦秧,在類(lèi)方法中撵溃,self是一個(gè)類(lèi)對(duì)象而不是實(shí)例對(duì)象缘挑。
當(dāng)我們要替換類(lèi)方法時(shí),其實(shí)是要替換類(lèi)對(duì)象所對(duì)應(yīng)元類(lèi)中的方法诲宇,要獲取類(lèi)對(duì)象的元類(lèi),需要調(diào)用
object_getClass方法鹅心,它會(huì)返回ISA()纺荧,而類(lèi)對(duì)象的ISA(),恰好是元類(lèi)虐秋。

當(dāng)我們要替換實(shí)例方法時(shí),需要找到實(shí)例所對(duì)應(yīng)的類(lèi)用押,這時(shí)靶剑,就需要調(diào)用[self class]桩引,雖然self是類(lèi)對(duì)象,但是+ class會(huì)返回類(lèi)對(duì)象自身血崭,也就是實(shí)例對(duì)象所對(duì)應(yīng)的類(lèi)厘灼。

這段話說(shuō)的比較繞设凹,如果模糊的同學(xué)可以結(jié)合上一章最后類(lèi),類(lèi)和元類(lèi)的關(guān)系進(jìn)行理解月匣。

附帶class方法的實(shí)現(xiàn)源碼:

NSObject.mm

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Method swizzling原理

就如之前所說(shuō)锄开,runtime中所謂的黑魔法胀蛮,只不過(guò)是基于runtime底層數(shù)據(jù)結(jié)構(gòu)的應(yīng)用而已糯钙。
現(xiàn)在,我們就一次剖析在method swizzling中所用到的runtime函數(shù)以及其背后實(shí)現(xiàn)和所依賴(lài)的數(shù)據(jù)結(jié)構(gòu)狡刘。

class & object_getClass

要進(jìn)行方法替換困鸥,首先要清楚我們要替換哪個(gè)類(lèi)中的方法疾就,即target class:

// When swizzling a Instance method, use the following:
        Class class = [self class];

// When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

我們有兩種方式獲取Class對(duì)象,NSObject的class方法以及runtime函數(shù)object_getClass鸟废。這兩種方法的具體實(shí)現(xiàn)姑荷,還是有差別的鼠冕。

class

先看NSObject的方法class,其實(shí)有兩個(gè)版本计露,一個(gè)是實(shí)例方法薄坏,一個(gè)是類(lèi)方法寨闹,其源碼如下:

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

當(dāng)調(diào)用者是類(lèi)對(duì)象時(shí),會(huì)調(diào)用類(lèi)方法版本沈善,返回類(lèi)對(duì)象自身闻牡。而調(diào)用者是實(shí)例對(duì)象時(shí)绳矩,會(huì)調(diào)用實(shí)例方法版本翼馆,在該版本中金度,又會(huì)調(diào)用runtime方法object_getClass猜极。
那么在object_getClass中消玄,又做了什么呢?

object_getClass

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

實(shí)現(xiàn)很簡(jiǎn)單受扳,就是調(diào)用了對(duì)象的getIsa()方法辞色。這里我們可以簡(jiǎn)單的理解為就是返回了對(duì)象的isa指針浮定。
如果對(duì)象是實(shí)例對(duì)象桦卒,isa返回實(shí)例對(duì)象所對(duì)應(yīng)的類(lèi)對(duì)象。
如果對(duì)象是類(lèi)對(duì)象建蹄,isa返回類(lèi)對(duì)象所對(duì)應(yīng)的元類(lèi)對(duì)象洞慎。
我們?cè)诨剡^(guò)頭來(lái)看這段注釋?zhuān)ㄗ⒁膺@里的前提是在+load()方法中嘿棘,self是類(lèi)對(duì)象):

// When swizzling a Instance method, use the following:
        Class class = [self class];

// When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

當(dāng)我們要調(diào)換實(shí)例方法鸟妙,則需要修改實(shí)例對(duì)象所對(duì)應(yīng)的類(lèi)對(duì)象的方法列表,因?yàn)檫@里的self已經(jīng)是一個(gè)類(lèi)對(duì)象花椭,所有調(diào)用class方法其實(shí)會(huì)返回其自身矿辽,即實(shí)例對(duì)象對(duì)應(yīng)的類(lèi)對(duì)象:

// When swizzling a Instance method, use the following:
        Class class = [self class];

當(dāng)我們要調(diào)換類(lèi)方法,則需要修改類(lèi)對(duì)象所對(duì)應(yīng)的元類(lèi)對(duì)象的方法列表雕蔽,因此要調(diào)用object_class方法萎羔,它會(huì)返回對(duì)象的isa碳默,而類(lèi)對(duì)象的isa嘱根,則恰是類(lèi)對(duì)象對(duì)應(yīng)的元類(lèi)對(duì)象:

// When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

class_getInstanceMethod

確認(rèn)了class后巷懈,我們就需要準(zhǔn)備方法調(diào)用的原材料:originalMethod method 和 swizzled method顶燕。Method數(shù)據(jù)類(lèi)型在runtime中的定義為:

typedef struct method_t *Method;

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

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

我們所說(shuō)的類(lèi)的方法列表中涌攻,就是存儲(chǔ)的method_t類(lèi)型。

Method數(shù)據(jù)類(lèi)型的實(shí)例芝此,如果自己創(chuàng)建的話因痛,會(huì)比較麻煩鸵膏,尤其是如何填充IMP,但我們可以從現(xiàn)有的class 方法列表中取出一個(gè)method來(lái)用僧。很簡(jiǎn)單责循,只需要調(diào)用class_getInstanceMethod方法攀操。

class_getInstanceMethod方法究竟做了什么呢?就像我們剛才說(shuō)的一樣歹垫,它就是在指定的類(lèi)對(duì)象中的方法列表中去取SEL所對(duì)應(yīng)的Method排惨。


/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;        
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
    return _class_getMethod(cls, sel);
}

class_getInstanceMethod首先調(diào)用了lookUpImpOrNil暮芭,其實(shí)它的內(nèi)部實(shí)現(xiàn)和普通的消息流程是一樣的(內(nèi)部會(huì)調(diào)用上一章中說(shuō)所的消息查找函數(shù)lookUpImpOrForward),只不過(guò)對(duì)于消息轉(zhuǎn)發(fā)得到的IMP畜晰,會(huì)替換為nil凄鼻。

在進(jìn)行了一波消息流程之后聚假,調(diào)用_class_getMethod方法

static Method _class_getMethod(Class cls, SEL sel)
{
    rwlock_reader_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}

static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    // 核心:沿著繼承鏈膘格,向上查找第一個(gè)SEL所對(duì)應(yīng)的method
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}

// getMethodNoSuper_nolock 方法實(shí)質(zhì)就是在查找class的消息列表
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

class_addMethod

當(dāng)我們獲取到target class和swizzled method后闯袒,首先嘗試調(diào)用class_addMethod方法將swizzled method添加到target class中。

這樣做的目的在于:如果target class中沒(méi)有要替換的original method其徙,則會(huì)直接將swizzled method 作為original method的實(shí)現(xiàn)添加到target class中唾那。如果target class中確實(shí)存在original method褪尝,則class_addMethod會(huì)失敗并返回false河哑,我們就可以直接調(diào)用method_exchangeImplementations 方法來(lái)實(shí)現(xiàn)方法替換。這就是下面一段邏輯代碼的意義:

BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }

我們先來(lái)看class_addMethod是怎么實(shí)現(xiàn)的鲤妥。其實(shí)到了這里拱雏,相信大家不用看代碼也能猜的出來(lái)铸抑,class_addMethod其實(shí)就是將我們提供的method,插入到target class的方法列表中蒲赂。事實(shí)是這樣的嗎柒昏,看源碼:

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    rwlock_writer_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertWriting();

    assert(types);
    assert(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 方法已經(jīng)存在
        if (!replace) { // 如果選擇不替換职祷,則返回原始的方法有梆,添加方法失敗
            result = m->imp;
        } else {  // 如果選擇替換意系,則返回原始方法蛔添,同時(shí),替換為新的方法
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 方法不存在, 則在class的方法列表中添加方法, 并返回nil
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

源碼證明,我們的猜想是正確的:)

class_replaceMethod

如果class_addMethod返回成功凶硅,則說(shuō)明我們已經(jīng)為target class添加上了SEL為original SEL足绅,并且其實(shí)現(xiàn)是swizzled method。至此粹污,我們方法交換完成了一半壮吩,現(xiàn)在我們將swizzled method替換為original method。

 if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
 } 

這里琅捏,我們調(diào)用了class_replaceMethod 方法柄延。它的內(nèi)部邏輯是這樣的:1. 如果target class中沒(méi)有SEL的對(duì)應(yīng)實(shí)現(xiàn)缀程,則會(huì)為target class添加上對(duì)應(yīng)實(shí)現(xiàn)杨凑。 2. 如果target class中已經(jīng)有了SEL對(duì)應(yīng)的方法撩满,則會(huì)將SEL對(duì)應(yīng)的原始IMP,替換為新的IMP昭躺。

IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    rwlock_writer_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertWriting();

    assert(types);
    assert(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 方法已經(jīng)存在
        if (!replace) { // 如果選擇不替換领炫,則返回原始的方法张咳,添加方法失敗
            result = m->imp;
        } else {  // 如果選擇替換脚猾,則返回原始方法婚陪,同時(shí),替換為新的方法
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 方法不存在, 則在class的方法列表中添加方法, 并返回nil
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

通過(guò)源碼對(duì)比可以發(fā)現(xiàn),class_addMethod和class_replaceMethod其實(shí)都是調(diào)用的addMethod方法沽一,區(qū)別只是bool replace參數(shù)铣缠,一個(gè)是NO昆禽,不會(huì)替換原始實(shí)現(xiàn)醉鳖,另一個(gè)是YES哮内,會(huì)替換原始實(shí)現(xiàn)北发。

method_exchangeImplementations

如果class_addMethod 失敗,則說(shuō)明target class中的original method是在target class中有定義的瞭恰,這時(shí)候惊畏,我們直接調(diào)用method_exchangeImplementations交換實(shí)現(xiàn)即可陕截。method_exchangeImplementations 實(shí)現(xiàn)很簡(jiǎn)單批什,就是交換兩個(gè)Method的IMP:

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    rwlock_writer_t lock(runtimeLock);

    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil);

    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}

值得注意的地方

在寫(xiě)這篇博文的時(shí)候驻债,筆者曾做過(guò)這個(gè)實(shí)驗(yàn),在UIViewController的Category中形葬,測(cè)試

- (void)exchangeImp {
    Class aClass = object_getClass(self);
    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(sw_viewWillAppearXXX:);

    Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
    IMP result = class_replaceMethod(aClass, originalSelector,method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    NSLog(@"result is %p", result);
}

因?yàn)樵赾lass_replaceMethod方法中合呐,如果target class已經(jīng)存在SEL對(duì)應(yīng)的方法實(shí)現(xiàn),則會(huì)返回其old IMP笙以,并替換為new IMP淌实。本來(lái)以為result會(huì)返回viewWillAppear:的實(shí)現(xiàn),但結(jié)果卻是返回了nil猖腕。這是怎么回事呢拆祈?

究其根本,原來(lái)是因?yàn)槲沂窃赨IViewController的子類(lèi)ViewController中調(diào)用的exchangeImp方法放坏,那么object_getClass(self),其實(shí)會(huì)返回子類(lèi)ViewController而不是UIViewController老玛。

在class_replaceMethod中淤年,runtime僅會(huì)查找當(dāng)前類(lèi)aClass钧敞,即ViewController的方法列表,而不會(huì)向上查詢(xún)其父類(lèi)UIViewController的方法列表麸粮。這樣自然就找不到viewWillAppear:的實(shí)現(xiàn)啦溉苛。

而對(duì)于class_getInstanceMethod,runtime除了查找當(dāng)前類(lèi)弄诲,還會(huì)沿著繼承鏈向上查找對(duì)應(yīng)的Method炊昆。

所以,這里就造成了威根,class_getInstanceMethod可以得到viewWillAppear:對(duì)應(yīng)的Method凤巨,而在class_replaceMethod中,卻找不到viewWillAppear:對(duì)應(yīng)的IMP洛搀。

如果不了解背后的實(shí)現(xiàn)敢茁,確實(shí)很難理解這種看似矛盾的結(jié)果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末留美,一起剝皮案震驚了整個(gè)濱河市彰檬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谎砾,老刑警劉巖逢倍,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異景图,居然都是意外死亡较雕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)挚币,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亮蒋,“玉大人,你說(shuō)我怎么就攤上這事妆毕∩骶粒” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵笛粘,是天一觀的道長(zhǎng)趁怔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)薪前,這世上最難降的妖魔是什么润努? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮序六,結(jié)果婚禮上任连,老公的妹妹穿的比我還像新娘。我一直安慰自己例诀,他們只是感情好随抠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布裁着。 她就那樣靜靜地躺著,像睡著了一般拱她。 火紅的嫁衣襯著肌膚如雪二驰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,821評(píng)論 1 314
  • 那天秉沼,我揣著相機(jī)與錄音桶雀,去河邊找鬼。 笑死唬复,一個(gè)胖子當(dāng)著我的面吹牛矗积,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敞咧,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼棘捣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了休建?” 一聲冷哼從身側(cè)響起乍恐,我...
    開(kāi)封第一講書(shū)人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎测砂,沒(méi)想到半個(gè)月后茵烈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砌些,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年呜投,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寄症。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宙彪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出有巧,到底是詐尸還是另有隱情,我是刑警寧澤悲没,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布篮迎,位于F島的核電站,受9級(jí)特大地震影響示姿,放射性物質(zhì)發(fā)生泄漏甜橱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一栈戳、第九天 我趴在偏房一處隱蔽的房頂上張望岂傲。 院中可真熱鬧,春花似錦子檀、人聲如沸镊掖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亩进。三九已至症虑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間归薛,已是汗流浹背谍憔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留主籍,地道東北人习贫。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像千元,于是被迫代替她去往敵國(guó)和親沈条。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

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

  • 方法替換诅炉,又稱(chēng)為method swizzling蜡歹,是一個(gè)比較著名的runtime黑魔法。網(wǎng)上有很多的實(shí)現(xiàn)涕烧,我們這里...
    無(wú)忘無(wú)往閱讀 395評(píng)論 0 1
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言月而,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,199評(píng)論 0 7
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)议纯,它使得 Objective-C 如虎添翼父款,具備了靈活的...
    lylaut閱讀 806評(píng)論 0 4
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過(guò)整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 928評(píng)論 0 6
  • 官方源碼下載地址:http://download.csdn.net/detail/liangliang103377...
    有一種再見(jiàn)叫青春閱讀 1,981評(píng)論 2 11