iOS的Runtime相关知识:消息发送、序列化、hook等
全栈老韩
全栈工程师,擅长iOS App开发、前端(vue、react、nuxt、小程序&Taro)开发、Flutter、React Native、后端(midwayjs、golang、express、koa)开发、docker容器、seo优化等。
Runtime
一、消息发送
Person *p = [[Person alloc] init];
// [p eat];
objc_msgSend(p, @selector(eat));
// Person *p = [Person alloc];
// Person *p = objc_msgSend(Person.class, @selector(alloc));
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
// p = [p init];
p = objc_msgSend(p, @selector(init));
- 在main.m中:
//#import <UIKit/UIKit.h>
//#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
return 0;
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
然后在terminal中执行
clang -rewrite-objc main.m
可以得到main.cpp文件,其中摘取片段如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
return 0;
}
}
二、自定义对象的序列化
1. 存取
NSString *tempPath = NSTemporaryDirectory();
NSString *filePath = [tempPath stringByAppendingPathComponent:@"store.store"];
if (save) {
Person *p = [[Person alloc] init];
p.name = @"cccc";
p.age = 10;
// 存在本地
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
} else {
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@, %d", p.name, p.age);
}
2. Person的encode和deco
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_name forKey:@"name"];
[coder encodeInt:_age forKey:@"age"];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
// self = [super initWithCoder:coder];
if (self = [super init]) {
_name = [coder decodeObjectForKey:@"name"];
_age = [coder decodeIntForKey:@"age"];
}
return self;
}
一般归档的对象是模型
Runtime
一、消息发送
Person *p = [[Person alloc] init];
// [p eat];
objc_msgSend(p, @selector(eat));
// Person *p = [Person alloc];
// Person *p = objc_msgSend(Person.class, @selector(alloc));
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
// p = [p init];
p = objc_msgSend(p, @selector(init));
- 在main.m中:
//#import <UIKit/UIKit.h>
//#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
return 0;
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
然后在terminal中执行
clang -rewrite-objc main.m
可以得到main.cpp文件,其中摘取片段如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
return 0;
}
}
二、自定义对象的序列化
1. 存取
NSString *tempPath = NSTemporaryDirectory();
NSString *filePath = [tempPath stringByAppendingPathComponent:@"store.store"];
if (save) {
Person *p = [[Person alloc] init];
p.name = @"cccc";
p.age = 10;
// 存在本地
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
} else {
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@, %d", p.name, p.age);
}
2. Person的encode和decode
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_name forKey:@"name"];
[coder encodeInt:_age forKey:@"age"];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
// self = [super initWithCoder:coder];
if (self = [super init]) {
_name = [coder decodeObjectForKey:@"name"];
_age = [coder decodeIntForKey:@"age"];
}
return self;
}
一般归档的对象是模型
3. 通过runtime来进行归档
通过类的
interface来extension的属性同样可以被runtime获取到
copy new create代表会在堆区域(malloc)开辟空间,其他变量就不能使用了,返回值就是开辟的空间的指针
指针是在栈区域,出了栈,指针就不能再使用了,但是指针指向的内存区域还在,所以就需要释放掉。OC如果使用了ARC,会自动释放,但是对于C语言来说,需要手动释放。防止内存泄漏溢出。
使用Runtime来实现:
- 1. 在类
Person.m中导入头文件:
#import <objc/runtime.h> - 2. 在类
Person.m重写归档方法:
- (void)encodeWithCoder:(NSCoder *)coder
{
/*
[coder encodeObject:_name forKey:@"name"];
[coder encodeInt:_age forKey:@"age"];
*/
/*
关于Runtime
API: #import <objc/runtime.h>
ivar: 成员变量
Method: 成员方法
*/
unsigned int count = 0;
Ivar *list = class_copyIvarList(Person.class, &count);
// NSLog(@"%u", count);
for (int i = 0; i < count; i++) {
Ivar ivar = list[i];
const char * name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
if (value) {
// 归档
[coder encodeObject:value forKey:key];
}
}
free(list);
}
- 3. 重写解档方法:
- (instancetype)initWithCoder:(NSCoder *)coder
{
// self = [super initWithCoder:coder];
if (self = [super init]) {
/*
_name = [coder decodeObjectForKey:@"name"];
_age = [coder decodeIntForKey:@"age"];
*/
unsigned int count = 0;
Ivar *list = class_copyIvarList(self.class, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = list[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
// 解档
id value = [coder decodeObjectForKey:key];
if (value) {
[self setValue:value forKey:key];
}
}
free(list);
}
return self;
}
在字典转模型时就要用到
runtime去动态遍历
三、oc的方法
类里面有
SEL编号和IMP实现一一对应,IMP是指针。
1. Method swizzle hook
HOOK钩子,勾住系统的方法,然后在调用前,修改方法的调用
load和main.m的执行先后顺序,load方法会先调用,因为程序是预加载的
类被加载的时候,
load里面的swizzle就执行了
NSURL *url = [NSURL URLWithString:@"www.baidu.com加中文后为nil"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSLog(@"%@", request);
@interface NSURL (CSURL)
+ (instancetype)CS_URLWithString:(NSString *)URLString;
@end
#import "NSURL+CSURL.h"
#import <objc/runtime.h>
@implementation NSURL (CSURL)
+ (void)load {
Method method1 = class_getClassMethod(self.class, @selector(URLWithString:));
Method method2 = class_getClassMethod(self.class, @selector(CS_URLWithString:));
// 交换IMP
method_exchangeImplementations(method1, method2);
}
+ (instancetype)CS_URLWithString:(NSString *)URLString {
NSURL *url = [NSURL CS_URLWithString:URLString];
if (url == nil) {
NSLog(@"url 为nil");
}
return url;
}
2. support un-implementation function invoke
- 没有传递参数时
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 动态添加方法,利用runtime
/**
cls: 类
namee: 方法编号
imp: 方法的实现
types: 方法类型
*/
class_addMethod(self, sel, eat, "v");
return [super resolveInstanceMethod:sel];
}
void eat() {
NSLog(@"吃了");
}
- 有传递参数时
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 动态添加方法,利用runtime
/**
cls: 类
namee: 方法编号
imp: 方法的实现
types: 方法类型
*/
class_addMethod(self, sel, eat, "v@:@");
return [super resolveInstanceMethod:sel];
}
// OC方法中有两个隐藏的参数:id self, SEL _cmd
// self: 方法的调用者,参数中的第一个参数
// cmd: 方法编号
void eat(id self, SEL _cmd, NSString *obj) {
NSLog(@"吃了: %@", obj);
}
self Person * 0x60000293cb30 0x000060000293cb30
_cmd SEL "eat:" 0x000000010a714926
obj __NSCFConstantString * @"banana" 0x000000010a7160a8
3 消息转发机制
#import <objc/runtime.h>
- 动态解析(第一步)
@interface Person : NSObject
- (void)sayHelloWith:(NSString *)msg;
@end
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sayHelloWith:"]) {
return class_addMethod(self, sel, (IMP)sayHello, "v@:@");
}
return NO;
}
void sayHello(id self, SEL _cmd, NSString *msg) {
NSLog(@"====%@", msg);
}
@end
- 如果1失败,那么转发给其他对象
@implementation Dog
- (void)sayHelloWith:(NSString *)msg {
NSLog(@"Dog say: %@", msg);
}
@end
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sayHelloWith:"]) {
return [Dog new];
}
return [super forwardingTargetForSelector:aSelector];
}
- 如果2失败,那么转发给其他的对象来实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sayHelloWith:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
Dog *dog = [Dog new];
if ([dog respondsToSelector:sel]) {
[anInvocation invokeWithTarget:dog];
} else {
[super forwardInvocation:anInvocation];
}
}
- 如果3失败,那么doNotRecognizeSelector
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"消息无法处理");
}
4. 实现多继承
oc没有多继承,但是可以通过runtime实现多继承
- ClassA (:NSObject)
.h中声明ProtocolA,其中有MethodA
.m中实现MethodA - ClassB (:NSObject)
.h中声明ProtocolB,其中有MethodB
.m中实现MethodB - ClassC (:NSProxy)
.h中遵循ProtocolA和ProtocolB
.m中,实例化init,实例化一个成员变量methodsMap。
+ (instancetype)takeOutProxy {
return [[ClassC alloc] init];
}
- (instancetype)init {
_methodsMap = [NSMutableDictionary dictionary];
[self registerMethodsWithTarget:[ClassA new]];
[self registerMethodsWithTarget:[ClassB new]];
return self;
}
- (void)registerMethodsWithTarget:(id)target {
unsigned int count = 0;
Method *method_list = class_copyMethodList([target class], &count);
for (int i = 0; i < count; i++) {
Method method = method_list[i];
SEL sel = method_getName(method);
[_methodsMap setObject:target forKey:NSStringFromSelector(sel)];
}
free(method_list);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
id target = _methodsMap[methodName];
if (target && [target respondsToSelector:aSelector]) {
return [target methodSignatureForSelector:aSelector];
} else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
NSString *methodName = NSStringFromSelector(sel);
id target = _methodsMap[methodName];
if (target && [target respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:target];
} else {
[super forwardInvocation:anInvocation];
}
}
调用:
ClassC *classC = [ClassC takeOutProxy];
[classC methodA];
[classC methodB];
发布于2024-01-31 05:12:41
浏览量25·
暂无评论,快来发表第一条评论吧