Delegate的IMP缓存

本文介绍了一种在Objective-C中提高delegate方法调用效率的技术——IMP缓存,并提供了具体的实现示例。通过缓存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);
}


[ERROR] ► - LUA ERROR: [NetWorkMgr] ErrorCode: 1000 ErrorCodeStr: SERVER_ERROR [StackTrace]: stack traceback: Assets/GameAssets/Lua/Network/NetworkMgr.lua.txt:434: in function 'Network.NetworkMgr.DOErrorCodeEvent' Assets/GameAssets/Lua/Network/NetworkMgr.lua.txt:425: in function 'Network.NetworkMgr.onGetWarn' Assets/GameAssets/Lua/Network/Handler/Handler_General.lua.txt:12: in function <Assets/GameAssets/Lua/Network/Handler/Handler_General.lua.txt:11> (...tail calls...) Assets/GameAssets/Lua/Network/NetworkMgr.lua.txt:208: in function 'Network.NetworkMgr.Receive' Assets/GameAssets/Lua/Network/ServerManager.lua.txt:48: in function 'Network.ServerManager.OnReceiveMsg' Assets/GameAssets/Lua/Main.lua.txt:77: in function 'OnReceiveMsg' UnityEngine.Debug:LogError (object) TEngine.DefaultLogHelper:LogImp (TEngine.DefaultLogHelper/ELogLevel,string) (at Assets/TEngine/Runtime/Core/Utility/DefaultHelper/DefaultLogHelper.cs:159) TEngine.DefaultLogHelper:Log (TEngine.GameFrameworkLogLevel,object) (at Assets/TEngine/Runtime/Core/Utility/DefaultHelper/DefaultLogHelper.cs:49) TEngine.GameFrameworkLog:Error (string) (at Assets/TEngine/Runtime/Core/Log/GameFrameworkLog.cs:1616) TEngine.Log:Error (string) (at Assets/TEngine/Runtime/Core/Log/Log.cs:1630) XLua.StaticLuaCallbacks:PrintError (intptr) (at Assets/GameScripts/HotFix/GameLogic/XLua/Src/StaticLuaCallbacks.cs:668) XLua.LuaDLL.Lua:lua_pcall (intptr,int,int,int) (at Assets/GameScripts/HotFix/GameLogic/XLua/Src/LuaDLL.cs:300) XLua.DelegateBridge:PCall (intptr,int,int,int) (at Assets/GameScripts/HotFix/GameLogic/XLua/Src/DelegateBridge.cs:138) XLua.DelegateBridge:__Gen_Delegate_Imp43 (byte[]) (at Assets/GameScripts/HotFix/GameLogic/XLua/Gen/DelegatesGensBridge.cs:1003) SocketManager:UpdatePacket () (at Assets/GameScripts/HotFix/GameLogic/GameModule/Network/SocketManager.cs:535) SocketManager:Update () (at Assets/GameScripts/HotFix/GameLogic/GameModule/Network/SocketManager.cs:95)
最新发布
07-12
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值