底层原理(六)------Runtime
一、引入
-
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
sel_registerName("personTest") == @selector(personTest)
二、OC的方法调用:消息机制,给方法调用者发送消息

三、objc_msgSend的执行流程可以分为3大阶段
消息发送
动态方法解析
消息转发
注:objc_msgSend如果找不到合适的方法调用,会报错 unrecognized selector send to instance
objc_msgSend执行流程-源码阅读

objc_msgSend源码汇编解析
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
// x0寄存器:消息接受者,receiver
cmp x0, #0 // nil check and tagged pointer check
// b是跳转 判断消息接收者是否为nil 是的话 跳转到LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret // 相当于return
END_ENTRY _objc_msgSend
注:b在汇编中是跳转的含义
hit是命中的含义
汇编语言中__class_lookupMethodAndLoadCache3这种写法在C语言中变成少一个下划线,也就是_class_lookupMethodAndLoadCache3
在class_rw_t里面查找method_array_t的method_t方法 如果是排好序的通过二分法查找,如果不是 则通过遍历循环查找

objc_msgSend执行流程01-消息发送

1、cache中查找:也就是从struct bucket_t *_buckets中查找;也就是@selector(方法名)&_mask(散列表的长度 - 1 mask_t会默认开辟一个空间,如果超出空间大小,就会重新开辟新空间,新空间=旧空间 * 2),这里面涉及到散列表(哈希表)查找,通过key生成索引,如果生成的索引一样,则自动-1。查找的时候,如果索引为0,则继续通过_mask开始查找也就是数组的最后一位开始
SEL作为key,IMP作为value
2、class_rw_t中查找方法列表:也就是在method_lists_t二维数组中查找
(class_ro_t是一维数组,只存储类的基本数据,而method_array_t methods中的二维数组会把分类信息也存储到methods中,先编译的分类会存储到数组的前边)
objc_msgSend执行流程02-动态方法解析

动态方法解析,又会重新走消息发送流程进行查找
动态方法解析方法一

动态方法解析方法二

动态方法解析方法三(c语言写法)

动态方法解析方法四(如果是+类方法)

动态方法总结

注:如果动态方法解析没有添加方法实现,也会进入消息转发,但是最后会程序报错
@dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现
objc_msgSend执行流程03-消息转发

代码剖析
#import <objc/runtime.h>
#import "MJCat.h"
/* 假如forwardingTargetForSelector返回值有值调用下边方法,不会走方法前面 */
// 消息转发+ - 两种方法写法
//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
// class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
//}
//- (id)forwardingTargetForSelector:(SEL)aSelector
//{
// if (aSelector == @selector(test)) {
// // objc_msgSend([[MJCat alloc] init], aSelector)
// return [[MJCat alloc] init];
// }
// return [super forwardingTargetForSelector:aSelector];
//}
/* 假如forwardingTargetForSelector返回nil 则需要走方法签名,如果方法签名也为nil,则报错找不到该方法:doesNotRecognizeSelector: */
// 方法签名:返回值类型、参数类型
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
// if (aSelector == @selector(test)) {
// return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// }
// return [super methodSignatureForSelector:aSelector];
//}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
//- (void)forwardInvocation:(NSInvocation *)anInvocation
//{
//// anInvocation.target = [[MJCat alloc] init];
//// [anInvocation invoke];
//
// [anInvocation invokeWithTarget:[[MJCat alloc] init]];
//}
方法签名的应用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
return [NSMethodSignature signatureWithObjCTypes:"i@:I"];
// 另一种写法调用test方法
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// 可以任意处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 参数顺序:receiver、selector、other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[MJCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[MJCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
类方法的消息转发
// 元类对象是一种特殊的类对象
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// objc_msgSend([[MJCat alloc] init], @selector(test))
// [[[MJCat alloc] init] test]
if (aSelector == @selector(test)) return [[MJCat alloc] init];
return [super forwardingTargetForSelector:aSelector];
}
//+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
// if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//
// return [super methodSignatureForSelector:aSelector];
//}
//
//+ (void)forwardInvocation:(NSInvocation *)anInvocation
//{
// NSLog(@"1123");
//}
消息转发机制的用途
1、可以收集bug

2、统一拦截(可以统一处理方法,或者统一处理找不到方法)

面试题
1、OC的消息机制

扩展--super的底层原理
1、[super run]的底层讲解:底层包含2个参数 第一个是消息接收者,第二个是消息接收者的父类,objc_super里面的[MJPerson class] 只是为了让self从MJPerson里面开始查钊

汇编转译以后

关于super的面试题

1、[self class],[self superclass],[super class],[super superclass]输出结果
不论是self calss 还是super class 最后查找到的都是NSObject里面的class
/*
[super message]的底层实现
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现
*/
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};
- (void)run
{
// super调用的receiver仍然是MJStudent对象
[super run];
// struct objc_super arg = {self, [MJPerson class]};
//
// objc_msgSendSuper(arg, @selector(run));
//
//
// NSLog(@"MJStudet.......");
}
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // MJStudent
NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
NSLog(@"[super class] = %@", [super class]); // MJStudent
NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
}
return self;
}
class 和superclass的底层实现
// 通过objc_msgSend和objc_msgSendSuper去查找receiver接收者是谁
- (Class)class
{
return object_getClass(self);
}
- (Class)superclass
{
return class_getSuperclass(object_getClass(self));
}
isKindOfClass和isMemberOfClass
isKindOfClass 当前类是否属于对比类或者是对比类的superclass
isMemberOfClass当前类是否跟对比类是同一个类
isKindOfClass和isMemberOfClass底层实现
@implementation NSObject
- (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;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
@end
上述图片答案
这种写法前面的NSObject 指向的都是元类对象
+号方法是拿左边的元类对象跟右边的类对象进行比较
-号方法是拿左边的类对象跟右边的类对象进行比较
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
面试题

可以运行,打印结果为

剖析:cls等价于实例对象的isa指针,在实例对象中isa指针存储在第一个位置也就是前8个字节,obj的第一个参数就是cls,所以cls跟isa指针类似 都是指向class对象

注:栈空间的地址是从高到低排列
obj进行查找实例对象里面存储 第一位是isa指针第二位才是_name参数,所以obj查找需要忽略8个字节进行查找,所以找到test对应的数值
![]()
为啥都没有的时候是viewcontroller,因为是[super viewdidload];
![]()
引申--查看源码的三种方式
1、转换为c++代码
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
2、运行时候转换汇编代码

3、直接转换成汇编代码(这里面会有详细的注释跟行号)

LLVM
注:LLVM:参考https://llvm.org/docs/LangRef.html
OC->中间代码->汇编、机器代码
![]()
Runtime API
一、Runtime API 01-类

二、Runtime API 02-成员变量

三、Runtime API 03-属性

四、Runtime API 04-方法


用法一:普通设置
MJPerson *person = [[MJPerson alloc] init];
[person run];
// 设置isa指向的Class
object_setClass(person, [MJCar class]);
[person run];
// 判断一个OC对象是否为Class
NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);
// 获取isa指向的Class
NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);
用法二:创建类
void run(id self, SEL _cmd)
{
NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}
void testClass()
{
// 注:成员变量必须在注册类之前添加,因为成员变量存储在class_ro_t里面是只读的
// 属性、方法、协议可以在注册类之后添加,因为存储在class_rw_t里面
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
// 动态添加成员变量
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
// 添加方法
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注册类
objc_registerClassPair(newClass);
// MJPerson *person = [[MJPerson alloc] init];
// // 将person的isa指针指向全新的class
// object_setClass(person, newClass);
// [person run];
//
// id dog = [[newClass alloc] init];
// [dog setValue:@10 forKey:@"_age"];
// [dog setValue:@20 forKey:@"_weight"];
// [dog run];
//
// NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
// 在不需要这个类时释放
objc_disposeClassPair(newClass);
}
用法三:成员变量的处理
void testIvars()
{
// 获取成员变量信息
// Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
// NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
// 设置和获取成员变量的值
// Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
//
// MJPerson *person = [[MJPerson alloc] init];
// object_setIvar(person, nameIvar, @"123");
// object_setIvar(person, ageIvar, (__bridge id)(void *)10);
// NSLog(@"%@ %d", person.name, person.age);
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([MJPerson class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[I];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars); // 在runtime中涉及到copy操作 比如class_copyIvarList这种,必须结束以后释放
}
实战应用一:查看私有成员边量->UITextField里面更改placeholder的属性
- (void)viewDidLoad {
[super viewDidLoad];
// // 先查找UITextField里面的成员变量
// unsigned int count;
// Ivar *ivars = class_copyIvarList([UITextField class], &count);
// for (int i = 0; i < count; i++) {
// // 取出i位置的成员变量
// Ivar ivar = ivars[I];
// NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
// }
// free(ivars);
self.textField.placeholder = @"请输入用户名";
// 直接设置
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
// 直接取值操作
// UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
// placeholderLabel.textColor = [UIColor redColor];
// 系统的方法
// NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
// attrs[NSForegroundColorAttributeName] = [UIColor redColor];
// self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
}
实战应用二:字典转模型
#import <Foundation/Foundation.h>
@interface NSObject (Json)
+ (instancetype)mj_objectWithJson:(NSDictionary *)json;
@end
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[I];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
// 设值
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
@end
// 快捷的就存储起来了(仿照MJExtension)
// 字典转模型
NSDictionary *json = @{
@"id" : @20,
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack"
};
MJPerson *person = [MJPerson mj_objectWithJson:json];
实战应用三:替换方法
#import <Foundation/Foundation.h>
#import "MJPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
// 交换方法实现
Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
return 0;
}
void myrun()
{
NSLog(@"---myrun");
}
void test()
{
MJPerson *person = [[MJPerson alloc] init];
// 替换方法
// class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");
// block 作为返回值
class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123123");
}), "v");
[person run];
}
总结:什么是Runtime?平时项目中有用过吗?

例子:交换UIButton的点击事件
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
+ (void)load
{
// hook:钩子函数
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
// 调用系统原来的实现
[self mj_sendAction:action to:target forEvent:event];
// [target performSelector:action];
// if ([self isKindOfClass:[UIButton class]]) {
// // 拦截了所有按钮的事件
//
// }
}
@end
NSMutableNSArray 添加nil值处理
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self mj_insertObject:anObject atIndex:index];
}
@end
NSMutableDictionary也可以这么处理
#import "NSMutableDictionary+Extension.h"
#import <objc/runtime.h>
@implementation NSMutableDictionary (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);
Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});
}
- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (!key) return;
[self mj_setObject:obj forKeyedSubscript:key];
}
- (id)mj_objectForKeyedSubscript:(id)key
{
if (!key) return nil;
return [self mj_objectForKeyedSubscript:key];
}
@end