Objective C SEL
2011-10-16 16:46 curer 阅读(3540) 评论(4) 编辑 收藏 举报上一篇http://www.cnblogs.com/studentdeng/archive/2011/10/06/2199873.html,总结了一点关于Objective C message send 的有意思的东西,中间穿插了一点关于SEL有趣的东西,之前,我们知道Objective-C runtime 在处理selector时,是做一个unique hash set, 那么今天,我们看看这个set 是如何产生的。这篇文章参考了http://www.sealiesoftware.com/blog/archive/2009/09/01/objc_explain_Selector_uniquing_in_the_dyld_shared_cache.html
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的代码,从来都是自解释的。