There are three full versions of this article:
- Ali, Byte: A set of efficient iOS interview questions (1 - runtime structure model - part 1)
- Ali, Byte: A set of efficient iOS interview questions (1 - runtime structure model - medium)
- Ali, Byte: A set of efficient iOS interview questions (1- runtime structure model-2)
3 methods
First look at the structure of the method:
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
};
复制代码
To put it simply, to shed the gorgeous coat, the method is realized by the name name
and type .types
imp
All method calls of OC are actually sending messages, which can be objc_msgSend
proved by clang -rewrite-objc main.m -o main.cpp
converting OC files to C++ files through , so the focus is objc_msgSend
on .
3.1 Method callobjc_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
复制代码
This is the source code objc_msgSend
in . For specific analysis, please refer to # objc-msg-arm64 source code in-depth analysis .
Why is it written in assembly?
- high speed
- Applicable to any number of parameters, any parameter type, and any return value type (C/C++, OC can't do it)
Going back objc_msgSend
, take a look at LGetIsaDone
the implementation when the method imp is not found in the cache __objc_msgSend_uncached
, and what does it look like?
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup ///+ 在方法列表中查找
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
复制代码
MethodTableLookup
In the search , look at the source code again:
.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 Method query
Well, you can lookUpImpOrForward
see , the incoming behavior is 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;
}
复制代码
The summary process is: 0. Preparatory operation: implement the class and execute the +initilize method
- Find methods along the inheritance chain (up to NSObject) with the priority of cache > method list
- If not found, press
LOOKUP_RESOLVER
the mark to try to call +resolveMethod to resolve and return directly - After finding the method, add the method to the cache and return
- 若未找到方法,按
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);
}
复制代码
- 如果是调用实例方法,则调用
+resolveInstanceMehtod:
尝试解决 - 如果是调用类方法,先调用
+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);
}
复制代码
- 首先确保类是否实现在
+resolveInstanceMethod:
方法,如果未实现则直接 return(但 NSObject 中会有默认实现) - 调用
+resolveInstanceMethod:
(可以再该方法内动态添加一个 IMP) - 再次通过原本的 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];
}
复制代码
好了总结一下:
- objc_msgSend
- NilOrTagged
- CacheLookup
- MethodTableLookup
- lookUpImpOrForward /// 找到 IMP 则调用
- resolveMethod,回调到 5
- forwardingTargetForSelector /// 找到“替死鬼”就让它处理
- methodSignatureForSelector
- forwardInvocation /// 让下一个“替死鬼”处理,没找到就崩溃
或者看一下大佬的图
4 其他
4.1 class_rw_t
与 class_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 均为对象。
看看搬运来的 描述上述结构体之间关系的类图 :
这就是 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 load
与 initialize
+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) |
Pass in an object to get its class, pass in a class object to get the metaclass |
Class object_setClass(id obj, Class cls) |
Set the type of the object and return the original type. This is how KVO is implemented by replacing isa |
BOOL object_isClass(id obj) |
Determine whether the incoming object is a class |
Class objc_getClass(const char *name) |
get class from string |
Class objc_getMetaClass(char *name) |
get metaclass from string |
char *class_getName(Class cls) |
get class name |
BOOL class_isMetaClass(Class cls) |
Determine whether the incoming object is a metaclass |
Class class_getSuperclass(Class cls) |
Get the parent class of the class |
size_t class_getInstanceSize(Class cls) |
Get the memory size occupied by this class instance |
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) |
Get the list of member variables of this class |
Method class_getInstanceMethod(Class cls, SEL name) |
Obtain the method structure of the instance method according to SEL |
Method class_getClassMethod(Class cls, SEL name) |
Obtain the method structure of the class method according to SEL |
IMP class_getMethodImplementation(Class cls, SEL name) |
According to the specific implementation of the strength method obtained by SEL IMP |
BOOL class_respondsToSelector(Class name, SEL name) |
Determine whether the instance object of this class can respond to the specified method |
Method *class_copyMethodList(Class cls, unsigned int *outCount) |
Get a list of instance methods of a class |
BOOL class_conformsToProtocol(Class cls, Protocol *protocol) |
Determine whether a class conforms to a specified protocol |
Protocol **class_copyProtocolList(Class cls, unsigned int *outCount) |
Get a list of protocols that a class conforms to |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) |
Get a list of properties of a class |
BOOL class_addMethod(Class cls, SEL name, IMP imp, char *types) |
Add a method to the class |
IMP class_replaceMethod(Class cls, SEL name, IMP imp, char *types) |
Implementation of the replacement method |
Class objc_allocateClassPair(Class superclass, char *name, size_t extraBytes) |
Dynamically create a new class and its metaclass, KVO uses |
void objc_registerClassPair(Class cls) |
Dynamically register classes objc_allocateClassPair created , used by KVO |
Class objc_duplicateClass(Class original, char *name, size_t extraBytes) |
Create a new class dynamically according to the original class, new class name and extra space, KVO is used, do not call directly |
void objc_disposeClassPair(Class cls) |
To destroy a class and its metaclass, it must be a objc_allocateClassPair created class, KVO is used |
SEL method_getName(Method m) |
Get the name of the specified method |
IMP method_getImplementation(Method m) |
Get the concrete implementation of the method |
char *method_getTypeEncoding(Method m) |
Get the type of method |
unsigned int method_getNumberOfArguments(Method m) |
Get the number of parameters of the method |
char *method_copyReturnType(Method m) |
Get the return type of the method |
char *method_copyArgumentType(Method m, unsigned int index) |
Get the type of the specified subscript parameter of the method |
void method_getReturnType(Methhod m, char *dst, size_t dst_len) |
Get the return type of the method, same asmethod_copyReturnType |
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) |
Get the method to specify the type of the parameters below, same asmethod_copyArgumentType |
IMP method_setImplementation(Method m, IMP imp) |
Set the specific implementation of the method and return to the original implementation |
void method_exchangeImplementations(Method m1, Method m2) |
Exchange the concrete implementation of two methods |
BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) |
Determine whether proto conforms to other |
IMP imp_implementationWithBlock(id block) |
Create an IMP by block |
id imp_getBlock(IMP anImp) |
Get the block imp_implementationWithBlock created |
BOOL imp_removeBlock(IMP anImp) |
Release the block imp_implementationWithBlock created |
id objc_loadWeak(id location) |
Get the original object pointed to by the weak reference |
id objc_storeWeak(id *localtion, id obj) |
store a new weak reference pointer into the list |
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) |
Add associated object |
id objc_getAssociatedObject(id object, void * key) |
Get the associated object corresponding to the key |
void objc_removeAssociatedObjects(id object) |
Remove all associated objects |