1.objc_msgSend
objc_msgSend(消息发送): 给某个对象,发送方法编号消息,通过SEL可以找到对应的IMP
2.MethodSwizzling
- MethodSwizzling: OC编译阶段,动态地进行操作method_exchangeImplementations
- class_addMethod resolveInstanceMethod
3.RunTime常见用法
- ①动态方法解析(防止未实现的method运行Crash)
- ②拦截系统方法(setBackgroundColor:)
- ③给Category添加属性 - ④Button设置时间间隔
- ⑤数据埋点:减少埋点所带来的侵入性
- ⑥字典、模型互转 - ⑦自动归结档
- ⑧查找内存泄漏 - ⑨通过Ivar指针改变实例变量的值(object_setIvar)
- 自定义KVO
4.消息发送机制和消息转发机制的区别
消息发送机制:
使用运行时,通过SEL快速去查找IMP的过程
消息转发机制:
IMP找不到的时候,通过一些方法做转发处理
eg:动态添加方法,在resolveInstanceMethod类方法里面通过class_addMethod实现消息的动态转发
5.runtime -消息发送的整个过程,怎么查找
- runtime消息发送给类对象的时候
- objc_msgSend() 会去它元类中查找能够响应消息的方法实现(先找子类,后找父类)
- 如果找到了,就会对这个类对象执行方法调用
- 找不到时会到resolveInstanceMethod方法中,通过class_AddMethod实现消息的动态转发
6.数据埋点:运行时Method Swizzling机制与AOP编程
场景需求
统计UIViewController加载次数
统计UIButton点击次数
统计自定义方法的执行
统计UITableView的Cell点击事件
需要监听不同类,不同按钮,系统方法,及表单元点击事件
7.objc_msg_Send解析
$阅读寄存器
$ register read x1
x1 = 0x00000001ded79b5f
$ 打印方法编号
po (SEL)0x00000001ded79b5f
objc_msg_Send(t,test)
* 方法缓存;
* 对象 isa isa_t (结构体)
- 缓存没有命中,方法列表的遍历
- cache_t 类对象
- 缓存的命中,地址的运算拿到缓存, sel
- 否则,objc_msgSend_uncached
8.lookUpImpOrForward
- 1.从当前类对象的方法列表中遍历方法列表
- 2会去它元类中查找能够响应消息的方法实现(先找子类,后找父类)
- 3.如果找到了,就会对这个类对象执行方法调用
- 4.找不到就会执行动态方法解析
动态方法解析
- 动态的添加一个方法,方法列表中(SEL-IMP(你给的))
void sendMessage(id self, SEL _cmd,NSString *msg)
{
// implementation ....
NSLog(@"sendMessage%@",msg);
}
// 第一个阶段,动态的方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString * methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sendMessage:"]){
// 动态的添加方法实现,sel -- IMP实现
//v 返回值
return class_addMethod(self, sel, (IMP)sendMessage, "v@:@");
}
return NO;
}
标准消息转发:objc_msgForward
- 方法转发
- forwardTargetForSelector 找一个备胎来接收你
- methodSignature 方法签名
- forwardInvocation消息转发
- doesNotRecognizeSelector 未识别
/// 第二个阶段:找备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector{
// 1. 判断方法名称
NSString * methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]){
return [SpareWheel new];
}
return [super forwardingTargetForSelector:aSelector];
}
//1.方法签名
//2.消息转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString * methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
SpareWheel *sp = [SpareWheel new];
if ([sp respondsToSelector:sel]){
[anInvocation invokeWithTarget:sp];
}else {
[super forwardInvocation:anInvocation];
}
}
-(void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"未知错误~");
}
9.拦截系统方法
修改setBackgroundColor:
创建扩展类UIView+Swizzing
#import "UIView+Swizzing.h"
#import <objc/runtime.h>
@implementation UIView (Swizzing)
+(void)load {
Method m1 = class_getInstanceMethod(self, @selector(setBackgroundColor:));
Method m2 = class_getInstanceMethod(self, @selector(hk_setBackgroundColor:));
method_exchangeImplementations(m1, m2);
}
- (void)hk_setBackgroundColor:(UIColor *)background {
if (background == [UIColor blueColor]){
[self hk_setBackgroundColor:[UIColor yellowColor]];
}
}
@end
分类里为什么不会生成实例变量?
- 类的内存布局在编译的时候被确定,分类是在运行时加载。
- 已经确定的内存布局不会被更改.
10.探索KVO实现原理
创建一个Person类,并实例化两个实例,p1和p2
对p1进行addObserver监听并添加回调方法observerValueForKeyPath
监听之前
(lldb) po object_getClassName(_p1)
"Person"
(lldb) po object_getClassName(_p2)
"Person”
监听之后
(lldb) po object_getClassName(_p2)
"Person"
(lldb) po object_getClassName(_p1)
"NSKVONotifying_Person"
自定义KVO
思路分析
- 创建指定前缀CustomKVONotifying_%@的子类并注册
- 修改isa 指针指向
- 保存observer:关联属性
- 保存set、get方法名
- 自己的方法实现
- 改变父类的属性值
- 获取观察者
- 通知发生改变
objc_msgSend
#import "NSObject+CustomKVO.h"
#import <objc/message.h>
#import <objc/runtime.h>
static const char *custom_observer = "custom_observer";
static const char *custom_getter = "custom_getter";
static const char *custom_setter = "custom_setter";
@implementation NSObject (CustomKVO)
NSString *getValueKey(NSString *setter) {
NSRange range = NSMakeRange(3, setter.length-4);
// name
NSString *key = [setter substringWithRange:range];
NSString *letter = [[key substringToIndex:0] lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:letter];
return key;
}
void setMethod(id self, SEL _cmd, NSString *name) {
// 改变父类的属性值
struct objc_super superClass = {
self,
class_getSuperclass([self class])
};
objc_msgSendSuper(&superClass, _cmd, name);
// 获取观察者
id observer = objc_getAssociatedObject(self, custom_observer);
// 通知发生改变
NSString *methodName = NSStringFromSelector(_cmd);
// setName
NSString *key = getValueKey(methodName);
objc_msgSend(observer, @selector(observeValueForKeyPath: ofObject: change: context:), observer, self, @{key:name}, nil);
}
- (void)custom_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//创建指定前缀CustomKVONotifying_%@的子类并注册
NSString *oldName = NSStringFromClass([self class]);
NSString *newName = [NSString stringWithFormat:@"CustomKVONotifying_%@", oldName];
// 动态创建一个类
Class customClass = objc_getClass(newName.UTF8String);
if (!customClass) {
customClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
objc_registerClassPair(customClass);
}
//set方法首字母大写
NSString *keyPathChange = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];
NSString *setNameStr = [NSString stringWithFormat:@"set%@", keyPathChange];
SEL setSEL = NSSelectorFromString([setNameStr stringByAppendingString:@":"]);
//添加set方法
Method getMethod = class_getInstanceMethod([self class], @selector(keyPath));
const char *types = method_getTypeEncoding(getMethod);
class_addMethod(customClass, setSEL, (IMP)setMethod, types);
//修改isa 指针指向
object_setClass(self, customClass);
//保存observer:关联属性
objc_setAssociatedObject(self, custom_observer, observer, OBJC_ASSOCIATION_ASSIGN);
//保存set、get方法名
objc_setAssociatedObject(self, custom_setter, setNameStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, custom_getter, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
使用
[_p1 custom_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
(lldb) po object_getClassName(_p1)
"CustomKVONotifying_Person"
查找所有子类
+ (NSArray *)hk_findSubClass:(Class)class{
int count = objc_getClassList(NULL, 0);
NSMutableArray *array = [NSMutableArray arrayWithObject:class];
Class *classes = (Class *)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i < count; i++) {
if (class == class_getSuperclass(classes[i])){
[array addObject:classes[i]];
}
}
free(classes);
return array;
}
自定义内存泄漏检查工具
原理分析
通常,UIViewController的释放都是在UINavigationController pop和自身的dismiss,而且一定会走viewDidDisappear这个方法,但是不是所有的viewWillDisappear都是这两个时机,所以需要区分标记。
如果没有走,则通过GCD提供的一个延时处理事物的方式,只需要将延时的任务放到block中,设置好时长即可,而我们将一个弱引用对象放到block中,不会影响对象的释放,就有如下的代码:
///延时处理,如果释放,不会走 [strongSelf showMsg:msg];
- (void) willDealloc{
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
NSString* msg = [NSString stringWithFormat:@"%@__存在内存泄漏",[weakSelf class]];
///泄漏提示
[strongSelf showAlertWithTitle:@"内存泄露了" message:msg confirmTitle:@"好的" handler:nil];
}
});
}
报错汇总
1.objc_msgSend Too many arguments to function call
方法①
项目配置文件 -> Build Settings -> Enable Strict Checking of objc_msgSend Calls 这个字段默认为YES,修改为NO即可
方法②强制转换
由于objc_msgSend函数本身是无返回值无参数的函数, 所以要给它强制转换类型代码如下:
((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, @selector(callMeNow));
((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, sel_registerName("callMeNow"));
@selector(callMeNow)等价于sel_registerName("callMeNow"),都是SEL
2.class_copyMethodList导致内存泄漏
runtime中的class_copyMethodList能获取对应类所有方法。它导致内存泄漏的原因是,这class_copyMethodList函数是C层的,没有OC的自动指针管理,需要手动free。