本文完好版共三篇:

  • 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 上)
  • 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 中)
  • 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 下)

3 办法

先看一下办法的结构:

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
};

简单来说,褪去富丽的外衣,办法由姓名name、 类型types, 完成imp

OC 一切的办法调用其实都是发音讯,经过 objc_msgSend 完成,能够经过 clang -rewrite-objc main.m -o main.cpp 将 OC 文件转换为 C++ 文件就能够证明这一点,所以要点就在 objc_msgSend 中了。

3.1 办法调用 objc_msgSend

    MSG_ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
    b.eq LReturnZero
#endif
    ldr p14, [x0] // p14 = raw isa
    GetClassFromIsa_p16 p14, 1, x0 // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq LReturnZero // nil check
    GetTaggedClass
    b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi d0, #0
    movi d1, #0
    movi d2, #0
    movi d3, #0
    ret
    END_ENTRY _objc_msgSend

这便是 arm64 环境下 objc_msgSend 的源码,详细解析能够查看 # objc-msg-arm64源码深入分析。

为什么是汇编编写呢?

  1. 速度快
  2. 至于任何参数个数,参数类型,回来值类型(C/C++, OC 都做不到)

回到 objc_msgSend,留意看一下 LGetIsaDone 完成未在 cache 查找到办法 imp 时的完成 __objc_msgSend_uncached,而它长什么姿态呢?

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup ///+ 在办法列表中查找
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

查找 MethodTableLookup 中,再看一下源码:

.macro MethodTableLookup
    SAVE_REGS MSGSEND  
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3,zai
    bl _lookUpImpOrForward ///+ 调用 lookUpImpOrForward
    // IMP in x0
    mov x17, x0
    RESTORE_REGS MSGSEND

3.2 办法查询

好了,能够看 lookUpImpOrForward 了,传入的 behavior 为 LOOKUP_INITIALIZE | LOOKUP_RESOLVER

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
  IMP imp = nil;
  Class curClass;
  lockdebug::assert_unlocked(&runtimeLock);
    ///+ 若该类还未 initilize 过,则疏忽缓存
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
    runtimeLock.lock();
    ///+ 经过 allocaltedClasses 检查该类是否已注册
    checkIsKnownClass(cls);
    ///+ 完成该类并履行 +initalize 办法
  cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    lockdebug::assert_locked(&runtimeLock);
  curClass = cls;
    ///+ 完成该类并履行 +initalize 办法
  cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
  // runtimeLock may have been dropped but is now locked again
  lockdebug::assert_locked(&runtimeLock);
  curClass = cls;
    for (unsigned attempts = unreasonableClassCount();;) {
    if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
      ///+ 从缓存中查找办法
      imp = cache_getImp(curClass, sel);
      if (imp) goto done_unlock;
      curClass = curClass->cache.preoptFallbackClass();
#endif
    } else {
      // curClass method list.
      ///+ 从本来办法列表中查找 imp
      method_t *meth = getMethodNoSuper_nolock(curClass, sel);
      if (meth) {
        imp = meth->imp(false);
        goto done;
      }  
      ///+ 切换为父类。若父类为空则将 imp 替换为 forward
      if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
        // No implementation found, and method resolver didn't help.
        // Use forwarding.
        imp = forward_imp;
        break;
      }
    }  
    // Halt if there is a cycle in the superclass chain.
    ///+ 若继承链中产生循环,停止履行
    if (slowpath(--attempts == 0)) {
      _objc_fatal("Memory corruption in class list.");
    }  
    // Superclass cache.
    ///+ 从父类缓存中查找
    imp = cache_getImp(curClass, sel);
    if (slowpath(imp == forward_imp)) {
      // Found a forward:: entry in a superclass.
      // Stop searching, but don't cache yet; call method
      // resolver for this class first.
      break;
    }
    if (fastpath(imp)) {
      // Found the method in a superclass. Cache it in this class.
      goto done;
    }
  }
    // No implementation found. Try method resolver once.
  ///+ 未找到 imp。测验 resolve
  ///+ 履行 +resolveInstanceMethod 办法
  if (slowpath(behavior & LOOKUP_RESOLVER)) {
        ///+ 去掉 LOOKUP_RESOVELER 符号
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);
  }
 done:
  if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { ///+ LOOK_NOCACHE 意味着不要缓存
#if CONFIG_USE_PREOPT_CACHES
        ///+ 同享缓存
    while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
      cls = cls->cache.preoptFallbackClass();
    }
#endif
    ///+ 将该办法增加至缓存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
  }
#if CONFIG_USE_PREOPT_CACHES
done_unlock:
#endif
  runtimeLock.unlock();
    ///+ 若行为符号为 LOOKUP_NIL 且未找到 IMP,则回来空,不进行 forward
  if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
    return nil;
  }
  return imp;
}

总结流程即为: 0. 预备操作:完成该类并履行 +initilize 办法

  1. 沿着继承链(一直到 NSObject)分别以 cache > 办法列表的优先级查找办法
  2. 若未找到,则按 LOOKUP_RESOLVER 符号测验调用 +resolveMethod 来处理且直接回来
  3. 找到办法后,将该办法增加至缓存并回来
  4. 若未找到办法,按 LOOKUP_NIL 回来 nil 或 forward

已然看了查找 imp 的办法,那就趁便看一下其他几个查找的办法:

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
  lockdebug::assert_unlocked(&runtimeLock);
    ///+ 假如类还未 initialize,则跳转至 loopUpImpOrForward
  if (slowpath(!cls->isInitialized())) {
    // see comment in lookUpImpOrForward
    return lookUpImpOrForward(inst, sel, cls, behavior);
  }  
    ///+ 从缓存中查找
  IMP imp = cache_getImp(cls, sel);
  if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
  if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
    imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
  }
#endif
    ///+ 若在缓存中未找到,则跳转至 loopUpImpOrForward
  if (slowpath(imp == NULL)) {
    return lookUpImpOrForward(inst, sel, cls, behavior);
  }  
done:
    ///+ 若行为符号为 LOOKUP_NIL 且未找到 IMP,则回来空
  if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
    return nil;
  }
  return imp;
}
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
  return _lookUpImpTryCache(inst, sel, cls, behavior);
}
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    ///+ 增加行为符号 LOOKUP_NIL(只查找实在 IMP 或 nil)
  return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

先看一下办法查找的进程:

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
  lockdebug::assert_locked(&runtimeLock);  
  ///+ 从该类中获取办法列表
  auto const methods = cls->data()->methods();
 
    ///+ 遍历每个办法列表,进行查找
  for (auto mlists = methods.beginLists(),
       end = methods.endLists();
    mlists != end;
    ++mlists)
  {
    method_t *m = search_method_list_inline(*mlists, sel);
    if (m) return m;
  }  
  return nil;
}
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
  int methodListIsFixedUp = mlist->isFixedUp();
  int methodListHasExpectedSize = mlist->isExpectedSize();  
  if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        ///+ 假如办法现已被排序过(load_image 中),则二分查找
    return findMethodInSortedMethodList(sel, mlist);
  } else {
        ///+ 不然线性查找
    if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
      return m;
  }
  return nil;
}
  • 在类中查找办法时,会从 instance.isa.data.rwe 中取出一切的办法列表数组(二维数组)
  • 然后遍历一切的办法列表,依据该类自身在完成时的情况(每个办法是否已依照办法名排序)进行二分查找或线性查找。

3.3 动态派发

再看一下 resolveMethod_locked

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
  lockdebug::assert_locked(&runtimeLock);
  runtimeLock.unlock();
  if (! cls->isMetaClass()) {
    // try [cls resolveInstanceMethod:sel]
    ///+ 一般类则调用 [cls resolveInstanceMethod:sel]
    resolveInstanceMethod(inst, sel, cls);
  }
  else {
    // try [nonMetaClass resolveClassMethod:sel]
    // and [cls resolveInstanceMethod:sel]
    ///+ 元类则先调用 [nonMetaClass resolveClassMethod:sel] ,
    resolveClassMethod(inst, sel, cls);
    if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
      ///+ 元类中未找到,则调用寻找实例办法
      resolveInstanceMethod(inst, sel, cls);
    }
  }  
  // chances are that calling the resolver have populated the cache
  // so attempt using it
  ///+ 再次查找并缓存本来的 imp
  ///+ 这儿答应找到 forward
  return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  1. 假如是调用实例办法,则调用 +resolveInstanceMehtod: 测验处理
  2. 假如是调用类办法,先调用 +resolveClassMehtod: 测验处理,若未处理则调用 +resolveInstanceMethod: 测验处理。

假如该类不是元类(调用实例办法时),经过 resolveInstanceMethod 测验 resolve

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
  lockdebug::assert_unlocked(&runtimeLock);
  SEL resolve_sel = @selector(resolveInstanceMethod:);
  ///+ 在类中查找 +resolveInstanceMethod:
    ///+ 将类作为实例在元类中查找:榜首个参数为 cls,第三个参数为元类
  ///+ 由于 +resolveInstanceMethod: 是类办法
  if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
    // Resolver not implemented.
        ///+ 未完成 +resolveInstanceMethod:
        ///+ 一般不会走到这儿,NSObject 中有默许完成
    return;
  }  
  ///+ 若类完成了 +resolveInstanceMethod: ,则将原始 sel 作为参数调用
  BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
  bool resolved = msg(cls, resolve_sel, sel);  
  // Cache the result (good or bad) so the resolver doesn't fire next time.
  // +resolveInstanceMethod adds to self a.k.a. cls
    ///+ 再次查找并缓存本来的 imp
    ///+ 这儿仅查找实在 IMP 或 nil
  IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
  1. 首要保证类是否完成在 +resolveInstanceMethod: 办法,假如未完成则直接 return(但 NSObject 中会有默许完成)
  2. 调用 +resolveInstanceMethod: (能够再该办法内动态增加一个 IMP)
  3. 再次经过本来的 sel 查找 IMP 并缓存(假如在第二步中增加了对应的 IMP 则能够找到)

若该类是元类(调用类办法),经过 resolveClassMethod > resolveInstanceMethod 的优先级测验 resolve:

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
  lockdebug::assert_unlocked(&runtimeLock);
  ///+ 首要测验在元类中查找缓存 resolveClassMethod: 办法的结果
  if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
    // Resolver not implemented.
    ///+ 未完成 +resolveClassMethod:
    ///+ 但 NSObject 中有默许完成
    return;
  }  
  Class nonmeta;
  {
    mutex_locker_t lock(runtimeLock);
        ///+ 获取原始的类目标,由于 +resolveClassMethod: 音讯是要发送给类目标的
    ///+ 里边有个特别情况,关于根类来说,他的元类便是自身,比如 NSObject
    nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    // +initialize path should have realized nonmeta already
    if (!nonmeta->isRealized()) {
      _objc_fatal(...);
    }
  }
  ///+ 调用 +resolveClassMethod:
  BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
  bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
  // Cache the result (good or bad) so the resolver doesn't fire next time.
  // +resolveClassMethod adds to self->ISA() a.k.a. cls
  ///+ 再次查找并缓存本来的 imp
    ///+ 这儿仅查找实在 IMP 或 nil
  IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
    ...
    打印
    ...
}

大致逻辑与 +resolveInstanceMethod: 相同,有一点比较重要。这儿额定做了一个获取原始类目标的操作,由于传入的 cls 可能是类目标,也可能是元类目标,而 +resolveClassMethod: 这个音讯是要发送给类目标,而不是元类目标。(只不过最终是在元类中查找该办法,但音讯仍是发送给原始类目标的)

resolveMethod_nolock 中 cls 是元类来看,调用一个类的类办法时,若该类办法未完成且没有完成 +resolveClassMethod: ,最终会调用到根类的同名实例办法

为什么是根类呢?由于根类的元类便是其自身。验证一下:

@interface SomeObject : NSObject
+ (void)sameMethod;
@end
@@implementation SomeObject
@end
@interface NSObject (Cat)
@end
@@implementation NSObject (Cat)
- (void)sameMethod {
    NSLog(@"履行了实例办法:%s", __FUNCTION__);
}
@end

测验调用一下 [SomeObject sameMethod] 看下结果。

当然,除了 objc_msgSend 外,还有其他好几个类似的办法,比较特别且重要的便是 objc_msgSendSuper。先对比下两者的揭露定义:

id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

很明显,除了办法名之外,榜首个参数(也便是音讯接纳者)不同。看一眼 objc_super 是什么:

/// Specifies the superclass of an instance.
struct objc_super {
  /// Specifies an instance of a class.
    ///+ 音讯接纳者
  __unsafe_unretained _Nonnull id receiver;  
  /// Specifies the particular superclass of the instance to message.
    ///+ 指定接纳音讯的特定父类,是继承链的直接父类
  __unsafe_unretained _Nonnull Class super_class;
};

一个误区

为一个什么都没有的类增加一个 init 办法:

@implementation SomeObject
- (instancetype)init {
    // self = [super init]; // 为了让代码更少,这行暂时不要
    NSStringFromClass([super class]));
  return self;
}
@end

运用 clang -rewrite-objc SomeObject.m -o SomeObject.cpp 重写成 C++,只看 找到该办法的定义:

static instancetype _I_SomeObject_init(SomeObject * self, SEL _cmd) {
  ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SomeObject"))}, sel_registerName("class"));
  return self;
}

仔细看一下 objc_msgSenSuper 的榜首个参数是什么?

(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SomeObject"))}

改写一下:

(struct objc_super){
    self, 
    SomeObject.superclass
}

此刻,音讯接受者其实依然是 self ,也便是调用途的这个目标自身。只不过榜首音讯响应类不是 SomeObject。

所以,这儿在查找办法时,完全能够改写成方才咱们看过的 lookUpImpOrForward 格局

lookUpImpOrForward(self, @selector(class), SomeObject.superclass, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

此刻接纳音讯的依然是 self。所以这儿拿到的依然是 SomeObject,而不是它的父类。

3.4 音讯转发

3.1 办法调用 查找办法时,有一个 _objc_msgForward_impcache。在未找到对应 sel 的办法且 behavior 未符号为 LOOKUP_NIL 时的回来。

从字面意思看,音讯转发。还在是 objc-msg-arm64.s 中,依然是汇编编写:

STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
///+ 无结构体特别版本
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
///+ 继续到这儿
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward

好了,能够去找 __objc_forward_handler 了:

__attribute__ ((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
  _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
        "(no message forward handler is installed)",
        class_isMetaClass(object_getClass(self)) ? '+' : '-',
        object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
  _objc_forward_handler = fwd;
}

先看一下 objc_defaultForwarHandler 办法内部完成。unrecognized selector sent to instance,嗯这个提示很熟悉,调用未完成的办法便是这报错。

再看一眼下边的 objc_setForwarHandler,跟 uncaught_handler 一样,咱们只需设置好这个 handler 就能够愉快地转发音讯了。

BUT~当我设置之后,并打印不出来任何堆栈信息。看来 Apple 搞了幺蛾子

已然这儿搞不定,那就回到 Apple 揭露的手法上:

- (id)forwardingTargetForSelector:(SEL)aSelector;
+ (id)forwardingTargetForSelector:(SEL)aSelector;

一者为实例办法,二者为类办法,都是为了找一个“替死鬼”来处理这个音讯,例如这样:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector("unimplementedMethod") {
        return otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector("unimplementedMethod") {
        return OtherClass;
    }
    return [super forwardingTargetForSelector:aSelector];
}

假如这儿没有“替死鬼”回来了 nil,还有最终一次机会:runtime 会向目标 -methodSignatureForSelector: 音讯,先拿到办法的完好签名生成 NSMethodInvocation,然后将其包装成一个 NSInvocation,然后再向目标发送 forwardInvocation: 音讯,咱们就能够处理这个 invocation 了:

- (void)forwardInvocation:(NSInvocation *)invocation;
+ (void)forwardInvocation:(NSInvocation *)invocation;

依然是实例办法与类办法共存:

- (void)forwardInvocation:(NSInvocation *)invocation
    if ([invocation selector] == @selector(unimplementedMethod)) {
        [invocation invokeWithTarget:otherObject];
        return;
    }
    return [super forwardInvocation:invocation];
}
+ (void)forwardInvocation:(NSInvocation *)invocation
    if ([invocation selector] == @selector(unimplementedMethod)) {
        [invocation invokeWithTarget:otherClass];
        return;
    }
    return [super forwardInvocation:invocation];
}

好了总结一下:

  1. objc_msgSend
  2. NilOrTagged
  3. CacheLookup
  4. MethodTableLookup
  5. lookUpImpOrForward /// 找到 IMP 则调用
  6. resolveMethod,回调到 5
  7. forwardingTargetForSelector /// 找到“替死鬼”就让它处理
  8. methodSignatureForSelector
  9. forwardInvocation /// 让下一个“替死鬼”处理,没找到就溃散

或者看一下大佬的图

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 下)

4 其他

4.1 class_rw_tclass_ro_t

首要从姓名上就能看出来,class_rw_t 是可读可写的,class_ro_t 是只读的。

先看一下 class_ro_t

struct class_ro_t {
  uint32_t flags;
  uint32_t instanceStart; ///+ 实例其实地址
  uint32_t instanceSize; ///+ 实例占用空间巨细
#ifdef __LP64__
  uint32_t reserved;
#endif  
  union {
    const uint8_t * ivarLayout; ///+ 实例变量布局。这表明成员变量在目标内存空间内占用的内存块
    Class nonMetaclass; ///+ 非元类
  };  
  explicit_atomic<const char *> name; ///+ 类名
  WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods; ///+ 编译期间就确认的办法(不包括分类中的)
  protocol_list_t * baseProtocols; ///+ 编译期间就确认的协议
  const ivar_list_t * ivars; ///+ 成员变量列表  
  const uint8_t * weakIvarLayout; ///+ 弱引证成员变量布局。这表明弱引证成员变量在目标内存空间内占用的内存块
  property_list_t *baseProperties; ///+ 编译期间就确认的特点
    ... 
    ...
}

再看一眼 class_rw_t

struct class_rw_t {
  uint32_t flags;
  uint16_t witness;
  uint16_t index; ///+ realizeClassWithoutSwift 中经过 chooseClassArrayIndex 指定
    explicit_atomic<uintptr_t> ro_or_rw_ext; ///+ 指针联合体:class_ro_t 或者 class_rw_ext_t
  Class firstSubclass; //+ 子类
  Class nextSiblingClass; //+ 兄弟类
}

很明显,没有太多要点,那看一下 class_rw_rext_t

struct class_rw_ext_t {
  DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
  class_ro_t_authed_ptr<const class_ro_t> ro; ///+ class_ro_t 指针。realizeClassWithoutSwift 中拓荒 rw 时设置
  method_array_t methods; ///+ 一切办法列表
  property_array_t properties; ///+ 一切特点
  protocol_array_t protocols; ///+ 一切特点
  const char *demangledName; ///+ 姓名
  uint32_t version;
};

这儿仅仅看一下这些结构体罢了,真实初始化是在 2 类是如何被加载的 中的。

4.2 目标

id 是一个通用 OC 目标。那么 id 又是什么呢?

typedef struct objc_object *id;
struct objc_object {
private:
  char isa_storage[sizeof(isa_t)];
    ...
    ...
}

也便是说,id 便是一个 objc_object 结构体指针,其内部仅存储一个 isa_t 结构体(可查看 **1.2 isa)。

再看一下类:

typedef struct objc_class *Class;
struct objc_class : objc_object {
    // Class ISA;
    Class superclass; /// 父类指针
    cache_t cache; /// 缓存该类现已用过的函数指针
    class_data_bits_t bits; /// 存储该类的办法、特点、遵循的协议列表等
}

也便是说,类自身也是一个目标。

其实,OC 中万物是目标,block、protocol 均为目标。

看看转移来的 描绘上述结构体之间联系的类图 :

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 下)

这便是 OC 中各类结构体之间的联系。

4.3 metaclass

什么是 metaclass ? 2 类是如何被加载的 有这样一些代码:

///+ 在 realizeClassWithSwift 办法中
///+ 完成其元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
cls->initClassIsa(metacls);
///+ 在 load_categories_nolock 办法中
///+ 将协议中的类办法,类特点附加到元类中
if (cat->classMethods || cat->protocols
    || (hasClassProperties && cat->_classProperties)) {
    attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
}

能够看到,class 的 isa 指向其元类,而协议中的类办法与类特点是增加到元类中的。再结合 3 办法调用,在进行办法查找时,实例办法在类的办法列表中查找,类办法在元类的办法列表中查找。这两者查找办法是不是完全相同,都是在 instance.isa.data.methods 中进行查找,两者一致了。

所以,元类的存在便是为了音讯转发机制的统一性而设计的

4.4 loadinitialize

  • +load

2 类是如何加载的 中有一个流程 load_image,其首要效果便是履行 +load 办法。

该流程是在 main 函数之前履行的,它会查找一切类与分类的 +load 办法并履行,先类再分类。

并且是依照 父类 > 子类 的次序递归履行的,也便是说,+load 办法最好不要调用 super,假如调用了那么父类中的流程将会履行很屡次。

关于没有继承联系的类来说,其 +load 的调用次序取决于 Compile Sources 的排列次序。category 的 +load 办法的调用次序也是如此。

  • +initialize

而在 3 办法 中有一个流程 lookUpImpOrForward,其首要效果是查找办法的 imp。

其中有这样一句:cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); ,这一个句的首要效果便是调用 +initialize 办法。

来看一下 realizeAndInitializeIfNeeded_locked

static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
  lockdebug::assert_locked(&runtimeLock);
  ///+ 假如类还未完成,则完成它。内部调用 realizeClassWithoutSwift
  if (slowpath(!cls->isRealized())) {
    cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
  }  
  if (slowpath(initialize && !cls->isInitialized())) {
    ///+ 初始化该类
    cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // If sel == initialize, class_initialize will send +initialize and
    // then the messenger will send +initialize again after this
    // procedure finishes. Of course, if this is not being called
    // from the messenger then it won't happen.
        ///+ 假如当时接纳到的音讯便是 +initialize,再完成该流程之后会再次发送 +initialize 音讯。
  }
  return cls;
}

嗯,继续往里边走

static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
  return initializeAndMaybeRelock(cls, obj, lock, true);
}

再往里边走:

static Class initializeAndMaybeRelock(Class cls, id inst,
                   mutex_t& lock, bool leaveLocked)
{
  lockdebug::assert_locked(&lock);
    ///+ 假如现已初始化过了,则疏忽
  if (cls->isInitialized()) {
    if (!leaveLocked) lock.unlock();
    return cls;
  }  
  // Find the non-meta class for cls, if it is not already one.
  // The +initialize message is sent to the non-meta class object.
  ///+ 获取原始的类
  Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);  
  // Realize the non-meta class if necessary.
  if (nonmeta->isRealized()) {
    ///+ nometa 是一个类,现已被完成了,所以这不需要做任何事情
    lock.unlock();
  } else {
    ///+ 假如该类还没完成,那么就完成它:内部会调用 realizeClassWithoutSwift
    nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
    ///+ 获取 nometa 的类
    cls = object_getClass(nonmeta);
  }
  ///+ 初始化这个原始的类,调用 +initialize
  initializeNonMetaClass(nonmeta);  
  if (leaveLocked) runtimeLock.lock();
  return cls;
}

看一下简化版的 initializeNonMetaClass

void initializeNonMetaClass(Class cls)
{
  Class supercls;
  bool reallyInitialize = NO;
  // Make sure super is done initializing BEFORE beginning to initialize cls.
  // See note about deadlock above.
  ///+ 来初始化该类之前,一定要保证父类现已完成初始化操作
  supercls = cls->getSuperclass();
  if (supercls && !supercls->isInitialized()) {
    ///+ 递归调用,传入父类
    initializeNonMetaClass(supercls);
  }
 
 
    monitor_locker_t lock(classInitLock);
    if (!cls->isInitialized() && !cls->isInitializing()) {
        ///+ 符号该类正在初始化 RW_INITIALIZING
        cls->setInitializing();
        reallyInitialize = YES;
    }
 
  if (reallyInitialize) {
    // We successfully set the CLS_INITIALIZING bit. Initialize the class.
    ///+ 现已成功将该类符号为正在初始化。履行初始化操作。
    // Record that we're initializing this class so we can message it.
    ///+ 记载当时线程正在初始化该类
    _setThisThreadIsInitializingClass(cls);
        ///+ 在答应多进程 fork() 履行的情况下,子线程完成后能够直接符号为已初始化  
    if (MultithreadedForkChild) {
      // LOL JK we don't really call +initialize methods after fork().
      performForkChildInitialize(cls, supercls);
      return;
    }
    // Exceptions: A +initialize call that throws an exception
    // is deemed to be a complete and successful +initialize.
    ///+ 抛出反常的 +initialize 调用才被认为是一次完好且成功的的
    @try
    {
      ///+ 调用 +initialize。
      ///+ 这儿发送 @selector(initialize) 音讯,会再走一次这个流程
      callInitialize(cls);
    }
    @catch (...) {
      @throw;
    }
    @finally
    {
      // Done initializing.
      ///+ 完成初始化操作
      lockAndFinishInitializing(cls, supercls);
    }
    return;
  }
  else if (cls->isInitializing()) {
    ...
  }
  else if (cls->isInitialized()) {
    ...
  }
  else {
    ...
  }
}

从上边源码能够看到,在调用某个类的 +initialize 之前,会先调用其父类的 +initialize 办法。

好,回想一下: lookUpImpOrForward 是在接纳音讯时才会走到的流程,这就意味着 +initialize 是在类接纳音讯才会调用的。

另外,在将分类附加到的进程中,会将分类中的一切办法插入到类办法列表的最前边。这就意味着分类中的 +initialize 办法会掩盖类自身的 +initialize 办法,其掩盖次序也取决的他们在 Compile Sources 中的次序,后者掩盖前者。

4.5 一些可能会用的 runtime 办法

依照 runtime.h 排序:

function note
Class object_getClass(id obj) 传入目标获取其类,传入类目标则获取元类
Class object_setClass(id obj, Class cls) 设置目标的类型,回来本来的类型,KVO 便是这样经过替换 isa 完成的
BOOL object_isClass(id obj) 判别传入目标是否是类
Class objc_getClass(const char *name) 依据字符串获取类
Class objc_getMetaClass(char *name) 依据字符串获取元类
char *class_getName(Class cls) 获取类名
BOOL class_isMetaClass(Class cls) 判别传入目标是否是元类
Class class_getSuperclass(Class cls) 获取类的父类
size_t class_getInstanceSize(Class cls) 获取该类实例占用的内存巨细
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 获取该类的成员变量列表
Method class_getInstanceMethod(Class cls, SEL name) 依据 SEL 获取实例办法的 method 结构体
Method class_getClassMethod(Class cls, SEL name) 依据 SEL 获取类办法的 method 结构体
IMP class_getMethodImplementation(Class cls, SEL name) 依据 SEL 获取实力办法的详细完成 IMP
BOOL class_respondsToSelector(Class name, SEL name) 判别该类实例目标是否能响应指定办法
Method *class_copyMethodList(Class cls, unsigned int *outCount) 获取类的实例办法列表
BOOL class_conformsToProtocol(Class cls, Protocol *protocol) 判别类是否契合指定协议
Protocol **class_copyProtocolList(Class cls, unsigned int *outCount) 获取类契合的协议列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) 获取类的特点列表
BOOL class_addMethod(Class cls, SEL name, IMP imp, char *types) 向类中增加一个办法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, char *types) 替换办法的完成
Class objc_allocateClassPair(Class superclass, char *name, size_t extraBytes) 动态创立一个新的类及其元类,KVO 用到
void objc_registerClassPair(Class cls) 向 runtime 动态注册由 objc_allocateClassPair 创立的类,KVO 用到
Class objc_duplicateClass(Class original, char *name, size_t extraBytes) 依据原始类,新类名以及额定空间动态创立一个新类,KVO 用到,不要直接调用
void objc_disposeClassPair(Class cls) 销毁一个类及其元类,必须是 objc_allocateClassPair 创立的类,KVO 用到
SEL method_getName(Method m) 获取指定 method 的姓名
IMP method_getImplementation(Method m) 获取 method 的详细完成
char *method_getTypeEncoding(Method m) 获取 method 的类型
unsigned int method_getNumberOfArguments(Method m) 获取 method 的参数数量
char *method_copyReturnType(Method m) 获取 method 的回来类型
char *method_copyArgumentType(Method m, unsigned int index) 获取 method 指定下标参数的类型
void method_getReturnType(Methhod m, char *dst, size_t dst_len) 获取 method 的回来类型,同 method_copyReturnType
void method_getArgumentType(Method m, unsigned int index,char *dst, size_t dst_len) 获取 method 指定下边参数的类型,同 method_copyArgumentType
IMP method_setImplementation(Method m, IMP imp) 设置 method 的详细完成,回来本来的完成
void method_exchangeImplementations(Method m1, Method m2) 交流两个 method 的详细完成
BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) 判别 proto 是否契合 other
IMP imp_implementationWithBlock(id block) 经过 block 创立一个 IMP
id imp_getBlock(IMP anImp) 获取由 imp_implementationWithBlock 创立时的 block
BOOL imp_removeBlock(IMP anImp) 开释由 imp_implementationWithBlock 创立时的 block
id objc_loadWeak(id location) 获取弱引证指向的原始目标
id objc_storeWeak(id *localtion, id obj) 将一个新的弱引证指针存储到列表中
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) 增加相关目标
id objc_getAssociatedObject(id object, void * key) 获取对应 key 的相关目标
void objc_removeAssociatedObjects(id object) 移除一切相关目标