是否能够响应方法:respondsToSelector的实现

首先附上常见类和类型的定义:

typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

typedef struct objc_object *id;

//SEL 可以将其理解为方法的 ID. 结构如下:
typedef struct objc_selector *SEL;
// IMP 可以理解为函数指针,指向了最终的实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

    Class _Nullable super_class OBJC2_UNAVAILABLE;
    const char * _Nonnull name  OBJC2_UNAVAILABLE;
    long version                OBJC2_UNAVAILABLE;
    long info                   OBJC2_UNAVAILABLE;
    long instance_size          OBJC2_UNAVAILABLE;
    // 成员变量地址列表
    struct objc_ivar_list * _Nullable ivars     OBJC2_UNAVAILABLE;
    // 方法地址列表
    struct objc_method_list * _Nullable * _Nullable methodLists     OBJC2_UNAVAILABLE;
    // 缓存最近使用的方法地址,以避免多次在方法地址列表中查询,提升效率
    struct objc_cache * _Nonnull cache      OBJC2_UNAVAILABLE;
    // 遵循的协议列表
    struct objc_protocol_list * _Nullable protocols     OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

NSObject.mm中的该方法实现如下:

// 传入的参数为SEL对象
- (BOOL)respondsToSelector:(SEL)sel {
    // 参数分别为:当前对象,SEL对象,当前对象的class
    return class_respondsToSelector_inst(self, sel, [self class]);
}

class_respondsToSelector_inst方法的实现如下:

NEVER_INLINE BOOL 
class_respondsToSelector_inst(id inst, SEL sel, Class cls)
{
    // 检测sel是否为nil、cls是否为nil、执行lookUpImpOrNil方法判断能够找到实现方法
    return sel && cls && lookUpImpOrNil(inst, sel, cls, LOOKUP_RESOLVER);
}

关于枚举数据LOOKUP_RESOLVER的定义如下:

/* method lookup */
enum {
    LOOKUP_INITIALIZE = 1, 
    LOOKUP_RESOLVER = 2,
    LOOKUP_CACHE = 4, 
    LOOKUP_NIL = 8, 
};

lookUpImpOrNil方法的实现如下:

// behavior具备默认值0
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
    // 将传入的behavior和LOOKUP_CACHE、LOOKUP_NIL进行或操作
    // 代表会同时检索这几种类型的SEL
    return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}

lookUpImpOrForward实现如下:

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 供外部使用的imps
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;  
    Class curClass;
    
    // 不加锁的查找cache
    runtimeLock.assertUnlocked();
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // 上锁,开始搜索
    runtimeLock.lock();
    
    // 检测该类是否合法,此检查在流程启动期间非常昂贵
    checkIsKnownClass(cls);

    // swift环境下类初始化检测
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock可能已被删除,但现在再次被锁定
    }
    
    // 确保该类已被初始化,如果没有就调用类方法+initialize,这里也说明了为什么OC的类会在
    // 第一次接收消息后调用+initialize进行初始化,相反的,如果想要代码在类注册runtime的
    // 时候就运行,可以将代码写在+load方法里
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock可能已被删除,但现在再次被锁定
    }

    // 对消息查找和填充cache加锁,由于填充cache是写操作,所以需要对其加锁
    // 以免加入了category之后的cache被旧的cache冲掉,导致category失效。
    runtimeLock.assertLocked();
    curClass = cls;

    // 在该类及其父类中查找该SEL
    for (unsigned attempts = unreasonableClassCount();;) {
        // 当前类的方法列表
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        
        // 乐观缓存查找——从缓存方法列表中快速查找
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }

        // 如果超类链中存在循环,则暂停
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // 父类缓存的方法
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
            // 在超类中找到该方法。将其缓存在此类中
            goto done;
        }
    }

    // 找不到实现,尝试一次方法解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    // 解锁
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    // 返回查找到的imp
    return imp;
}

关于以上代码中出现的函数及宏定义标示符注解如下:

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls, nil);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        ASSERT(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}

static unsigned unreasonableClassCount()
{
    runtimeLock.assertLocked();

    int base = NXCountMapTable(gdb_objc_realized_classes) +
    getPreoptimizedClassUnreasonableCount();

    // Provide lots of slack here. Some iterations touch metaclasses too.
    // Some iterations backtrack (like realized class iteration).
    // We don't need an efficient bound, merely one that prevents spins.
    return (base + 1) * 16;
}

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}
转载时请注明“来自简书-EvenZhu”
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容