本文主要針對(duì)<objc/message.h>進(jìn)行討論
首先導(dǎo)入上面的頭文件
消息發(fā)送
在OC中,調(diào)用一個(gè)方法的格式如下:
[davin playWith: friend];
在方法調(diào)用的時(shí)候,runtime會(huì)將上面的方法調(diào)用轉(zhuǎn)換成一個(gè)C語言的函數(shù)調(diào)用,表示朝著davin發(fā)送了一個(gè)playWith:消息,并傳入了friend這個(gè)參數(shù):
objc_msgSend(davin, @selector(playWith:), friend);
那么在這個(gè)C語言函數(shù)中發(fā)生了什么事情?編譯器是如何找到這個(gè)類的方法的呢?蘋果開源了 runtime的實(shí)現(xiàn)代碼,其中為了高度優(yōu)化性能,蘋果使用匯編實(shí)現(xiàn)了這個(gè)函數(shù)(源碼處于Source/objc-msg-arm.s
文件下):
/*****************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
*
*****************************************************************/
ENTRY objc_msgSend
MESSENGER_START
cbz r0, LNilReceiver_f // 判斷消息接收者是否為nil
ldr r9, [r0] // r9 = self->isa
CacheLookup NORMAL // 到緩存中查找方法
LCacheMiss: // 方法未緩存
MESSENGER_END_SLOW
ldr r9, [r0, #ISA]
b __objc_msgSend_uncached
LNilReceiver: // 消息接收者為nil處理
mov r1, #0
mov r2, #0
mov r3, #0
FP_RETURN_ZERO
MESSENGER_END_NIL
bx lr
LMsgSendExit:
END_ENTRY objc_msgSend
即使不懂匯編,上面的代碼通過注釋后也足以讓各位一窺究竟。從上述代碼中我們可以看到一個(gè)方法調(diào)用過程中發(fā)生的事情,包括:
判斷接收者是否為nil,如果為nil,清空寄存器,消息發(fā)送返回nil
到類緩存中查找方法,如果存在直接返回方法
沒有找到緩存,到類的方法列表中依次尋找
查找方法實(shí)現(xiàn)是通過
_class_lookupMethodAndLoadCache3
這個(gè)奇怪的函數(shù)完成的:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;
methodListLock.assertUnlocked();
// 如果傳入的cache為YES,到類緩存中查找方法緩存
if (cache) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}
// 判斷類是否已經(jīng)被釋放
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
// 如果類未初始化,對(duì)其進(jìn)行初始化。如果這個(gè)消息是initialize,那么直接進(jìn)行類的初始化
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
retry:
methodListLock.lock();
// 忽略在GC環(huán)境下的部分消息,比如retain、release等
if (ignoreSelector(sel)) {
methodPC = _cache_addIgnoredEntry(cls, sel);
goto done;
}
// 遍歷緩存方法,如果找到,直接返回
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;
// 遍歷類自身的方法列表查找方法實(shí)現(xiàn)
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
// 嘗試向上遍歷父類的方法列表查找實(shí)現(xiàn)
curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// 查找父類的方法列表
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}
// 沒有找到任何的方法實(shí)現(xiàn),進(jìn)入消息轉(zhuǎn)發(fā)第一階段“動(dòng)態(tài)方法解析”
// 調(diào)用+ (BOOL)resolveInstanceMethod: (SEL)selector
// 征詢接收者所屬的類是否能夠動(dòng)態(tài)的添加這個(gè)未實(shí)現(xiàn)的方法來解決問題
if (resolver && !triedResolver) {
methodListLock.unlock();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}
// 仍然沒有找到方法實(shí)現(xiàn)進(jìn)入消息轉(zhuǎn)發(fā)第二階段“備援接收者”
// 先后會(huì)調(diào)用 -(id)forwardingTargetForSelector: (SEL)selector
// 以及 - (void)forwardInvocation: (NSInvocation*)invocation 進(jìn)行最后的補(bǔ)救
// 如果補(bǔ)救未成功拋出消息發(fā)送錯(cuò)誤異常
_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;
done:
methodListLock.unlock();
assert(!(ignoreSelector(sel) && methodPC != (IMP)&_objc_ignored_method));
return methodPC;
}
上面就是一個(gè)方法調(diào)用的全部過程。主要分為三個(gè)部分:
查找是否存在對(duì)應(yīng)的方法緩存,如果存在直接返回調(diào)用為了優(yōu)化性能,方法的緩存使用了散列表的方式,在下一部分會(huì)進(jìn)行比較詳細(xì)的講述
未找到緩存,到類本身或順著類結(jié)構(gòu)向上查找方法實(shí)現(xiàn),返回的method_t *類型也被命名為Method
//非加鎖狀態(tài)下查找方法實(shí)現(xiàn) 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; } // 搜索方法列表 static method_t * search_method_list(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { // 對(duì)有序數(shù)組進(jìn)行線性探測(cè) return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list for (auto& meth : *mlist) { if (meth.name == sel) return &meth; } } #if DEBUG // sanity-check negative results if (mlist->isFixedUp()) { for (auto& meth : *mlist) { if (meth.name == sel) { _objc_fatal("linear search worked when binary search did not"); } } } #endif return nil; }
如果在這個(gè)步驟中找到了方法的實(shí)現(xiàn),那么將它加入到方法緩存中以便下次調(diào)用能快速找到:
// 記錄并且緩存方法
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
//在無加鎖狀態(tài)下緩存方法
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
if (!cls->isInitialized()) return;
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// 如果緩存占用不到3/4,進(jìn)行緩存。
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
}
else {
// 擴(kuò)充緩存。為了性能,擴(kuò)充后原有緩存方法全部移除
cache->expand();
}
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
如果在類自身中沒有找到方法實(shí)現(xiàn),那么循環(huán)獲取父類,重復(fù)上面的查找動(dòng)作,找到后再將方法緩存到本類而非父類的緩存中
未找到任何方法實(shí)現(xiàn),觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制進(jìn)行最后補(bǔ)救
其中消息轉(zhuǎn)發(fā)分為兩個(gè)階段,第一個(gè)階段我們可以通過動(dòng)態(tài)添加方法之后讓編譯器再次執(zhí)行查找方法實(shí)現(xiàn)的過程;第二個(gè)階段稱作備援的接收者,就是找到一個(gè)接盤俠來處理這個(gè)事件
void _class_resolveMethod(Class cls, SEL sel, id inst) { // 非beta類的情況下直接調(diào)用 resolveInstanceMethod 方法 if (! cls->isMetaClass()) { _class_resolveInstanceMethod(cls, sel, inst); } else { // 先調(diào)用 resolveClassMethod 請(qǐng)求動(dòng)態(tài)添加方法 // 然后進(jìn)行一次查找判斷是否處理完成 // 如果沒有添加,再調(diào)用 resolveInstanceMethod 方法 _class_resolveClassMethod(cls, sel, inst); if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); } } }
方法緩存在上一篇runtime文章中筆者已經(jīng)說過對(duì)于OC的每一個(gè)對(duì)象來說,本質(zhì)上都是一個(gè)objc_class的結(jié)構(gòu)體封裝,在最新的runtime源碼的objc-runtime-new.h
中,objc_class的結(jié)構(gòu)如下(筆者已經(jīng)略去了大部分的函數(shù)):
struct objc_class : objc_object {
Class superclass; // Class ISA;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// .........
}
結(jié)構(gòu)一目了然,很明顯cache存儲(chǔ)著我們?cè)诜椒ㄕ{(diào)用中需要查找的方法緩存。作為緩存方法的cache采用了散列表,以此來大幅度提高檢索的速度:
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
// functions
}
// cache method
buckets = (cache_entry **)cache->buckets;
for (index = CACHE_HASH(sel, cache->mask);
buckets[index] != NULL;
index = (index+1) & cache->mask)
{ }
buckets[index] = entry;
在每次調(diào)用完未被緩存的方法時(shí),下面的那段緩存方法的代碼就會(huì)調(diào)用。蘋果利用了sel的指針地址和mask做了一個(gè)簡(jiǎn)單的位運(yùn)算,然后找到一個(gè)空槽存儲(chǔ)起來。 以此我們可以推出從緩存中查找sel實(shí)現(xiàn)的代碼CacheLookup,但是為了高度優(yōu)化性能,蘋果同樣喪心病狂的使用匯編完成了查找的步驟,官方給出的注釋足夠我們大致看明白這段代碼:
.macro CacheLookup
ldrh r12, [r9, #CACHE_MASK] // r12 = mask
ldr r9, [r9, #CACHE] // r9 = buckets
.if $0 == STRET || $0 == SUPER_STRET
and r12, r12, r2 // r12 = index = SEL & mask
.else
and r12, r12, r1 // r12 = index = SEL & mask
.endif
add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
ldr r12, [r9] // r12 = bucket->sel
2:
.if $0 == STRET || $0 == SUPER_STRET
teq r12, r2
.else
teq r12, r1
.endif
bne 1f
CacheHit $0
1:
cmp r12, #1
blo LCacheMiss_f // if (bucket->sel == 0) cache miss
it eq // if (bucket->sel == 1) cache wrap
ldreq r9, [r9, #4] // bucket->imp is before first bucket
ldr r12, [r9, #8]! // r12 = (++bucket)->sel
b 2b
.endmacro
具體的源碼可以從蘋果開源這里下載,這個(gè)方法蘋果已經(jīng)注釋的足夠清晰了。
上面所有的操作都是對(duì)方法的緩存、查找操作,那么方法究竟是什么?在OC中方法被抽象成的數(shù)據(jù)類Method
,如果了解并且使用過runtime的讀者們可能了解這個(gè)類型,其結(jié)構(gòu)如下:
struct old_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
typedef struct method_t *Method;
方法的實(shí)現(xiàn)代碼,你可以把它看做一個(gè)block。事實(shí)上,后者確實(shí)可以轉(zhuǎn)換成一個(gè)IMP類型來實(shí)現(xiàn)某些黑魔法。method_types方法的參數(shù)編碼,什么意思?在屬性與變量中我說過每一種數(shù)據(jù)類型有著自己對(duì)應(yīng)的字符編碼,這個(gè)表示方法返回值、參數(shù)的字符編碼,比如-(void)playWith:(id)
的字符編碼為v@:@
method_name
顧名思義,方法的名字。通常我們使用@selector()
的方式獲取一個(gè)方法的sel地址,這個(gè)被用來進(jìn)行散列計(jì)算存儲(chǔ)方法的imp實(shí)現(xiàn)。由于SEL類型采用了散列的算法,因此如果同一個(gè)類中存在同樣名字的方法,那么就會(huì)導(dǎo)致方法的imp地址無法唯一化。這也是蘋果不允許同名不同參數(shù)類型的方法存在的原因
消息轉(zhuǎn)發(fā)
通常情況下,在我們調(diào)用不屬于某個(gè)對(duì)象的方法的時(shí)候,我們的應(yīng)用就會(huì)崩潰crash,比如筆者經(jīng)歷過好幾次因?yàn)楹笈_(tái)返回的NSNull類型導(dǎo)致了測(cè)試反饋應(yīng)用閃退。通過上面的方法調(diào)用源碼我們可以看到并不是沒有找到方法實(shí)現(xiàn)就直接發(fā)生了崩潰,在崩潰之前編譯器會(huì)進(jìn)行消息轉(zhuǎn)發(fā)機(jī)制,總共給了我們?nèi)螜C(jī)會(huì)來避免這樣的崩潰并盡可能的找到方法的響應(yīng)者。

首先先看第一階段。我們都知道,在iOS開發(fā)當(dāng)中我們需要非常的注意用戶體驗(yàn)。單純的是因?yàn)閿?shù)據(jù)類型錯(cuò)誤而導(dǎo)致應(yīng)用出現(xiàn)閃退,這樣的處理會(huì)極大的影響使用app的用戶。因此,我們可以通過
class_addMethod這個(gè)函數(shù)來動(dòng)態(tài)的添加這種錯(cuò)誤的處理(類可以在objc_registerClassPair完成類的注冊(cè)之后動(dòng)態(tài)的添加方法,但不允許動(dòng)態(tài)添加屬性,參考category機(jī)制)
id wrongTypeGetter(id object, SEL sel) {
return nil;
}
void wrongTypeSetter(id object, SEL sel, id value) {
// do nothing
}
+ (BOOL)resolveInstanceMethod: (SEL)selector
{
NSString * selName = NSStringFromSelector(selector);
if ([sel hasPrefix: @"set"]) {
class_addMethod(self, selector, (IMP)wrongTypeSetter, "v@:@");
} else {
class_addMethod(self, selector, (IMP)wrongTypeGetter, "@@:")
}
}
在第二階段最開始的時(shí)候,這時(shí)候已經(jīng)默許了你并不想使用消息接收者來響應(yīng)這個(gè)方法,所以我們需要找到消息接盤俠
—— 這并不是一件壞事。在iOS中不支持多繼承,盡管我們可以通過協(xié)議和組合模式實(shí)現(xiàn)偽多繼承。偽多繼承和多繼承的區(qū)別在于:多繼承是將多個(gè)類的功能組合到一個(gè)對(duì)象當(dāng)中,而偽多繼承多個(gè)類的功能依舊分布在不同對(duì)象當(dāng)中,但是對(duì)象彼此對(duì)消息發(fā)送者透明
。那么,如果我們消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象可以用來實(shí)現(xiàn)這種偽多繼承。
@interface Person: NSObject@property (nonatomic, strong) NSNumber * age;@end@implementation Person- (id)forwardingTargetForSelector: (SEL)aSelector{ // 甚至可以通過runtime遍歷自己屬性找到可以響應(yīng)方法的接盤俠 NSString * selName = NSStringFromSelector(aSelector); if ([selName hasSuffix: @"Value"]) { return self.age; } return nil;}@end// View controllerid p = [[Person alloc] init];[p setAge: @(18)];NSLog(@"%lu, %.2f", [p integerValue], [p doubleValue]); //18, 18.00
如果你依舊沒有為這個(gè)方法找到另外一個(gè)調(diào)用者,那么阻止你app閃退的最后時(shí)刻到來了。runtime需要生成一個(gè)methodSignature變量來組裝,這將通過調(diào)用消息接收者的-(NSMethodSignature *)methodSignatureForSelector:
獲取,這個(gè)變量包含了方法的參數(shù)類型、參數(shù)個(gè)數(shù)以及消息接收者等信息。接著把這個(gè)變量組裝成一個(gè)NSInvocation對(duì)象進(jìn)行最后一次的消息轉(zhuǎn)發(fā),調(diào)用接收者的-forwardInvocation:來進(jìn)行最后的挽救機(jī)會(huì)。這意味著我們可以盡情的對(duì)invocation
做任何事情,包括隨意修改參數(shù)值、消息接收者等。我最常拿來干的事情就是減少數(shù)組的遍歷工作:
@implementation NSArray(LXDRuntime)- (void)forwardInvocation: (NSInvocation *)anInvocation{ for (id item in self) { if ([item respondsToSelector: anInvocation.selector]) { [anInvocation invokeWithTarget: item]; } }}@end
總的來說整個(gè)消息發(fā)送的過程可以歸納成下面這張圖:

雖然消息轉(zhuǎn)發(fā)可以幫助我們顯著的減少app的閃退率,但是在開發(fā)階段千萬不要加入這些特性。最好是在app申請(qǐng)上架的那個(gè)階段再加,這樣不至于app其他消息發(fā)送異常被我們忽略了。
消息機(jī)制黑魔法
上面筆者講解了關(guān)于一個(gè)調(diào)用方法之中發(fā)生的事情,確實(shí)非常的復(fù)雜。同樣的這些特性也非常值得我們?nèi)W(xué)習(xí)使用,runtime提供了一系列關(guān)于Method的方法給我們實(shí)現(xiàn)面向切面編程的工作。這些工作包括了替換原有方法實(shí)現(xiàn),交換方法實(shí)現(xiàn)等等工作。
假設(shè)現(xiàn)在我需要一個(gè)圓角按鈕,并且保證點(diǎn)擊觸發(fā)事件的范圍必須要這個(gè)圓之內(nèi),那么通過一個(gè)UIButton+LXDRuntime的擴(kuò)展來替換舊有-pointInside:withEvent:
方法
@interface UIButton(LXDRuntime)@property (nonatomic, assign) BOOL roundTouchEnable;@endconst void * LXDRoundTouchEnableKey = & LXDRoundTouchEnableKey;@implementation UIButton(LXDRuntime)- (BOOL)roundTouchEnable{ return [objc_getAssociatedObject(self, LXDRoundTouchEnableKey) boolValue];}- (void)setRoundTouchEnable: (BOOL)roundTouchEnable{ objc_setAssociatedObject(self, LXDRoundTouchEnableKey, @(roundTouchEnable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (BOOL)replacePointInside: (CGPoint)point withEvent: (UIEvent *)event{ if (CGRectGetWidth(self.frame) != CGRectGetHeight(self.frame) || !self. roundTouchEnable) {? return [super pointInside: point withEvent: event]; } CGFloat radius = CGRectGetWidth(self.frame) / 2; CGPoint offset = CGPointMake(point.x - radius, point.y - radius); return sqrt(offset.x * offset.x + offset.y * offset.y) <= radius;}// 替換方法實(shí)現(xiàn)+ (void)initialize{ [super initialize]; Method replaceMethod = class_getInstanceMethod([self class], @selector(replacePointInside:withEvent:)); Method originMethod = class_getInstanceMethod([self class], @selector(pointInside:withEvent:)); method_setImplementation(originMethod, method_getImplementation(replaceMethod));}@end
那么當(dāng)我需要我的按鈕只響應(yīng)圓形點(diǎn)擊區(qū)域的時(shí)候,只需要設(shè)置button.roundTouchEnable = YES,就會(huì)自動(dòng)實(shí)現(xiàn)了圓形點(diǎn)擊的判斷。除了上面的上面的方法替換,還有另一個(gè)常用的黑魔法是交換兩個(gè)方法的實(shí)現(xiàn)。歸功于Method的特殊結(jié)構(gòu),將方法名字sel
跟代碼實(shí)現(xiàn)imp分隔開來。你可以把imp當(dāng)做是一個(gè)block代碼塊,而交換實(shí)現(xiàn)的操作就相當(dāng)于把這兩個(gè)block交換了。
@interface Person : NSObject@property (nonatomic, strong) NSString * name;@property (nonatomic, strong) NSNumber * age;@end@implementation Person+ (void)initialize{ [super initialize]; Method ageGetter = class_getInstanceMethod([self class], @selector(age)); Method nameGetter = class_getInstanceMethod([self class], @selector(name)); method_exchangeImplementations(ageGetter, nameGetter);}// View controllerPerson * p = [[Person alloc] init];p.age = @(56);p.name = @"Job Steve";NSLog(@"%@ is %@ year old", p.name, p.age); // LXDRuntimeDemo[7316:244912] 56 is Job Steve year old@end
上面的代碼交換了name和age
的實(shí)現(xiàn),用圖示來表示:

應(yīng)該不難看出,
method_exchangeImplementations之所以被推崇的原因在于這種方式交換實(shí)現(xiàn)的時(shí)候不會(huì)導(dǎo)致原有的方法實(shí)現(xiàn)發(fā)生改變(從頭到尾,
age的IMP跟name的IMP都沒有進(jìn)行任何的修改),當(dāng)然了,它的缺點(diǎn)也是非常明顯的:多人開發(fā)對(duì)同一個(gè)方法都進(jìn)行方法
替換/交換時(shí),會(huì)使得業(yè)務(wù)邏輯復(fù)雜,非常的不利于調(diào)試
被交換的方法實(shí)現(xiàn)會(huì)直接的影響到所有該類的實(shí)例對(duì)象以及子類,不適用于單個(gè)對(duì)象的實(shí)現(xiàn)
可以說runtime提供的這些黑魔法都是雙刃劍,合理的運(yùn)用能讓我們更加的強(qiáng)大。另外,除了Method的黑魔法,還要提到一個(gè)IMP相關(guān)的使用陷阱。上文說過,IMP跟block是非常相似的東西,前者可以跟函數(shù)指針強(qiáng)制轉(zhuǎn)換,因此可以看做是一個(gè)特殊的block,同樣的系統(tǒng)提供了兩者相互轉(zhuǎn)換的方法:imp_implementationWithBlock和imp_getBlock。按照上面說的,當(dāng)調(diào)用方法轉(zhuǎn)換成消息轉(zhuǎn)發(fā)的時(shí)候,objc_msgSend自身已經(jīng)存在了兩個(gè)參數(shù)id object以及SEL aSelector,那么按照這種思路IMP和block
的切換應(yīng)該是這樣的:
+ (void)initialize
void (^requestBlock)(id object, SEL aSelector, id URL, id parameters) =
^(id object, SEL aSelector, id URL, id parameters) {
// do some networking request
};
IMP requestIMP = imp_implementationWithBlock(requestBlock);
class_addMethod([self class], @selector(networkReuqest:parameters:), requestIMP, "v@:@@");
}
// View controller
[self performSelector: @selector(networkReuqest:parameters:) withObject: URL withObject: parameters];
上面這段代碼會(huì)crash的非常無厘頭,提示你EXC_BAD_ACCESS
錯(cuò)誤。重要的事情說三遍:
block參數(shù)不存在SEL!!
block參數(shù)不存在SEL!!
block參數(shù)不存在SEL!!
上面的代碼只要去掉了SEL aSelector
這個(gè)參數(shù),這段代碼就能正常執(zhí)行。
本文參考自Bison的技術(shù)博客