Delegate的IMP缓存

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


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值