在objective-c中,所有的[receiver message:...]方法调用最终都会以obj_msgSend(recevier, @selector(message), …)的形式执行,这相比于c/c++的直接调用多少会有点影响,《深入分析 objc_msgSend》这篇文章主要分析了objc_msgSend具体需要执行的操作和可能的实现源码。
在WebKit的源码中就使用了将delegate中selector的IMP全部缓存起来的方式,然后在调用的时候直接调用而非通过obj_msgSend的形式来执行,下面根据WebKit的源码简单写了一下delegate缓存的实现。
缓存数据结构:
首先我们需要一个数据结构来缓存所有相关的delegate方法。
typedef struct _MyDelegateImplementationCache {
IMP logMessage;
IMP shouldDisMiss;
} MyDelegateImplementationCache;
然后在delegate的setter函数中除了设置delegate变量,还需要做的一件事就是初始化delegate相关的方法缓存;
delegate的setting方法;
- (void)setMydelegate:(id<MyDelegate>)mydelegate
{
_mydelegate = mydelegate;
// Initialize the Delegate Implementation Cache.
[self_cacheMyDelegateImplementations];
}
- (id<MyDelegate>)mydelegate
{
return_mydelegate;
}
初始化缓存:
static inline IMP getMethod(id o, SEL s)
{
return [o respondsToSelector:s] ? [o methodForSelector:s] : 0;
}
- (void)_cacheMyDelegateImplementations
{
MyDelegateImplementationCache *cache = &(_delegateCache);
id delegate = self.mydelegate;
if (!delegate) {
bzero(cache, sizeof(MyDelegateImplementationCache));
return;
}
// Get all the method's implecation and cache it in a struct.
cache->logMessage = getMethod(delegate, @selector(logMessage:));
cache->shouldDisMiss = getMethod(delegate, @selector(shouldDisMiss));
}
然后写几个对应的直接调用缓存中IMP的wrapper:
id CallDelegateString(IMP impletation, id delegate, SEL selector, NSString* arg1)
{
if (!delegate || ![delegate respondsToSelector:selector])
return nil;
@try {
id result =(id)((id (*)(id, SEL, NSString*))(impletation)(delegate, @selector(selector), arg1));
return result;
} @catch(id exception) {
ReportDiscardedDelegateException(selector, exception);
}
returnnil;
}
BOOL CallDelegateReturnBOOL(IMP impletation, id delegate, SEL selector)
{
if (!delegate || ![delegate respondsToSelector:selector])
return NO;
@try {
BOOL result =(BOOL)((BOOL (*)(id, SEL))(impletation)(delegate, @selector(selector)));
return result;
} @catch(id exception) {
ReportDiscardedDelegateException(selector, exception);
}
return NO;
}
最后在需要调用delegate方法的时候通过调用wrapper来替代用[delegate message:…]的方式来调用。
// Call the method from the delegate cache.
CallDelegateString(_delegateCache.logMessage, self.delegate, @selector(logMessage:), @"the delegate method's impletation has been cached!\n");
BOOL ret = CallDelegateReturnBOOL(_delegateCache.shouldDisMiss, self.delegate, @selector(shouldDisMiss));
那么,为什么WebKit仅针对delegate的这种方式来现实[delegate message:…]的IMP缓存,这是因为delegate都被声明为id类型,而objective-c的runtime机制向id类型的对象发送消息又比向确定类型的对象发送消息要满一点,仿照《深入分析 objc_msgSend》中的方法对这两种情况做了一个简单的测试:
START
for (NSUInteger i = 0; i < LOOP; ++i) {
[self.viewController logMessage:nil];
}
END
START
for (NSUInteger i = 0; i < LOOP; ++i) {
[self.mydelegate logMessage:nil];
}
END
测试结果为:[self.viewController logMessage:nil] Cost:898.973000; [self.mydelegate logMessage:nil] Cost:1420.758000。
另外,WebKit的wrapper是用c++来实现的,其中用到了模版类,具体的实现如下,这里的实现是c实现,如果需要扩展其他的参数形式可能要麻烦一些,是不是有点java中jni的感觉。
static inline id CallDelegate(WebView *self, id delegate, SEL selector, NSRect rect)
{
if (!delegate || ![delegate respondsToSelector:selector])
return nil;
@try {
return wtfObjcMsgSend<id>(delegate, selector, self, rect);
} @catch(id exception) {
ReportDiscardedDelegateException(selector, exception);
}
returnnil;
}
template<typename RetType>
RetType wtfObjcMsgSend(id target, SEL selector)
{
return reinterpret_cast<RetType (*)(id, SEL)>(objc_msgSend)(target, selector);
}