Method Swizzling的各種姿勢(shì)

轉(zhuǎn)載:http://www.cocoachina.com/ios/20161102/17920.html

因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzling這個(gè)黑魔法解決了我們實(shí)際開(kāi)發(fā)中諸多常規(guī)手段所無(wú)法解決的問(wèn)題, 比如代碼的插樁,Hook,Patch等等. 我們首先看看常規(guī)的Method Swizzling是怎樣用的, NSHipster有一篇介紹基本用法的文章Method Swizzling, 我們就先以這篇文章中的示例開(kāi)始說(shuō)起吧:

#import?@implementation?UIViewController?(Tracking)

+?(void)load?{

staticdispatch_once_t?onceToken;

dispatch_once(&onceToken,?^{

Classclass=?[selfclass];

SEL?originalSelector?=?@selector(viewWillAppear:);

SEL?swizzledSelector?=?@selector(xxx_viewWillAppear:);

Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);

BOOLdidAddMethod?=

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

}

});

}

#pragma?mark?-?Method?Swizzling

-?(void)xxx_viewWillAppear:(BOOL)animated?{

[self?xxx_viewWillAppear:animated];

NSLog(@"viewWillAppear:?%@",?self);

}

@end

簡(jiǎn)要說(shuō)明一下以上代碼的幾個(gè)重點(diǎn):

通過(guò)在Category的+ (void)load方法中添加Method Swizzling的代碼,在類(lèi)初始加載時(shí)自動(dòng)被調(diào)用,load方法按照父類(lèi)到子類(lèi),類(lèi)自身到Category的順序被調(diào)用.

在dispatch_once中執(zhí)行Method Swizzling是一種防護(hù)措施,以保證代碼塊只會(huì)被執(zhí)行一次并且線程安全,不過(guò)此處并不需要,因?yàn)楫?dāng)前Category中的load方法并不會(huì)被多次調(diào)用.

嘗試先調(diào)用class_addMethod方法,以保證即便originalSelector只在父類(lèi)中實(shí)現(xiàn),也能達(dá)到Method Swizzling的目的.

xxx_viewWillAppear:方法中[self xxx_viewWillAppear:animated];代碼并不會(huì)造成死循環(huán),因?yàn)镸ethod Swizzling之后,調(diào)用xxx_viewWillAppear:實(shí)際執(zhí)行的代碼已經(jīng)是原來(lái)viewWillAppear中的代碼了.

其實(shí)以上的代碼也可以簡(jiǎn)寫(xiě)為以下:

+?(void)load?{

Classclass=?[selfclass];

SEL?originalSelector?=?@selector(viewWillAppear:);

SEL?swizzledSelector?=?@selector(xxx_viewWillAppear:);

Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);

if(!originalMethod?||?!swizzledMethod)?{

return;

}

IMP?originalIMP?=?method_getImplementation(originalMethod);

IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);

constchar*originalType?=?method_getTypeEncoding(originalMethod);

constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);

//?這兒的先后順序是有講究的,如果先執(zhí)行后一句,那么在執(zhí)行完瞬間方法被調(diào)用容易引發(fā)死循環(huán)

class_replaceMethod(class,swizzledSelector,originalIMP,originalType);

class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);

}

這是因?yàn)閏lass_replaceMethod方法其實(shí)能夠覆蓋到class_addMethod和method_setImplementation兩種場(chǎng)景, 對(duì)于第一個(gè)class_replaceMethod來(lái)說(shuō), 如果viewWillAppear:實(shí)現(xiàn)在父類(lèi), 則執(zhí)行class_addMethod, 否則就執(zhí)行method_setImplementation將原方法的IMP指定新的代碼塊; 而第二個(gè)class_replaceMethod完成的工作便只是將新方法的IMP指向原來(lái)的代碼.

但此處需要特別注意交換的順序,應(yīng)該優(yōu)化把新的方法指定原IMP,再修改原有的方法的IMP.

除了以上的場(chǎng)景之外,其它場(chǎng)景下我們?nèi)绾问褂肕ethod Swizzling呢?

1.在不同類(lèi)之間實(shí)現(xiàn)Method Swizzling

上面示例是通過(guò)Category來(lái)新增一個(gè)方法然后實(shí)現(xiàn)Method Swizzling的, 但有一些場(chǎng)景可能并不適合使用Category(比如私有的類(lèi),未獲取到該類(lèi)的聲明), 此時(shí)我們應(yīng)該如何來(lái)做Method Swizzling呢?

例如已知一個(gè)className為Car的類(lèi)中有一個(gè)實(shí)例方法- (void)run:(double)speed, 目前需要Hook該方法對(duì)速度小于120才執(zhí)行run的代碼, 按照方法交換的流程, 代碼應(yīng)該是這樣的:

#import?@interface?MyCar?:?NSObject

@end

@implementation?MyCar

+?(void)load?{

Class?originalClass?=?NSClassFromString(@"Car");

Class?swizzledClass?=?[selfclass];

SEL?originalSelector?=?NSSelectorFromString(@"run:");

SEL?swizzledSelector?=?@selector(xxx_run:);

Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);

//?向Car類(lèi)中新添加一個(gè)xxx_run:的方法

BOOLregisterMethod?=?class_addMethod(originalClass,

swizzledSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

if(!registerMethod)?{

return;

}

//?需要更新swizzledMethod變量,獲取當(dāng)前Car類(lèi)中xxx_run:的Method指針

swizzledMethod?=?class_getInstanceMethod(originalClass,?swizzledSelector);

if(!swizzledMethod)?{

return;

}

//?后續(xù)流程與之前的一致

BOOLdidAddMethod?=?class_addMethod(originalClass,

originalSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

if(didAddMethod)?{

class_replaceMethod(originalClass,

swizzledSelector,

method_getImplementation(originalMethod),

method_getTypeEncoding(originalMethod));

}else{

method_exchangeImplementations(originalMethod,?swizzledMethod);

}

}

-?(void)xxx_run:(double)speed?{

if(speed?<?120)?{

[self?xxx_run:speed];

}

}

@end

與之前的流程相比,在前面添加了兩個(gè)邏輯:

利用runtime向目標(biāo)類(lèi)Car動(dòng)態(tài)添加了一個(gè)新的方法,此時(shí)Car類(lèi)與MyCar類(lèi)一樣具備了xxx_run:這個(gè)方法,MyCar的利用價(jià)值便結(jié)束了;

為了完成后續(xù)Car類(lèi)中run:與xxx_run:的方法交換,此時(shí)需要更新swizzledMethod變量為Car中的xxx_run:方法所對(duì)應(yīng)的Method.

以上所有的邏輯也可以合并簡(jiǎn)化為以下:

+?(void)load?{

Class?originalClass?=?NSClassFromString(@"Car");

Class?swizzledClass?=?[selfclass];

SEL?originalSelector?=?NSSelectorFromString(@"run:");

SEL?swizzledSelector?=?@selector(xxx_run:);

Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);

IMP?originalIMP?=?method_getImplementation(originalMethod);

IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);

constchar*originalType?=?method_getTypeEncoding(originalMethod);

constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);

class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);

class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);

}

簡(jiǎn)化后的代碼便與之前使用Category的方式并沒(méi)有什么差異, 這樣代碼就很容易覆蓋到這兩種場(chǎng)景了, 但我們需要明確此時(shí)class_replaceMethod所完成的工作卻是不一樣的.

第一個(gè)class_replaceMethod直接在Car類(lèi)中注冊(cè)了xxx_run:方法,并且指定的IMP為當(dāng)前run:方法的IMP;

第二個(gè)class_replaceMethod與之前的邏輯一致,當(dāng)run:方法是實(shí)現(xiàn)在Car類(lèi)或Car的父類(lèi),分別執(zhí)行method_setImplementation或class_addMethod;

2.如何實(shí)現(xiàn)類(lèi)方法的Method Swizzling

以上的代碼都是實(shí)現(xiàn)的對(duì)實(shí)例方法的交換, 那如何來(lái)實(shí)現(xiàn)對(duì)類(lèi)方法的交換呢, 依舊直接貼代碼吧:

@interface?NSDictionary?(Test)

@end

@implementation?NSDictionary?(Test)

+?(void)load?{

Class?cls?=?[selfclass];

SEL?originalSelector?=?@selector(dictionary);

SEL?swizzledSelector?=?@selector(xxx_dictionary);

//?使用class_getClassMethod來(lái)獲取類(lèi)方法的Method

Method?originalMethod?=?class_getClassMethod(cls,?originalSelector);

Method?swizzledMethod?=?class_getClassMethod(cls,?swizzledSelector);

if(!originalMethod?||?!swizzledMethod)?{

return;

}

IMP?originalIMP?=?method_getImplementation(originalMethod);

IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);

constchar*originalType?=?method_getTypeEncoding(originalMethod);

constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);

//?類(lèi)方法添加,需要將方法添加到MetaClass中

Class?metaClass?=?objc_getMetaClass(class_getName(cls));

class_replaceMethod(metaClass,swizzledSelector,originalIMP,originalType);

class_replaceMethod(metaClass,originalSelector,swizzledIMP,swizzledType);

}

+?(id)xxx_dictionary?{

id?result?=?[self?xxx_dictionary];

returnresult;

}

@end

相比實(shí)例方法的Method Swizzling,流程有兩點(diǎn)差異:

獲取Method的方法變更為class_getClassMethod(Class cls, SEL name),從函數(shù)命名便直觀體現(xiàn)了和class_getInstanceMethod(Class cls, SEL name)的差別;

對(duì)于類(lèi)方法的動(dòng)態(tài)添加,需要將方法添加到MetaClass中,因?yàn)閷?shí)例方法記錄在class的method-list中,類(lèi)方法是記錄在meta-class中的method-list中的.

3.在類(lèi)簇中如何實(shí)現(xiàn)Method Swizzling

在上面的代碼中我們實(shí)現(xiàn)了對(duì)NSDictionary中的+ (id)dictionary方法的交換,但如果我們用類(lèi)似代碼嘗試對(duì)- (id)objectForKey:(id)key方法進(jìn)行交換后, 你便會(huì)發(fā)現(xiàn)這似乎并沒(méi)有什么用.

這是為什么呢? 平常我們?cè)赬code調(diào)試時(shí),在下方Debug區(qū)域左側(cè)的Variables View中,常常會(huì)發(fā)現(xiàn)如__NSArrayI或是__NSCFConstantString這樣的Class類(lèi)型, 這便是在Foundation框架中被廣泛使用的類(lèi)簇, 詳情請(qǐng)參看Apple文檔class cluster的內(nèi)容.

所以針對(duì)類(lèi)簇的Method Swizzling問(wèn)題就轉(zhuǎn)變?yōu)槿绾螌?duì)這些類(lèi)簇中的私有類(lèi)做Method Swizzling, 在上面介紹的不同類(lèi)之間做Method Swizzling便已經(jīng)能解決該問(wèn)題, 下面一個(gè)簡(jiǎn)單的示例通過(guò)交換NSMutableDictionary的setObject:forKey:方法,讓調(diào)用這個(gè)方法時(shí)當(dāng)參數(shù)object或key為空的不會(huì)拋出異常:

@interface?MySafeDictionary?:?NSObject

@end

@implementation?MySafeDictionary

+?(void)load?{

Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");

Class?swizzledClass?=?[selfclass];

SEL?originalSelector?=?@selector(setObject:forKey:);

SEL?swizzledSelector?=?@selector(safe_setObject:forKey:);

Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);

IMP?originalIMP?=?method_getImplementation(originalMethod);

IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);

constchar*originalType?=?method_getTypeEncoding(originalMethod);

constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);

class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);

class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);

}

-?(void)safe_setObject:(id)anObject?forKey:(id)aKey?{

if(anObject?&&?aKey)?{

[self?safe_setObject:anObject?forKey:aKey];

}

elseif(aKey)?{

[(NSMutableDictionary?*)self?removeObjectForKey:aKey];

}

}

@end

4.在Method Swizzling之后如何恢復(fù)

使用了Method Swizzling的各種姿勢(shì)之后, 是否有考慮如何恢復(fù)到交換之前的現(xiàn)場(chǎng)呢?

一種方案就是通過(guò)一個(gè)開(kāi)關(guān)標(biāo)識(shí)符, 如果需要從邏輯上面恢復(fù)到交換之前, 就設(shè)置一下這個(gè)標(biāo)識(shí)符, 在實(shí)現(xiàn)中判定如果設(shè)定了該標(biāo)識(shí)符, 邏輯就直接調(diào)用原方法的實(shí)現(xiàn), 其它什么事兒也不干, 這是目前大多數(shù)代碼的實(shí)現(xiàn)方法, 當(dāng)然也是非常安全的方式, 只不過(guò)當(dāng)交換方法過(guò)多時(shí), 每一個(gè)交換的方法體中都需要增加這樣的邏輯, 并且也需要維護(hù)大量這些標(biāo)識(shí)符變量, 只是會(huì)覺(jué)得不夠優(yōu)雅, 所以此處也就不展開(kāi)詳細(xì)討論了.

那下面來(lái)討論一下有沒(méi)有更好的方案, 以上描述的Method Swizzling各種場(chǎng)景和處理的技巧, 但綜合總結(jié)之后最核心的其實(shí)也只做了兩件事情:

class_addMethod添加一個(gè)新的方法,可能是把其它類(lèi)中實(shí)現(xiàn)的方法添加到目標(biāo)類(lèi)中,也可能是把父類(lèi)實(shí)現(xiàn)的方法添加一份在子類(lèi)中,可能是添加的實(shí)例方法,也可能是添加的類(lèi)方法,總之就是添加了方法.

交換IMP交換方法的實(shí)現(xiàn)IMP,完成這個(gè)步驟除了使用method_exchangeImplementations這個(gè)方法外,也可以是調(diào)用了method_setImplementation方法來(lái)單獨(dú)修改某個(gè)方法的IMP,或者是采用在調(diào)用class_addMethod方法中設(shè)定了IMP而直接就完成了IMP的交換,總之就是對(duì)IMP的交換.

那我們來(lái)分別看一下這兩件事情是否都還能恢復(fù):

對(duì)于class_addMethod,我們首先想到的可能就是有沒(méi)有對(duì)應(yīng)的remove方法呢,在Objective-C 1.0的時(shí)候有class_removeMethods這個(gè)方法,不過(guò)在2.0的時(shí)候就已經(jīng)被禁用了,也就是蘋(píng)果并不推薦我們這樣做,想想似乎也是挺有道理的,本來(lái)runtime的接口看著就挺讓人心驚膽戰(zhàn)的,又是添加又是刪除總覺(jué)得會(huì)出岔子,所以只能放棄remove的想法,反正方法添加在那兒倒也沒(méi)什么太大的影響.

針對(duì)IMP的交換,在Method Swizzling時(shí)做的交換動(dòng)作,如果需要恢復(fù)其實(shí)要做的動(dòng)作還是交換回來(lái)罷了,所以是可以做到的,不過(guò)需要怎樣做呢?對(duì)于同一個(gè)類(lèi),同一個(gè)方法,可能會(huì)在不同的地方被多次做Method Swizzling,所以要回退某一次的Method Swizzling,我們就需要記錄下來(lái)這一次交換的時(shí)候是哪兩個(gè)IMP做了交換,恢復(fù)的時(shí)候再換回來(lái)即可.另一個(gè)問(wèn)題是如果已經(jīng)經(jīng)過(guò)多次交換,我們?cè)鯓诱业竭@兩個(gè)IMP所對(duì)應(yīng)的Mehod呢,還好runtime提供了一個(gè)class_copyMethodList方法,可以直接取出Method列表,然后我們就可以逐個(gè)遍歷找到IMP所對(duì)應(yīng)的Method了,下面是對(duì)上一個(gè)示例添加恢復(fù)之后實(shí)現(xiàn)的代碼邏輯:

#import?@interface?MySafeDictionary?:?NSObject

@end

staticNSLock?*kMySafeLock?=?nil;

staticIMP?kMySafeOriginalIMP?=?NULL;

staticIMP?kMySafeSwizzledIMP?=?NULL;

@implementation?MySafeDictionary

+?(void)swizzlling?{

staticdispatch_once_t?onceToken;

dispatch_once(&onceToken,?^{

kMySafeLock?=?[[NSLock?alloc]?init];

});

[kMySafeLock?lock];

do{

if(kMySafeOriginalIMP?||?kMySafeSwizzledIMP)break;

Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");

if(!originalClass)break;

Class?swizzledClass?=?[selfclass];

SEL?originalSelector?=?@selector(setObject:forKey:);

SEL?swizzledSelector?=?@selector(safe_setObject:forKey:);

Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);

if(!originalMethod?||?!swizzledMethod)break;

IMP?originalIMP?=?method_getImplementation(originalMethod);

IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);

constchar*originalType?=?method_getTypeEncoding(originalMethod);

constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);

kMySafeOriginalIMP?=?originalIMP;

kMySafeSwizzledIMP?=?swizzledIMP;

class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);

class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);

}while(NO);

[kMySafeLock?unlock];

}

+?(void)restore?{

[kMySafeLock?lock];

do{

if(!kMySafeOriginalIMP?||?!kMySafeSwizzledIMP)break;

Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");

if(!originalClass)break;

Method?originalMethod?=?NULL;

Method?swizzledMethod?=?NULL;

unsignedintoutCount?=?0;

Method?*methodList?=?class_copyMethodList(originalClass,?&outCount);

for(unsignedintidx=0;?idx?<?outCount;?idx++)?{

Method?aMethod?=?methodList[idx];

IMP?aIMP?=?method_getImplementation(aMethod);

if(aIMP?==?kMySafeSwizzledIMP)?{

originalMethod?=?aMethod;

}

elseif(aIMP?==?kMySafeOriginalIMP)?{

swizzledMethod?=?aMethod;

}

}

//?盡可能使用exchange,因?yàn)樗莂tomic的

if(originalMethod?&&?swizzledMethod)?{

method_exchangeImplementations(originalMethod,?swizzledMethod);

}

elseif(originalMethod)?{

method_setImplementation(originalMethod,?kMySafeOriginalIMP);

}

elseif(swizzledMethod)?{

method_setImplementation(swizzledMethod,?kMySafeSwizzledIMP);

}

kMySafeOriginalIMP?=?NULL;

kMySafeSwizzledIMP?=?NULL;

}while(NO);

[kMySafeLock?unlock];

}

-?(void)safe_setObject:(id)anObject?forKey:(id)aKey?{

if(anObject?&&?aKey)?{

[self?safe_setObject:anObject?forKey:aKey];

}

elseif(aKey)?{

[(NSMutableDictionary?*)self?removeObjectForKey:aKey];

}

}

@end

注意這段代碼的Method Swizzling和恢復(fù)都需要主動(dòng)調(diào)用, 并且相比上面其它的示例, 這段代碼還添加如鎖機(jī)制來(lái)加之保護(hù). 這個(gè)示例是以不同的類(lèi)來(lái)實(shí)現(xiàn)的Method Swizzling和恢復(fù), 如果是Category或者是類(lèi)方法, 根據(jù)之前的示例也需要做相應(yīng)的調(diào)整.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末免姿,一起剝皮案震驚了整個(gè)濱河市饼酿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胚膊,老刑警劉巖故俐,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異紊婉,居然都是意外死亡药版,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)喻犁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)槽片,“玉大人,你說(shuō)我怎么就攤上這事株汉】鹑椋” “怎么了歌殃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵乔妈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我氓皱,道長(zhǎng)路召,這世上最難降的妖魔是什么勃刨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮股淡,結(jié)果婚禮上身隐,老公的妹妹穿的比我還像新娘。我一直安慰自己唯灵,他們只是感情好贾铝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著埠帕,像睡著了一般垢揩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敛瓷,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天叁巨,我揣著相機(jī)與錄音,去河邊找鬼呐籽。 笑死锋勺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狡蝶。 我是一名探鬼主播庶橱,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牢酵!你這毒婦竟也來(lái)了悬包?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馍乙,失蹤者是張志新(化名)和其女友劉穎布近,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丝格,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撑瞧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了显蝌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片预伺。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曼尊,靈堂內(nèi)的尸體忽然破棺而出酬诀,到底是詐尸還是另有隱情,我是刑警寧澤骆撇,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布瞒御,位于F島的核電站,受9級(jí)特大地震影響神郊,放射性物質(zhì)發(fā)生泄漏肴裙。R本人自食惡果不足惜趾唱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜻懦。 院中可真熱鬧甜癞,春花似錦、人聲如沸宛乃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)征炼。三九已至乔煞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柒室,已是汗流浹背渡贾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雄右,地道東北人空骚。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琅捏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,725評(píng)論 0 9
  • 不知道從哪里復(fù)制的~肋坚!非原創(chuàng)就是了?? 因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzl...
    Bearger閱讀 487評(píng)論 0 0
  • 因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzling這個(gè)黑魔法解決了我們實(shí)際開(kāi)發(fā)中諸...
    SuAdrenine閱讀 508評(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
  • Method Swizzling參考資料 1.用到的運(yùn)行時(shí)基礎(chǔ)知識(shí)介紹 SEL : 方法選擇器,SEL是函數(shù)ob...
    shannoon閱讀 1,370評(píng)論 0 7