消息轉(zhuǎn)發(fā)機(jī)制與Aspects源碼解析

前言

最近在搞重構(gòu)相關(guān)的事情,遇到了不少這樣的場(chǎng)景:

進(jìn)入一個(gè)界面,在viewWillAppear:的時(shí)候做相應(yīng)判斷,如果滿足條件則執(zhí)行對(duì)應(yīng)代碼锈嫩。

這類業(yè)務(wù)有一個(gè)特點(diǎn),業(yè)務(wù)內(nèi)容是對(duì)應(yīng)整個(gè)App的垦搬,與對(duì)應(yīng)的ViewController毛關(guān)系都沒(méi)有呼寸,但是卻不得不耦合到(即使是調(diào)用代碼可以精簡(jiǎn)到一行)ViewController中。

我們都知道悼沿,這種類似的業(yè)務(wù)用AOP(面向切片編程)來(lái)做十分適合等舔,所謂面向切片編程就是在不修改原方法的前提下,動(dòng)態(tài)的插入自己的想要的執(zhí)行代碼糟趾,由于Objective C是動(dòng)態(tài)語(yǔ)言慌植,可以很容易的利用method swizzling來(lái)實(shí)現(xiàn)AOP。

在正文之前特別感謝微信閱讀團(tuán)隊(duì)的這篇博客:

面向切面編程之 Aspects 源碼解析及應(yīng)用

這篇博客原理上講解的比較清楚义郑,但是細(xì)節(jié)上并沒(méi)有講的很詳細(xì)蝶柿,所以也就有了本文。

Objective C方法調(diào)用過(guò)程

這個(gè)其實(shí)我之前在這篇博客里講過(guò):

iOS Runtime詳解(消息機(jī)制非驮,類元對(duì)象交汤,緩存機(jī)制,消息轉(zhuǎn)發(fā))

這里劫笙,把核心的內(nèi)容再一次列出來(lái)芙扎。

如下Objective C代碼

-?(NSInteger?)myTestFunction:(NSInteger)input{

????return?input?+?1;

}

-?(void)mySpecialFunction{

???NSInteger?result?=??[self?myTestFunction:10];

}

用clang來(lái)重寫為C++,

clang?-rewrite-objc??MyClass.m

然后,我們通過(guò)搜索mySpecialFunction方法名字填大,來(lái)找到轉(zhuǎn)換后的代碼戒洼,經(jīng)過(guò)簡(jiǎn)單整理如下

static?NSInteger?_I_MyClass_myTestFunction_(MyClass?*?self,?SEL?_cmd,?NSInteger?input)?{

????return?input?+?1;

}

static?void?_I_MyClass_mySpecialFunction(MyClass?*?self,?SEL?_cmd)?{

???NSInteger?result?=?objc_msgSend(self,?sel_registerName("myTestFunction:"),10);

}

我們看到,方法體進(jìn)行了如下轉(zhuǎn)換

//OC

-?(NSInteger?)myTestFunction:(NSInteger)input{

????return?input?+?1;

}

//C++

static?NSInteger?_I_MyClass_myTestFunction_(MyClass?*?self,?SEL?_cmd,?NSInteger?input)?{

????return?input?+?1;

}

方法調(diào)用進(jìn)行了如下轉(zhuǎn)換

//OC

NSInteger?result?=??[self?myTestFunction:10];

//C++

NSInteger?result?=?objc_msgSend(self,?sel_registerName("myTestFunction:"),10);

不難看出允华,方法的調(diào)用并不是直接轉(zhuǎn)換成了對(duì)應(yīng)的C/C++方法調(diào)用圈浇,而是調(diào)用了objc_msgSend通過(guò)SEL(就是一個(gè)字符串)在運(yùn)行時(shí)動(dòng)態(tài)找到這個(gè)的執(zhí)行體_I_MyClass_myTestFunction_。

那么靴寂,在運(yùn)行時(shí)如何找到這個(gè)方法的執(zhí)行體呢磷蜀? 這里省略一些細(xì)節(jié),對(duì)細(xì)節(jié)感興趣的同學(xué)可以看我上文寫的那篇文章百炬。一個(gè)實(shí)例方法的流程如下:


對(duì)象實(shí)例收到消息(SEL+參數(shù))

根據(jù)存儲(chǔ)在對(duì)象實(shí)例中的ISA到類對(duì)象褐隆,類對(duì)象依次查找Class Cache(方法表緩存)和dispatch table找到對(duì)應(yīng)的Method,如果找到Method剖踊,執(zhí)行對(duì)應(yīng)Method的IMP(方法體)妓灌,并且返回結(jié)果

如果找不到Method轨蛤,則根據(jù)類對(duì)象中的super_class指針找到父類的Class對(duì)象蜜宪。一直找到NSObject的類對(duì)象

如果NSObject也無(wú)法找到這個(gè)SEL虫埂,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制

如果消息轉(zhuǎn)發(fā)機(jī)制無(wú)法處理,則拋出異常: doesNotRecognizeSelector

Method Swizzling

通過(guò)上文我們知道圃验,一個(gè)方法的調(diào)用實(shí)際上就是SEL(方法名)通過(guò)Runtime找到IMP(方法執(zhí)行體)


既然是通過(guò)Runtime動(dòng)態(tài)找到的掉伏,那么我們就可以利用Runtime的API,講SEL_1來(lái)指向IMP_2澳窑,接著我們?cè)僭谠贗MP_2的方法體中執(zhí)行IMP_1斧散,就實(shí)現(xiàn)了動(dòng)態(tài)插入代碼。


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

在Objective C的方法調(diào)用過(guò)程中摊聋,我們提到了當(dāng)無(wú)法響應(yīng)一個(gè)selector時(shí)鸡捐,在拋出異常之前會(huì)先進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制。這里來(lái)詳細(xì)講解消息轉(zhuǎn)發(fā)的過(guò)程:

關(guān)于消息轉(zhuǎn)發(fā)麻裁,官方文檔在這里:Message Forwarding

在觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制即forwardInvocation:之前箍镜,Runtime提供了兩步來(lái)進(jìn)行輕量級(jí)的動(dòng)態(tài)處理這個(gè)selector.

resolveInstanceMethod:

Dynamically provides an implementation for a given selector for an instance method.

這個(gè)方法提供了一個(gè)機(jī)會(huì):為當(dāng)前類無(wú)法識(shí)別的SEL動(dòng)態(tài)增加IMP

比如:最常見(jiàn)的可以通過(guò)class_addMethod

void?dynamicMethodIMP(id?self,?SEL?_cmd){/*...implementation...*/}

+?(BOOL)?resolveInstanceMethod:(SEL)aSEL

{

????if?(aSEL?==?@selector(resolveThisMethodDynamically))

????{

??????????class_addMethod([self?class],?aSEL,?(IMP)?dynamicMethodIMP,?"v@:");

??????????return?YES;

????}

????return?[super?resolveInstanceMethod:aSel];

}

Tips煎源,這里的"v@:"表示方法參數(shù)編碼色迂,v表示Void,@表示OC對(duì)象手销,:表示SEL類型歇僧。關(guān)于方法參數(shù)編碼,更詳細(xì)的內(nèi)容參見(jiàn)文檔锋拖。

如果resolveInstanceMethod返回NO诈悍,則表示無(wú)法在這一步動(dòng)態(tài)的添加方法,則進(jìn)入下一步:

forwardingTargetForSelector:

Returns?the?object?to?which?unrecognized?messages?should?first?be?directed.

這個(gè)方法提供了一個(gè)機(jī)會(huì):簡(jiǎn)單的把這個(gè)SEL交給另外一個(gè)對(duì)象來(lái)執(zhí)行兽埃。

比如:

-(id)forwardingTargetForSelector:(SEL)aSelector{

????if?(aSelector?==?@selector(dynamicSelector)?&&?[self.myObj?respondsToSelector:@selector(dynamicSelector)])?{

????????return?self.myObj;

????}else{

????????return?[super?forwardingTargetForSelector:aSelector];

????}

}

如果上述兩步都無(wú)法完成這個(gè)SEL的處理侥钳,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制,消息轉(zhuǎn)發(fā)機(jī)制有兩個(gè)比較重要的方法:

forwardInvocation: 具體的NSInvocaion

methodSignatureForSelector: 返回SEL的方法簽名

這里不得不提一下兩個(gè)類:

NSMethodSignature 用來(lái)表示方法的參數(shù)簽名信息:返回值讲仰,參數(shù)數(shù)量和類型

NSInvocaion SEL + 執(zhí)行SEL的Target + 參數(shù)值

通常慕趴,拿到NSInvocaion對(duì)象后,我們可選擇的進(jìn)行如下操作

修改執(zhí)行的SEL

修改執(zhí)行的Target

修改傳入的參數(shù)

然后調(diào)用:[invocation invoke]鄙陡,來(lái)執(zhí)行這個(gè)消息冕房。

_objc_msgForward

我們知道,正常情況下SEL背后會(huì)對(duì)一個(gè)IMP趁矾,在OC中有一個(gè)特殊的IMP就是:_objc_msgForward耙册。當(dāng)執(zhí)行_objc_msgForward時(shí),會(huì)直接觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制毫捣,即forwardInvocation:详拙。

Aspect的基本原理

使用Aspect帝际,可以在一個(gè)OC方法執(zhí)行前/后插入代碼,也可以替換這個(gè)OC方法的實(shí)現(xiàn)饶辙。

這里蹲诀,我們以在ViewControler的viewWillAppear:方法之后插入一段代碼為例,來(lái)講解hook前后的變化弃揽,

在沒(méi)有hook之前脯爪,ViewController的SEL與IMP關(guān)系如下


調(diào)用以下aspect來(lái)hook viewWillAppear:后:

?[ViewController?aspect_hookSelector:@selector(viewWillAppear:)

????????????????????????????withOptions:AspectPositionAfter

?????????????????????????????usingBlock:^{

?????????????????????????????????NSLog(@"Insert?some?code?after?ViewWillAppear");

?????????????????????????????}?error:&error];


最初的viewWillAppear: 指向了_objc_msgForward

增加了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP

最初的forwardInvocation:指向了Aspect提供的一個(gè)C方法__ASPECTS_ARE_BEING_CALLED__

動(dòng)態(tài)增加了__aspects_forwardInvocation:,指向最初的forwardInvocation:的IMP

然后,我們?cè)賮?lái)看看hook后矿微,一個(gè)viewWillAppear:的實(shí)際調(diào)用順序:

object收到selector(viewWillAppear:)的消息

找到對(duì)應(yīng)的IMP:_objc_msgForward痕慢,執(zhí)行后觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制。

object收到forwardInvocation:消息

找到對(duì)應(yīng)的IMP:__ASPECTS_ARE_BEING_CALLED__涌矢,執(zhí)行IMP

向object對(duì)象發(fā)送aspects_viewWillAppear:,執(zhí)行最初的viewWillAppear方法的IMP

????????????執(zhí)行插入的block代碼

????????????如果ViewController無(wú)法響應(yīng)aspects_viewWillAppear掖举,則向object對(duì)象發(fā)送__aspects_forwardInvocation:來(lái)執(zhí)行最初的forwardInvocation IMP

????????????所以,Aspects是采用了集中式的hook方式娜庇,所有的調(diào)用最后走的都是一個(gè)C函數(shù)__ASPECTS_ARE_BEING_CALLED__塔次。

核心類/數(shù)據(jù)結(jié)構(gòu)

AspectIdentifier - 代表一個(gè)Aspect的具體信息:包括被Hook的對(duì)象,SEL思灌,插入的block等具體信息俺叭。

@interface?AspectIdentifier?:?NSObject

@property?(nonatomic,?assign)?SEL?selector;

@property?(nonatomic,?strong)?id?block;

@property?(nonatomic,?strong)?NSMethodSignature?*blockSignature;

@property?(nonatomic,?weak)?id?object;

@property?(nonatomic,?assign)?AspectOptions?options;

@end

AspectTracker - 跟蹤一個(gè)類的繼承鏈中的hook狀態(tài):包括被hook的類,哪些SEL被hook了泰偿。

@interface?AspectTracker?:?NSObject

@property?(nonatomic,?strong)?Class?trackedClass;

@property?(nonatomic,?readonly)?NSString?*trackedClassName;

@property?(nonatomic,?strong)?NSMutableSet?*selectorNames;

@property?(nonatomic,?strong)?NSMutableDictionary?*selectorNamesToSubclassTrackers;

@end

AspectContainer - AspectIdentifier的容器:以SEL合成key熄守,然后作為關(guān)聯(lián)對(duì)象存儲(chǔ)到對(duì)應(yīng)的類/對(duì)象里。包括beforeAspects耗跛,insteadAspects裕照,afterAspects

@interface?AspectsContainer?:?NSObject

@property?(atomic,?copy)?NSArray?*beforeAspects;

@property?(atomic,?copy)?NSArray?*insteadAspects;

@property?(atomic,?copy)?NSArray?*afterAspects;

@end

AspectInfo - NSInvocation的容器,表示一個(gè)執(zhí)行的Command调塌。

@interface?AspectInfo?:?NSObject

@property?(nonatomic,?unsafe_unretained,?readonly)?id?instance;

@property?(nonatomic,?strong,?readonly)?NSArray?*arguments;

@property?(nonatomic,?strong,?readonly)?NSInvocation?*originalInvocation;

@end

hook過(guò)程

同樣晋南,我們以一個(gè)實(shí)例方法為例,講解在這個(gè)方法調(diào)用后發(fā)生了什么

?[ViewController?aspect_hookSelector:@selector(viewWillAppear:)

????????????????????????????withOptions:AspectPositionAfter

?????????????????????????????usingBlock:^{

?????????????????????????????????NSLog(@"Insert?some?code?after?ViewWillAppear");

?????????????????????????????}?error:&error];

1.對(duì)Class和MetaClass進(jìn)行進(jìn)行合法性檢查羔砾,判斷能否hook负间,規(guī)則如下

????????????retain,release,autorelease,forwoardInvocation:不能被hook

????????????dealloc只能在方法前hook

????????????類的繼承關(guān)系中,同一個(gè)方法只能被hook一次

2.創(chuàng)建AspectsContainer對(duì)象姜凄,以aspects_ + SEL為key政溃,作為關(guān)聯(lián)對(duì)象依附到被hook 的對(duì)象上

objc_setAssociatedObject(self,?aliasSelector,?aspectContainer,?OBJC_ASSOCIATION_RETAIN);

3.創(chuàng)建AspectIdentifier對(duì)象,并且添加到AspectsContainer對(duì)象里存儲(chǔ)起來(lái)态秧。這個(gè)過(guò)程分為兩步?

????????????生成block的方法簽名NSMethodSignature

????????????對(duì)比block的方法簽名和待hook的方法簽名是否兼容(參數(shù)個(gè)數(shù)董虱,按照順序的類型)

4.根據(jù)hook實(shí)例對(duì)象/類對(duì)象/類元對(duì)象的方法做不同處理。其中,對(duì)于上文以類方法來(lái)hook的時(shí)候愤诱,分為兩步

hook類對(duì)象的forwoardInvocation:方法云头,指向一個(gè)靜態(tài)的C方法,并且創(chuàng)建一個(gè)aspects_ forwoardInvocation:動(dòng)態(tài)添加到之前的類中

IMP?originalImplementation?=?class_replaceMethod(klass,?@selector(forwardInvocation:),?(IMP)__ASPECTS_ARE_BEING_CALLED__,?"v@:@");

if?(originalImplementation)?{

????class_addMethod(klass,?NSSelectorFromString(AspectsForwardInvocationSelectorName),?originalImplementation,?"v@:@");

}

hook類對(duì)象的viewWillAppear:方法讓其指向_objc_msgForward,動(dòng)態(tài)添加aspects_viewWillAppear:指向最初的viewWillAppear:實(shí)現(xiàn)

Hook實(shí)例的方法

Aspects支持只hook一個(gè)對(duì)象的實(shí)例方法

只不過(guò)在第4步略有出入淫半,當(dāng)hook一個(gè)對(duì)象的實(shí)例方法的時(shí)候:

新建一個(gè)子類溃槐,_Aspects_ViewController,并且按照上述的方式hook forwoardInvocation:

hook _Aspects_ViewController的class方法,讓其返回ViewController

hook 子類的類元對(duì)象撮慨,讓其返回ViewController

調(diào)用objc_setClass來(lái)修改ViewController的類為_(kāi)Aspects_ViewController

這樣做竿痰,就可以通過(guò)object_getClass(self)獲得類名,然后看看是否有前綴類名來(lái)判斷是否被hook過(guò)了

其他

object_getClass/self.class的區(qū)別

object_getClass獲得的是isa的指向

self.class則不一樣砌溺,當(dāng)self是實(shí)例對(duì)象的時(shí)候,返回的是類對(duì)象变隔,否則則返回自身规伐。

比如:

TestClass?*?testObj?=?[[TestClass?alloc]?init];

//Same

logAddress([testObj?class]);

logAddress([TestClass?class]);

//Not?same

logAddress(object_getClass(testObj));

logAddress(object_getClass([TestClass?class]));

Log

2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930

2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930

2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930

2017-05-22?22:41:49.061?OCTest[899:25934]?0x107d10908

Block簽名

block因?yàn)楸澈笃鋵?shí)是一個(gè)C結(jié)構(gòu)體,結(jié)構(gòu)體中存儲(chǔ)著著一個(gè)函數(shù)指針來(lái)指向?qū)嶋H的方法體

Block的內(nèi)存布局如下

typedef?NS_OPTIONS(int,?AspectBlockFlags)?{

????AspectBlockFlagsHasCopyDisposeHelpers?=?(1?<<?25),

????AspectBlockFlagsHasSignature??????????=?(1?<<?30)

};

typedef?struct?_AspectBlock?{

????__unused?Class?isa;

????AspectBlockFlags?flags;

????__unused?int?reserved;

????void?(__unused?*invoke)(struct?_AspectBlock?*block,?...);

????struct?{

????????unsigned?long?int?reserved;

????????unsigned?long?int?size;

????????//?requires?AspectBlockFlagsHasCopyDisposeHelpers

????????void?(*copy)(void?*dst,?const?void?*src);

????????void?(*dispose)(const?void?*);

????????//?requires?AspectBlockFlagsHasSignature

????????const?char?*signature;

????????const?char?*layout;

????}?*descriptor;

????//?imported?variables

}?*AspectBlockRef;

對(duì)應(yīng)生成NSMethodSignature的方法:

static?NSMethodSignature?*aspect_blockMethodSignature(id?block,?NSError?**error)?{

????AspectBlockRef?layout?=?(__bridge?void?*)block;

????if?(!(layout->flags?&?AspectBlockFlagsHasSignature))?{

????????NSString?*description?=?[NSString?stringWithFormat:@"The?block?%@?doesn't?contain?a?type?signature.",?block];

????????AspectError(AspectErrorMissingBlockSignature,?description);

????????return?nil;

????}

????void?*desc?=?layout->descriptor;

????desc?+=?2?*?sizeof(unsigned?long?int);

????if?(layout->flags?&?AspectBlockFlagsHasCopyDisposeHelpers)?{

????????desc?+=?2?*?sizeof(void?*);

????}

????if?(!desc)?{

????????NSString?*description?=?[NSString?stringWithFormat:@"The?block?%@?doesn't?has?a?type?signature.",?block];

????????AspectError(AspectErrorMissingBlockSignature,?description);

????????return?nil;

????}

????const?char?*signature?=?(*(const?char?**)desc);

????return?[NSMethodSignature?signatureWithObjCTypes:signature];

}

關(guān)于Block的更多講解匣缘,參見(jiàn)我的前一篇博客

Objective C block背后的黑魔法

效率

消息轉(zhuǎn)發(fā)機(jī)制相對(duì)于正常的方法調(diào)用來(lái)說(shuō)是比較昂貴的猖闪,所以一定不要用消息轉(zhuǎn)發(fā)機(jī)制來(lái)處理那些一秒鐘成百上千次的調(diào)用。

總結(jié)

Objective C的消息轉(zhuǎn)發(fā)機(jī)制是一個(gè)非常靈活的機(jī)制肌厨,用好它會(huì)讓你實(shí)現(xiàn)很多黑科技培慌,也能夠讓你的架構(gòu)更加靈活

原文:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柑爸,隨后出現(xiàn)的幾起案子吵护,更是在濱河造成了極大的恐慌,老刑警劉巖表鳍,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馅而,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡譬圣,警方通過(guò)查閱死者的電腦和手機(jī)瓮恭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厘熟,“玉大人屯蹦,你說(shuō)我怎么就攤上這事∩蹋” “怎么了登澜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)就缆。 經(jīng)常有香客問(wèn)我帖渠,道長(zhǎng),這世上最難降的妖魔是什么竭宰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任空郊,我火速辦了婚禮份招,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狞甚。我一直安慰自己锁摔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布哼审。 她就那樣靜靜地躺著谐腰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涩盾。 梳的紋絲不亂的頭發(fā)上十气,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音春霍,去河邊找鬼砸西。 笑死,一個(gè)胖子當(dāng)著我的面吹牛址儒,可吹牛的內(nèi)容都是我干的芹枷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼莲趣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸳慈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起喧伞,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤走芋,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后絮识,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绿聘,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年次舌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熄攘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彼念,死狀恐怖挪圾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逐沙,我是刑警寧澤哲思,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吩案,受9級(jí)特大地震影響棚赔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一靠益、第九天 我趴在偏房一處隱蔽的房頂上張望丧肴。 院中可真熱鬧,春花似錦胧后、人聲如沸芋浮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纸巷。三九已至,卻和暖如春眶痰,著一層夾襖步出監(jiān)牢的瞬間瘤旨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工凛驮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裆站,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓黔夭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親羽嫡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子本姥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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

  • 轉(zhuǎn)摘自面向切面編程 1. 背景 最近在做項(xiàng)目的打點(diǎn)統(tǒng)計(jì)的時(shí)候,發(fā)現(xiàn)業(yè)務(wù)邏輯和打點(diǎn)邏輯經(jīng)常耦合在一起杭棵,這樣一方面影響...
    Arthurcsh閱讀 644評(píng)論 1 1
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 763評(píng)論 0 1
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)婚惫,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 800評(píng)論 0 4
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過(guò) selector 快速查找 ...
    lylaut閱讀 1,844評(píng)論 2 3
  • 我們常常會(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