Runtime 相关知识笔记
- isa详解
struct objc_object {
private:
isa_t isa;
}
union isa_t // arm64 架构
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1; // 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址1,代表优化过,使用位域存储更多的信息
uintptr_t has_assoc : 1; // 是否有过关联对象
uintptr_t has_cxx_dtor : 1; // 是否有C++析构函数
uintptr_t shiftcls : 33; // 存储Class、Meta-Class的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否有弱引用
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t extra_rc : 19; // 里面存储的值是引用计数减1
};
}
- Class的结构内容分析
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
struct class_rw_t {
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
}
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
struct class_ro_t {
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
property_list_t *baseProperties;
};
method_t是对方法\函数的封装
struct method_t {
SEL name; // 函数名
const char *types; // 编码(返回值类型、参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
typedef struct objc_selector *SEL;
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
1. 可以通过@selector()和sel_registerName()获得
2. 可以通过sel_getName()和NSStringFromSelector()转成字符串
3. 不同类中相同名字的方法,所对应的方法选择器是相同的
Class底层结构图
Type Encoding
Code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object |
# | A class object(Class) |
: | A method selector (SEL) |
- 方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; // 散列表长度 - 1
mask_t _occupied; // 已经缓存的方法数量
}
struct bucket_t {
private:
cache_key_t _key; // SEL作为key
IMP _imp; // 函数的内存地址
}
// 缓存查找的源码
objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
- objc_msgSend的执行流程
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段:
消息发送、动态方法解析、消息转发
objc_msgSend执行流程 – 源码跟读
objc-msg-arm64.s
1. ENTRY _objc_msgSend
2. b.le LNilOrTagged
3. CacheLookup NORMAL
4. .macro CacheLookup
5. .macro CheckMiss
6. STATIC_ENTRY __objc_msgSend_uncached
7. .macro MethodTableLookup
8. __class_lookupMethodAndLoadCache3
objc-runtime-new.mm
1. _class_lookupMethodAndLoadCache3
2. lookUpImpOrForward
3. getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
4. cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
5. _class_resolveInstanceMethod
6. _objc_msgForward_impcache
objc-msg-arm64.s
1. STATIC_ENTRY __objc_msgForward_impcache
2. ENTRY __objc_msgForward
Core Foundation
__forwarding__(不开源)
阅读流程:objc-msg-arm64.s
1、首先在objc-msg-arm64.s找到ENTRY _objc_msgSend 方法,比较cmp x0, #0,如果x0为空,则return(x0是消息接收者)
2、然后查看CacheLookup NORMAL的实现找到.macro CheckMiss,然后调用__objc_msgSend_uncached,最后查看MethodTableLookup的实现,发现调用__class_lookupMethodAndLoadCache3方法,该方法的实现在objc-runtime-new.mm中
objc-runtime-new.mm代码流程:
1、首先查看lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/)方法的实现
2、其次调用cache_getImp在方法缓存中查找方法,如果没有在方法列表中查找->getMethodNoSuper_nolock获取方法列表->search_method_list->findMethodInSortedMethodList查找方法-> 如果找到方法写入缓存log_and_fill_cache
3、如果没有找到方法就在super的方法缓存查找方法(Class curClass = cls->superclass, cache_getImp),如果找到该方法写入子类方法缓存中(log_and_fill_cache),如果没有找到则在父类方法列表中查找(getMethodNoSuper_nolock),如果找到则写入子类方法缓存中
4、如果没有找到方法的实现则会调用_class_resolveMethod(实际上是查看对象是否实现了resolveInstanceMethod),如果实现动态解析,则会触发以上的方法查找流程
5、如果没有则会调用_objc_msgForward_impcache(实际上是看对象是否实现forwardingTargetForSelector方法),触发消息转发流程
// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸对象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已经在 Runtime 注册过
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
objc_msgSend消息发送流程:
1、首先从消息接收对象(receiver)的cache方法列表中查找,如果没有找到
2、再从该对象中的方法列表(class_rw_t)中查找,如果找到缓存到当前对象的cache方法列表中,如果没有找到
3、再从接收对象的父类中的cache方法列表中查找,如果找到,将方法缓存到对象的cache列表中,如果没有找到
4、再从该接收对象的父类的方法列表中查找,如果找到就缓存到该对象的cache方法列表中,如果没有找到
5、触发方法解析流程
如果是从class_rw_t中查找方法
已经排序的,二分查找
没有排序的,遍历查找
receiver通过isa指针找到receiverClass
receiverClass通过superclass指针找到superClass
objc_msgSend的动态方法解析流程:
1、判断该方法是否动态解析,如果解析就出发消息转发,如果没有
2、再调用resolveInstanceMethod: 或者resolveClassMethod:方法来动态解析
3、标记为已经动态解析然后重新走消息发送流程
objc_msgSend的消息转发流程:
1、调用forwardingTargetForSelector,如果返回值不为空,则触发消息发送流程,如果返回值为空
2、则调用methodSignatureForSelector,如果返回nil,则触发doesNotRecognizeSelector异常,如果返回值不为nil
3、则调用forwardInvocation方法(方法中自定义任何逻辑)
消息转发流程
void testIMP (void){
NSLog(@"testIMP invoke");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
NSLog(@"resolveInstanceMethod:");
// 第一种。利用Runtime动态添加方法实现,返回yes,
//class_addMethod([self class], @selector(test), (IMP)testIMP, "v@:");
//return YES;
// 第二种返回NO
return NO;
}
return [super resolveInstanceMethod:sel];
}
// 查看有没有其他对象处理该消息
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
NSLog(@"forwardingTargetForSelector:");
// 返回给其他实现了 test 方法的对象处理(在.h中没有定义test方法也可以)
// Other * other = [[Other alloc] init];
// return other;
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
NSLog(@"methodSignatureForSelector:");
// 返回方法签名 + 实现forwardInvocation方法处理该消息
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation:");
}
- super的本质
super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
struct objc_super2、SEL
[super test];
转换成c++的编译代码:
objc_msgSendSuper({self,class_getSuperclass(objc_getClass("SubTestNSObject"))}, sel_registerName("test"));
struct objc_super {
id receiver; // 消息接受者
Class super_class;
};
NSLog(@"self class] = %@", [self class]);
NSLog(@"super class] = %@", [super class]);
NSLog(@"self superclass] = %@", [self superclass]);
NSLog(@"super superclass] = %@", [super superclass]);
class和superclass的底层实现
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
根据以上分析super的消息接受者还是当前类的实例对象,只不过super查找方法是从父类开始寻找的,所以[super class]返回的是当前实例对象的类对象
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现
isMemberOfClass和isKindOfClass的区别:
源码实现
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isMemberOfClass是判断self和传进来的类是否相同
isKindOfClass是判断self的类对象是否是传进来的类或子类
- 讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
- 什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
- Runtime常用API
类:
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
获取isa指向的Class
Class object_getClass(id obj)
设置isa指向的Class
Class object_setClass(id obj, Class cls)
判断一个OC对象是否为Class
BOOL object_isClass(id obj)
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
获取父类
Class class_getSuperclass(Class cls)
成员变量:
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
属性:
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
方法:
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)