Delegate的IMP缓存

来源:互联网 发布:搞笑的网络短剧 编辑:程序博客网 时间:2024/06/05 16:23

     在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:nilCost:898.973000; [self.mydelegate logMessage:nilCost: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);}