1、编译时
编译阶段,会通过编译器将语言转化成机器码,然后直接在CPU上执行机器码,效率
更高.
OC便是使用编译器进行处理,生成可执行文件;
而像Python等语言则是使用解释器,在运行时解释执行代码,将代码翻译成目标代
码,然后再一句一句的去执行目标代码,所以相对来说效率就低,但是跑起来后不用
重启编译代码,直接修改代码就可以看到效果。
编译过程的详细信息请移步
2、为什么说OC是动态语言
静态语言
,是在编译期已经决定了一切,在运行时只是遵守在编译器确定好的指令
在进行而已;
动态语言
,只是在编译期做语义语法的检查,而不会分配内存,在编译期只关心对
象是否调用这个方法,而不会关心这个对象是否为nil, 也不会关心这个方法的具体
实现以及这个对象的本质是否能调用该方法,更不会关心有没有这个方法实现,这些
事情都是在运行时进行的.
OC的动态性体现在三个方面:
动态类型
,动态绑定
和动态加载
-
动态类型
,在运行时才确定对象的类型。如'NSString *data = [[NSData alloc] init]' 编译时是字符串类型,运行时确定真正类型是data类型 -
动态绑定
,基于动态类型,在对象被确定后,其类型也被确定了,则该对象对应的属性和响应小时也就确定了 -
动态加载
,根据需求加载所需资源,让程序在运行时添加代码模块以及其他资源, 用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件
3、对象的底层结构
isa
指针,实例对象通过它的isa
指针找到它的类,类对象通过它找到它的元类,元类指向根类
isa
指针确定所属类,super_class 确定继承关系。
/// 实例对象的底层结构
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// 类对象的底层结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
};
/// isa
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
/// 对象结构
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
uintptr_t overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
uintptr_t rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
size_t sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
};
从上面的结构体信息,可以知道,每个对象的本质都是一个结构体,都有一个isa指
针指向对应的类
4、消息发送的基础数据
SEL
,方法选择器,是一个方法的selector
指针,用于表示运行时下方法的名称,定义如下:
typedef struct objc_selector *SEL;
1、底层维护一张选择子表(
SEL
)
2、OC编译时,会依据每个方法的名称和参数生成一个唯一整型标识(int类型的地
址),这个标识就是SEL
3、两个类之间,不管有没有关系,只要方法名相同,对应的SEL地址是一样的,所以
在OC同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也
不行(同一个类使用相同的方法名,即便参数不同,编译时也会报错)
4、不同类方法名可以相同,因为方法查找会根据SEL从各自的类方法列表中查询对应
的IMP
/// SEL方法调用
// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );
/*
向Runtime系统中注册一个方法,如果已注册则直接返回对应的选择器,如果未注册,
则将方法名映射到一个选择器,并返回这个选择器
*/
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );
// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
IMP
函数指针,指向方法实现的首地址
id (*IMP)(id, SEL, ...)
Method
表示类定义中的方法
struct objc_method {
/// 方法名
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
/// 方法类型
char * _Nullable method_types OBJC2_UNAVAILABLE;
/// 方法实现
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
/// Method 方法调用
// 调用指定方法的实现,返回的是实际实现的返回值。参数receiver不能为空。这个方法的效率会比method_getImplementation和method_getName更快
id method_invoke ( id receiver, Method m, ... );
// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
// 获取方法名
SEL method_getName ( Method m );
// 返回方法的实现
IMP method_getImplementation ( Method m );
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );
// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );
5、消息发送机制
消息发送流程:
- 1、编译器将消息发送转化成
objct_msgSend(id , SEL, ...)
,通过汇编代码
一步步处理最终会走到这个方法lookUpImpOrForward
(最新的源码) - 2、先判断当期那对象是否为nil,是则直接返回nil
- 3、在运行时去执行时,该对象(实例或类)会先通过isa指针指向对应的类(类或元
类) - 4、通过
SEL
签名在cache_t
中进行查找,有则返回 - 5、没有再去
method_list
中进行查找,有则添加到缓存中,并回调 - 6、没有就通过
super_class
找到父类,继续进行查找,一直循环遍历,直到根类NSObject
- 7、以上都未能找到,则进行动态解析,最后进入消息转发
异常提示IMP:
__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);
}
/// 截取部分汇编源码,可以看到lookUpImpOrForward调用的基本信息
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
/*
_objc_msgForward_impcache 这里通过汇编发现最终执行了
objc_defaultForwardHandler方法调用,主要用来进行异常回调
*/
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
///添加线程锁,防止多线程操作
runtimeLock.assertUnlocked();
//如果当前的行为是查找缓存,直接查找当前方法缓存
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
/// 类的懒加载(为查找方法做准备条件,如果类没有初始化时,初始化类和父类、元
/// 类等),对是否有swift进行不同的处理
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
/// 类的初始化操作
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
/// 循环遍历类-父类进行查找
for (unsigned attempts = unreasonableClassCount();;) {
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 查找当前类中的方法列表,如果方法存在,则跳转到缓存插入处理
imp = meth->imp;
goto done;
}
/// 对当前类进行重新赋值,查找父类
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver did not 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 do not cache yet; call method
// resolver for this class first.
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;
}
return imp;
}
- 从上面
lookUpImpOrForward
方法查找过程中,我们可以看到对类或者父类方法列表
进行方法查询用到了getMethodNoSuper_nolock
这个方法: -
getMethodNoSuper_nolock
,对search_method_list_inline
进行遍历,查询对应的方法
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->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;
}
-
search_method_list_inline
,在所有方法列表中(自身,categorys)使用二分法或遍历逐一寻找以name属性值为sel的method_t(Method),如果找到,以sel为键把method存入cache_t, 直接执行mehtod里的IMP;
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
// 有序的话 进行二分查找(折半遍历查找)
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 无序则进行线性遍历查找
for (auto& meth : *mlist) {
/// 对比方法名的地址
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
-
findMethodInSortedMethodList
,二分查找的具体实现
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 分类与类的同名方法,分类会放到最前面,所以这里进行'--'操作后,进行往前查找
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
方法缓存相关内容请移步
6、消息转发
-
动态方法解析
resolveMethod_locked
/// 如果这个类不是元类则执行__class_resolveInstanceMethod,如果是元类
/// 的话则执行__class_resolveClassMethod,所以如果是类方法的话执行resolveClassMethod,
/// 那么会一直查找最后还会执行一次resolveinstancemethod,但是这个方法是
/// 执行的元类的resolveinstancemethod而不是类的,因为类是元类对象如果
/// 元类找不到就会往上层查找,元类的上层是根元类,根元类的父类指向NSObject,
/// 所以最后还会执行一次resolveInstanceMethod最终的实例方法。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
1、尝试调用自身的_class_resolveMethod动态为类对象或元类对象里添加方法实
现。如果成功添加了method,记录已经添加过
2、看是否在resolveClassMethod 或者 resolveInstanceMethod方法中为sel
提供了动态方法决议(可通class_addMethod方法添加),如果没有提供则进入下
一步
3、解析完成后继续调用lookUpImpOrForward完成以下操作
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
-
备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
该方法只能让我们把消息转发到另一个能处理这个消息的对象,但是无法处理消息
的内容,比如参数和返回值该方法用于把消息转发到另外一个可以处理该消息的对象,如果该
aSelector
在
另一个对象中实现了,则该对象就作为该消息新的接收值,直接返回该对象。-
完整消息转发
运行时系统在这一步让消息接收者将消息转发给其他对象。当前对象会将对应的
sel -ector
,目标(target
)和参数打包成anInvocation
对象转发给其他对象
消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([XXX instancesRespondToSelector:aSelector]) {
signature = [XXX instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
forwardInvocation
:方法的实现有两个任务:
(1)定位可以响应封装在anInvocation
中的消息的对象。这个对象不需要能处理所
有未知消息。
(2)使用anInvocation
作为参数,将消息发送到选中的对象。anInvocation
将
会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
XXX *x = [XXX new];
if ([XXX instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:x];
}
}
如果以上均未实现,则无法识别
selector
,并抛出异常
7、runtime实践:
1、获取对象的属性、成员变量、方法和协议列表
2、通过消息转发机制进行方法的拦截
3、动态添加方法(class_addMethod
)
4、方法的动态交换(method-swizzling
)
5、将当前类与自定义类建立映射(Class object_setClass(id obj, Class cls
),由runtime的说明可知该函数的作用是为一个对象设置一个指定的类,object内部有一个叫做isa
的变量指向它的class
。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的class)
例:object_setClass([NSBundle mainBundle],[ModifyBundle class])
,
NSBundle与ModifyBundle建立映射关系,将bundle的isa指针指向[ModifyBundle class]类,在自定义Bundle类中重写加载bundle
对应的加载自建的XML类文件、加载xib storyboard plist等文件、加载本地多
语言文件(.strings)等方法,实现全局的多语言处理
8、疑难解析
为什么要设计元类
如果没有元类,则类对象的信息和实例对象的信息都会存储在类中,这样对于查找对
应的信息,会更加复杂且混乱(如类方法和实例方法一样的情况下,需要区分查找对
象的类型)依次类推,实例对象的方法列表在类对象的结构体中,如果直接放在实例对象中,那
么同一个类对象可能生成多个实例对象,这些实例对象调用对应的方法时,内存将会被
爆掉的综上,元类的设计符合单一原则和可扩展性,实例对象管理自己的属性信息,实例对
象的方法列表由类管理,类对象方法列表由元类管理,各司其职,减少了对于对象类
型和方法类型的判断,增加了查找效率;不同种类的方法走同一套流程,大大节约了
维护成本。