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. Personencodedeco

复制代码
- (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. Personencodedecode

复制代码
- (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来进行归档

通过类的interfaceextension的属性同样可以被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钩子,勾住系统的方法,然后在调用前,修改方法的调用

loadmain.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>
  1. 动态解析(第一步)
复制代码
@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. 如果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];
}
  1. 如果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];
    }
}
  1. 如果3失败,那么doNotRecognizeSelector
复制代码
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"消息无法处理");
}

4. 实现多继承

oc没有多继承,但是可以通过runtime实现多继承

  1. ClassA (:NSObject)
    .h中声明ProtocolA,其中有MethodA
    .m中实现MethodA
  2. ClassB (:NSObject)
    .h中声明ProtocolB,其中有MethodB
    .m中实现MethodB
  3. ClassC (:NSProxy)
    .h中遵循ProtocolAProtocolB
    .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];

暂无评论,快来发表第一条评论吧