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

方法替換虱肄,又稱為method swizzling滩愁,是一個(gè)比較著名的runtime黑魔法。網(wǎng)上有很多的實(shí)現(xiàn)瞧筛,我們這里直接講最正規(guī)的實(shí)現(xiàn)方式以及其背后的原理。

Method Swizzling

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

  1. 要替換的方法在target class中有實(shí)現(xiàn)
  2. 要替換的方法在target class中沒有實(shí)現(xiàn)较幌,而是在其父類中實(shí)現(xiàn)

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

而對(duì)于第二種情況,我們要仔細(xì)想想了滤馍。
因?yàn)樵趖arget class中沒有對(duì)應(yīng)的方法實(shí)現(xiàn)岛琼,方法實(shí)際上是在target class的父類中實(shí)現(xiàn)的,<font color=red>因此當(dāng)我們要交換方法實(shí)現(xiàn)時(shí)巢株,其實(shí)是交換了target class父類的實(shí)現(xiàn)</font>衷恭。這樣當(dāng)其他地方調(diào)用這個(gè)父類的方法時(shí),也會(huì)調(diào)用我們所替換的方法纯续,這顯然使我們不想要的随珠。

比如,我想替換UIViewController類中的methodForSelector:方法猬错,其實(shí)該方法是在其父類NSObject類中實(shí)現(xiàn)的窗看。如果我們直接調(diào)用method_exchangeImplementations,則會(huì)替換掉NSObject的方法倦炒。這樣當(dāng)我們?cè)趧e的地方显沈,比如UITableView中再調(diào)用methodForSelector:方法時(shí),其實(shí)會(huì)調(diào)用到父類NSObject逢唤,而NSObject的實(shí)現(xiàn)拉讯,已經(jīng)被我們替換了。

為了避免這種情況鳖藕,我們?cè)谶M(jìn)行方法替換前魔慷,需要檢查target class是否有對(duì)應(yīng)方法的實(shí)現(xiàn),如果沒有著恩,則要講方法動(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);
        }
    });
}

作者:春田花花幼兒園
鏈接:http://www.reibang.com/p/a6b675f4d073
來源:簡(jiǎn)書
著作權(quán)歸作者所有蜻展。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處邀摆。

這是網(wǎng)上的一段代碼例子纵顾,比較工整。

來解釋一下:
這里我們用class_addMethod方法來檢查target class是否有方法實(shí)現(xiàn)栋盹。如果target class沒有實(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方法來對(duì)調(diào)originalMethodswizzledMethod即可件余。

這里有兩個(gè)細(xì)節(jié),一個(gè)是在class_addMethod方法中遭居,我們傳入的SEL是originalSelector啼器,而實(shí)現(xiàn)是swizzledMethod IMP,這樣就等同于調(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中枪蘑,如果添加失敗损谦,則說明class已經(jīng)存在該方法,這時(shí)岳颇,會(huì)調(diào)用method_setImplementation來設(shè)置方法的IMP照捡。

if (didAddMethod) 中,我們將swizzledMethod的IMP設(shè)置為了originalMethod IMP话侧,完成了方法交換栗精。

第二個(gè)細(xì)節(jié)是這段注釋:

+(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)用的類方法瞻鹏。因此悲立,我們一般會(huì)在這里面進(jìn)行方法交換,因?yàn)闀r(shí)機(jī)是很靠前的新博。

這里要注意薪夕,在類方法中,self是一個(gè)類對(duì)象而不是實(shí)例對(duì)象赫悄。

當(dāng)我們要替換類方法時(shí)寥殖,其實(shí)是要替換類對(duì)象所對(duì)應(yīng)元類中的方法玩讳,要獲取類對(duì)象的元類,需要調(diào)用
object_getClass方法嚼贡,它會(huì)返回ISA()熏纯,而類對(duì)象ISA(),恰好是元類

當(dāng)我們要替換實(shí)例方法時(shí)粤策,需要找到實(shí)例所對(duì)應(yīng)的類樟澜,這時(shí),就需要調(diào)用[self class]叮盘,雖然self類對(duì)象秩贰,但是+ class會(huì)返回類對(duì)象自身,也就是實(shí)例對(duì)象所對(duì)應(yīng)的類柔吼。

這段話說的比較繞毒费,如果模糊的同學(xué)可以結(jié)合上一章最后類,元類的關(guān)系進(jìn)行理解愈魏。

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

NSObject.mm

+ (Class)class {
    return self;
}

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

Method swizzling原理

就如之前所說觅玻,<font color=blue>runtime中所謂的黑魔法,只不過是基于runtime底層數(shù)據(jù)結(jié)構(gòu)的應(yīng)用而已培漏。</font>

現(xiàn)在溪厘,我們就一次剖析在method swizzling中所用到的runtime函數(shù)以及其背后實(shí)現(xiàn)和所依賴的數(shù)據(jù)結(jié)構(gòu)。

class & object_getClass

要進(jìn)行方法替換牌柄,首先要清楚我們要替換哪個(gè)類中的方法畸悬,即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ì)象,NSObjectclass方法以及runtime函數(shù)object_getClass珊佣。這兩種方法的具體實(shí)現(xiàn)蹋宦,還是有差別的。

class

先看NSObject的方法class咒锻,其實(shí)有兩個(gè)版本妆档,一個(gè)是實(shí)例方法,一個(gè)是類方法虫碉,其源碼如下:

+ (Class)class {
    return self;
}

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

當(dāng)調(diào)用者是類對(duì)象時(shí)贾惦,會(huì)調(diào)用類方法版本,返回類對(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)的類對(duì)象牍氛。
如果對(duì)象是類對(duì)象isa返回類對(duì)象所對(duì)應(yīng)的元類對(duì)象烟阐。

我們?cè)诨剡^頭來看這段注釋(注意這里的前提是在+load()方法中搬俊,self類對(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)換<font color=orange>實(shí)例方法</font>,則需要修改實(shí)例對(duì)象所對(duì)應(yīng)的類對(duì)象的方法列表蜒茄,因?yàn)檫@里的self已經(jīng)是一個(gè)類對(duì)象唉擂,所有調(diào)用class方法其實(shí)會(huì)返回其自身,即實(shí)例對(duì)象對(duì)應(yīng)的類對(duì)象

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

當(dāng)我們要調(diào)換<font color=orange>類方法</font>檀葛,則需要修改類對(duì)象所對(duì)應(yīng)的元類對(duì)象的方法列表玩祟,因此要調(diào)用object_class方法,它會(huì)返回對(duì)象的isa屿聋,而類對(duì)象isa空扎,則恰是類對(duì)象對(duì)應(yīng)的元類對(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 methodswizzled method胜臊。Method數(shù)據(jù)類型在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; }
    };
};

我們所說的<font color=blue>類的方法列表</font>中勺卢,就是存儲(chǔ)的method_t類型伙判。

Method數(shù)據(jù)類型的實(shí)例象对,如果自己創(chuàng)建的話,會(huì)比較麻煩宴抚,尤其是如何填充IMP勒魔,但我們可以從現(xiàn)有的class 方法列表中取出一個(gè)method來。很簡(jiǎn)單菇曲,只需要調(diào)用class_getInstanceMethod方法冠绢。

class_getInstanceMethod方法究竟做了什么呢?<font color=blue>就像我們剛才說的一樣常潮,它就是在指定的類對(duì)象中的方法列表中去取SEL所對(duì)應(yīng)的Method</font>弟胀。


/***********************************************************************
* 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)用上一章中說所的消息查找函數(shù)lookUpImpOrForward)喊式,只不過對(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 classswizzled method后,首先嘗試調(diào)用class_addMethod方法將swizzled method添加到target class中献联。

這樣做的目的在于:如果target class中沒有要替換的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 方法來實(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);
        }

我們先來看class_addMethod 是怎么實(shí)現(xiàn)的。其實(shí)到了這里龄减,相信大家不用看代碼也能猜的出來项钮,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返回成功,則說明我們已經(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中沒有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;
}

通過源碼對(duì)比可以發(fā)現(xiàn),class_addMethodclass_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 失敗,則說明target class中的original method是在target class中有定義的笋除,這時(shí)候斜友,我們直接調(diào)用method_exchangeImplementations交換實(shí)現(xiàn)即可。method_exchangeImplementations 實(shí)現(xiàn)很簡(jiǎn)單垃它,就是交換兩個(gè)MethodIMP:


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

值得注意的地方

在寫這篇博文的時(shí)候鲜屏,筆者曾做過這個(gè)實(shí)驗(yàn),在UIViewControllerCategory中国拇,測(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)樵?code>class_replaceMethod方法中洛史,如果target class已經(jīng)存在SEL對(duì)應(yīng)的方法實(shí)現(xiàn),則會(huì)返回其old IMP酱吝,并替換為new IMP也殖。本來以為result會(huì)返回viewWillAppear:的實(shí)現(xiàn),但結(jié)果卻是返回了nil务热。這是怎么回事呢忆嗜?

究其根本,原來是因?yàn)槲沂窃?code>UIViewController的子類ViewController中調(diào)用的exchangeImp方法崎岂,那么object_getClass(self)捆毫,其實(shí)會(huì)返回子類ViewController而不是UIViewController

class_replaceMethod中冲甘,runtime僅會(huì)查找當(dāng)前類aClass绩卤,即ViewController的方法列表,而不會(huì)向上查詢其父類UIViewController的方法列表损合。這樣自然就找不到viewWillAppear:的實(shí)現(xiàn)啦省艳。

而對(duì)于class_getInstanceMethod娘纷,runtime除了查找當(dāng)前類嫁审,還會(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閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異甫窟,居然都是意外死亡密浑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門粗井,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尔破,“玉大人,你說我怎么就攤上這事浇衬±凉梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵耘擂,是天一觀的道長(zhǎng)痴脾。 經(jīng)常有香客問我,道長(zhǎng)梳星,這世上最難降的妖魔是什么赞赖? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮冤灾,結(jié)果婚禮上前域,老公的妹妹穿的比我還像新娘。我一直安慰自己韵吨,他們只是感情好匿垄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著归粉,像睡著了一般椿疗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糠悼,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天届榄,我揣著相機(jī)與錄音,去河邊找鬼倔喂。 笑死铝条,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的席噩。 我是一名探鬼主播班缰,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼悼枢!你這毒婦竟也來了埠忘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎莹妒,沒想到半個(gè)月后假丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡动羽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年包帚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片运吓。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渴邦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拘哨,到底是詐尸還是另有隱情谋梭,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布倦青,位于F島的核電站瓮床,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏产镐。R本人自食惡果不足惜隘庄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望癣亚。 院中可真熱鬧丑掺,春花似錦、人聲如沸述雾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玻孟。三九已至唆缴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間黍翎,已是汗流浹背面徽。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玩敏,地道東北人斗忌。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓质礼,卻偏偏與公主長(zhǎng)得像旺聚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眶蕉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉砰粹,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,721評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言,那么這個(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-c-r...
    F麥子閱讀 735評(píng)論 0 2
  • 官方源碼下載地址:http://download.csdn.net/detail/liangliang103377...
    有一種再見叫青春閱讀 1,978評(píng)論 2 11
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)碱璃,它使得 Objective-C 如虎添翼弄痹,具備了靈活的...
    lylaut閱讀 802評(píng)論 0 4