不会开机的男孩

Objective C SEL

| Comments

上一篇《Objective-C Message》,总结了一点关于Objective-C 消息发送中有意思的东西,中间穿插了一点关于SEL有趣的东西,之前,我们知道Objective-C runtime 在处理selector时,是做一个unique hash set, 那么今天,我们看看这个set 是如何产生的。这篇文章参考了link

unique set的好处是,字符串的比较可以非常迅速,但是也带来一个棘手的问题,创建一个这样的集合,真的不容易。虽然我们能够在compiler和 link的时候保证我们程序中唯一,但是这还远远不够,因为我们并不是生活在真空中,我们的程序需要和各种各样的其他程序协同工作,那么如何能够在这种繁杂的各种情况下,保证唯一呢?

简单说,就是在程序A中,有一个@selector(customInit),但在程序A中引入了程序B,而B中也有一个@selector(customInit),那么,显然,我们需要修正这2个selector,使他们指向同一个内存地址,这样才能保证消息发送正确。

好吧,我们程序员又要惊呼了,这是一个非常非常大的开销,因为

1、我们只能在运行时做这些工作。

2、这些工作是不可能不绕过strcmp(创建hash表时,如果发生了冲突,我们为了保证绝对正确,只能strcmp)。

3、当我们修正之后,也意味着,我们浪费了空间,而实际上就是我们创建了一个更大的hashtable(元素越多,发生碰撞的概率越大,空间的开销越大),

4、代码段在被映射到内存地址空间时,都在可读地址空间上,那么修正,意味着我们又多做了copy-on-write,同样意味着更多的空间开销

5、事实上,这样的函数还非常多,那些界面库函数等等,几乎被所有app引用 e.g. init,initWithFrame:。

更多的空间,更多的比较,导致了性能下降,特别是在程序载入时。事实上,runtime 和os 为我们的selector unique 做了下面的优化,大体可以理解成2个部分

1、减少需要修正的selector 集合

之前,我们看到的只有一个hash set,在runtime 载入时创建,但是,现在我们有了2个set(这个set是在Snow Leopard被加入的)。

一个是之前我们知道的,另一个也是一个hash set 当然,特别的是,这是一个perfect hash set。

从之前的5条件中,我们知道了,这些常用的如系统库函数,cocoa.framework中的selector 几乎被所有app引用,而且,我们非常开心的看到了,这些函数,都是可以确定的固定集合。事实上,dyld(dynamic loader and linker),给我们build了一个dyld shared cache,而且是一个perfect hash。而这个被映射到了各种app内存地址空间,并被共享。当我们创建unique selector set 时,我们可以先查找这个perfect hash set,来判断,我们是不是需要动态扩展我们的程序自己的selector hash set。而且,由于是perfect hash,使我们能够拥有在最坏情况下常数时间的开销。

2、延迟加载

对于这个,我们已经不陌生了,不管是windows dll 中的延时加载,还是各种在linux中的动态模块的延时载入,原理都是一样的。这些工作,只有在认为是必要条件时,才被真正的加载并初始化。

说的实在是太空了,让我们来看代码吧。

当类被调用或是说在被发送消息之前,类,需要被初始化一下,做的工作就是一些,运行时必要的空间分配,初始化,修正selector,methodlist, propertylist,categorylist等等的工作,我们这里,只是关注selector部分。

prepareForMethodLookup->realizeClass –> methodizeClass->attachMethodLists->fixupMethodList.

经过一系列的东东,修正我们的methodlist时,我们需要将methodlist中的SEL 修正,而这个过程就是我们关注的select unique。

不知道,大家还记得不记得,上一篇讲的 method结构

typedef struct method_list_t {

uint32_t entsize_NEVER_USE;  // low 2 bits used for fixup markers

uint32_t count;

struct method_t first;

} method_list_t;

typedef struct method_t {

SEL name;

const char *types;

IMP imp;

} method_t




static void 
fixupMethodList(method_list_t *mlist, BOOL bundleCopy) 
{ 
    assert(!isMethodListFixedUp(mlist));

    // fixme lock less in attachMethodLists ? 
    sel_lock();

    uint32_t m; 
    for (m = 0; m < mlist->count; m++) {

        //studentdeng note:fixup selector and make sure selector unique 
        method_t *meth = method_list_nth(mlist, m); 
        SEL sel = sel_registerNameNoLock((const char *)meth->name, bundleCopy); 
        meth->name = sel;

        if (sel == (SEL)kIgnore) { 
            meth->imp = (IMP)&_objc_ignored_method; 
        } 
    }

    sel_unlock();

    setMethodListFixedUp(mlist); 
}






sel_registerNameNoLock->__sel_registerName


static SEL __sel_registerName(const char *name, int lock, int copy) 
{ 
    SEL result = 0;

    if (lock) rwlock_assert_unlocked(&selLock); 
    else rwlock_assert_writing(&selLock);

    if (!name) return (SEL)0; 
    result = _objc_search_builtins(name); //studentdeng note:这里就是查找perfect hash set build by dyld cache 
    if (result) return result; 

    if (lock) rwlock_read(&selLock); 
    if (_objc_selectors) { 
        result = __objc_sel_set_get(_objc_selectors, (SEL)name); //studentdeng note: 这里就是查找我们程序自己的hash set 
    } 
    if (lock) rwlock_unlock_read(&selLock); 
    if (result) return result;

    // No match. Insert.

    if (lock) rwlock_write(&selLock);

    if (!_objc_selectors) { 
        _objc_selectors = __objc_sel_set_create(NUM_NONBUILTIN_SELS); 
    } 
    if (lock) { 
        // Rescan in case it was added while we dropped the lock 
        result = __objc_sel_set_get(_objc_selectors, (SEL)name); 
    } 
    if (!result) { 
        result = (SEL)(copy ? _strdup_internal(name) : name); 
        __objc_sel_set_add(_objc_selectors, result); 
#if defined(DUMP_UNKNOWN_SELECTORS) 
        printf("\t\"%s\",\n", name); 
#endif 
    }

    if (lock) rwlock_unlock_write(&selLock); 
    return result; 
}

牛b的代码,从来都是自解释的。

Comments