[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fUzU8lnMJatIVgfQF13mB3wlT4sFSh0SQNO_-3A3-Bmg":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":12,"notice":12,"visitCount":28,"commentCount":29,"keywords":30},59,"2024-01-31T05:12:41.099Z","iOS的Runtime相关知识：消息发送、序列化、hook等","# Runtime\n\n## 一、消息发送\n\n```\nPerson *p = [[Person alloc] init];\n//    [p eat];\nobjc_msgSend(p, @selector(eat));\n```\n\n```\n//    Person *p = [Person alloc];\n//    Person *p = objc_msgSend(Person.class, @selector(alloc));\n    Person *p = objc_msgSend(objc_getClass(\"Person\"), sel_registerName(\"alloc\"));\n//    p = [p init];\n    p = objc_msgSend(p, @selector(init));\n```\n\n- 在main.m中：\n```\n//#import \u003CUIKit/UIKit.h>\n//#import \"AppDelegate.h\"\n#import \"Person.h\"\n\nint main(int argc, char * argv[]) {\n    @autoreleasepool {\n        Person *p = [[Person alloc] init];\n        return 0;\n//        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n    }\n}\n```\n然后在terminal中执行\n```\nclang -rewrite-objc main.m\n```\n可以得到`main.cpp`文件，其中摘取片段如下：\n```\nint main(int argc, char * argv[]) {\n    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; \n        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\"));\n        return 0;\n\n    }\n}\n```\n\n## 二、自定义对象的序列化\n### 1. 存取\n```\nNSString *tempPath = NSTemporaryDirectory();\n    NSString *filePath = [tempPath stringByAppendingPathComponent:@\"store.store\"];\n    if (save) {\n        Person *p = [[Person alloc] init];\n        p.name = @\"cccc\";\n        p.age = 10;\n        // 存在本地\n        [NSKeyedArchiver archiveRootObject:p toFile:filePath];\n    } else {\n        Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];\n        NSLog(@\"%@, %d\", p.name, p.age);\n    }\n```\n### 2. `Person`的`encode`和`deco`\n```\n- (void)encodeWithCoder:(NSCoder *)coder\n{\n    [coder encodeObject:_name forKey:@\"name\"];\n    [coder encodeInt:_age forKey:@\"age\"];\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder\n{\n//    self = [super initWithCoder:coder];\n    if (self = [super init]) {\n        _name = [coder decodeObjectForKey:@\"name\"];\n        _age = [coder decodeIntForKey:@\"age\"];\n    }\n    return self;\n}\n```\n> 一般归档的对象是模型\n\n# Runtime\n\n## 一、消息发送\n\n    Person *p = [[Person alloc] init];\n    //    [p eat];\n    objc_msgSend(p, @selector(eat));\n\n\u003C!---->\n\n    //    Person *p = [Person alloc];\n    //    Person *p = objc_msgSend(Person.class, @selector(alloc));\n        Person *p = objc_msgSend(objc_getClass(\"Person\"), sel_registerName(\"alloc\"));\n    //    p = [p init];\n        p = objc_msgSend(p, @selector(init));\n\n*   在main.m中：\n\n\u003C!---->\n\n    //#import \u003CUIKit/UIKit.h>\n    //#import \"AppDelegate.h\"\n    #import \"Person.h\"\n\n    int main(int argc, char * argv[]) {\n        @autoreleasepool {\n            Person *p = [[Person alloc] init];\n            return 0;\n    //        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n        }\n    }\n\n然后在terminal中执行\n\n    clang -rewrite-objc main.m\n\n可以得到`main.cpp`文件，其中摘取片段如下：\n\n    int main(int argc, char * argv[]) {\n        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; \n            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\"));\n            return 0;\n\n        }\n    }\n\n## 二、自定义对象的序列化\n\n### 1. 存取\n\n    NSString *tempPath = NSTemporaryDirectory();\n        NSString *filePath = [tempPath stringByAppendingPathComponent:@\"store.store\"];\n        if (save) {\n            Person *p = [[Person alloc] init];\n            p.name = @\"cccc\";\n            p.age = 10;\n            // 存在本地\n            [NSKeyedArchiver archiveRootObject:p toFile:filePath];\n        } else {\n            Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];\n            NSLog(@\"%@, %d\", p.name, p.age);\n        }\n\n### 2. `Person`的`encode`和`decode`\n\n    - (void)encodeWithCoder:(NSCoder *)coder\n    {\n        [coder encodeObject:_name forKey:@\"name\"];\n        [coder encodeInt:_age forKey:@\"age\"];\n    }\n\n    - (instancetype)initWithCoder:(NSCoder *)coder\n    {\n    //    self = [super initWithCoder:coder];\n        if (self = [super init]) {\n            _name = [coder decodeObjectForKey:@\"name\"];\n            _age = [coder decodeIntForKey:@\"age\"];\n        }\n        return self;\n    }\n\n> 一般归档的对象是模型\n\n### 3. 通过`runtime`来进行归档\n> 通过类的`interface`来`extension`的属性同样可以被`runtime`获取到\n\n> copy new  create代表会在堆区域（malloc）开辟空间，其他变量就不能使用了，返回值就是开辟的空间的指针\n\n> 指针是在栈区域，出了栈，指针就不能再使用了，但是指针指向的内存区域还在，所以就需要释放掉。OC如果使用了ARC，会自动释放，但是对于C语言来说，需要手动释放。防止内存泄漏溢出。\n\n使用`Runtime`来实现:\n*   [x] 1\\. 在类`Person.m`中导入头文件：\n    `#import \u003Cobjc/runtime.h>`\n*   [x] 2\\. 在类`Person.m`重写归档方法：\n```\n- (void)encodeWithCoder:(NSCoder *)coder\n{\n    /*\n    [coder encodeObject:_name forKey:@\"name\"];\n    [coder encodeInt:_age forKey:@\"age\"];\n     */\n    /*\n     关于Runtime\n     API: #import \u003Cobjc/runtime.h>\n     ivar: 成员变量\n     Method: 成员方法\n     */\n    unsigned int count = 0;\n    Ivar *list = class_copyIvarList(Person.class, &count);\n//    NSLog(@\"%u\", count);\n    for (int i = 0; i \u003C count; i++) {\n        Ivar ivar = list[i];\n        const char * name = ivar_getName(ivar);\n        NSString *key = [NSString stringWithUTF8String:name];\n        id value = [self valueForKey:key];\n        if (value) {\n            // 归档\n            [coder encodeObject:value forKey:key];\n        }\n    }\n    free(list);\n}\n```\n- [x]  3. 重写解档方法：\n```\n- (instancetype)initWithCoder:(NSCoder *)coder\n{\n//    self = [super initWithCoder:coder];\n    if (self = [super init]) {\n        /*\n        _name = [coder decodeObjectForKey:@\"name\"];\n        _age = [coder decodeIntForKey:@\"age\"];\n         */\n        unsigned int count = 0;\n        Ivar *list = class_copyIvarList(self.class, &count);\n        for (int i = 0; i \u003C count; i++) {\n            Ivar ivar = list[i];\n            const char *name = ivar_getName(ivar);\n            NSString *key = [NSString stringWithUTF8String:name];\n            // 解档\n            id value = [coder decodeObjectForKey:key];\n            if (value) {\n                [self setValue:value forKey:key];\n            }\n        }\n        free(list);\n    }\n    return self;\n}\n```\n\n> 在字典转模型时就要用到`runtime`去动态遍历\n\n## 三、oc的方法\n> 类里面有`SEL`编号和`IMP`实现一一对应，`IMP`是指针。\n### 1. Method swizzle hook\n> HOOK钩子，勾住系统的方法，然后在调用前，修改方法的调用\n\n> `load`和`main.m`的执行先后顺序，`load`方法会先调用，因为程序是预加载的\n\n> 类被加载的时候，`load`里面的`swizzle`就执行了\n\n```\nNSURL *url = [NSURL URLWithString:@\"www.baidu.com加中文后为nil\"];\n        NSURLRequest *request = [NSURLRequest requestWithURL:url];\n        NSLog(@\"%@\", request);\n\n```\n```\n@interface NSURL (CSURL)\n\n+ (instancetype)CS_URLWithString:(NSString *)URLString;\n\n@end\n\n#import \"NSURL+CSURL.h\"\n#import \u003Cobjc/runtime.h>\n\n@implementation NSURL (CSURL)\n\n+ (void)load {\n    Method method1 =  class_getClassMethod(self.class, @selector(URLWithString:));\n    Method method2 = class_getClassMethod(self.class, @selector(CS_URLWithString:));\n    // 交换IMP\n    method_exchangeImplementations(method1, method2);\n}\n\n+ (instancetype)CS_URLWithString:(NSString *)URLString {\n    NSURL *url = [NSURL CS_URLWithString:URLString];\n    if (url == nil) {\n        NSLog(@\"url 为nil\");\n    }\n    return url;\n}\n```\n### 2. support un-implementation function invoke\n- [x] 没有传递参数时\n```\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    // 动态添加方法，利用runtime\n    /**\n     cls: 类\n     namee: 方法编号\n     imp: 方法的实现\n     types: 方法类型\n     */\n    class_addMethod(self, sel, eat, \"v\");\n    return [super resolveInstanceMethod:sel];\n}\n\nvoid eat() {\n    NSLog(@\"吃了\");\n}\n```\n\n- [x] 有传递参数时\n```\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    // 动态添加方法，利用runtime\n    /**\n     cls: 类\n     namee: 方法编号\n     imp: 方法的实现\n     types: 方法类型\n     */\n    class_addMethod(self, sel, eat, \"v@:@\");\n    return [super resolveInstanceMethod:sel];\n}\n// OC方法中有两个隐藏的参数：id self, SEL _cmd\n// self: 方法的调用者，参数中的第一个参数\n// cmd: 方法编号\nvoid eat(id self, SEL _cmd, NSString *obj) {\n    NSLog(@\"吃了: %@\", obj);\n}\n```\n\n```\nself\tPerson *\t0x60000293cb30\t0x000060000293cb30\n_cmd\tSEL\t\"eat:\"\t0x000000010a714926\nobj\t__NSCFConstantString *\t@\"banana\"\t0x000000010a7160a8\n```\n\n### 3 消息转发机制\n```\n#import \u003Cobjc/runtime.h>\n```\n1. 动态解析（第一步）\n```\n@interface Person : NSObject\n\n- (void)sayHelloWith:(NSString *)msg;\n\n@end\n\n@implementation Person\n\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    NSString *methodName = NSStringFromSelector(sel);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return class_addMethod(self, sel, (IMP)sayHello, \"v@:@\");\n    }\n    return NO;\n}\n\nvoid sayHello(id self, SEL _cmd, NSString *msg) {\n    NSLog(@\"====%@\", msg);\n}\n\n@end\n```\n\n2. 如果1失败，那么转发给其他对象\n```\n@implementation Dog\n\n- (void)sayHelloWith:(NSString *)msg {\n    NSLog(@\"Dog say: %@\", msg);\n}\n\n@end\n```\n```\n- (id)forwardingTargetForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return [Dog new];\n    }\n    return [super forwardingTargetForSelector:aSelector];\n}\n```\n3. 如果2失败，那么转发给其他的对象来实现\n```\n- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return [NSMethodSignature signatureWithObjCTypes:\"v@:@\"];\n    }\n    return [super methodSignatureForSelector:aSelector];\n}\n\n- (void)forwardInvocation:(NSInvocation *)anInvocation {\n    SEL sel = [anInvocation selector];\n    Dog *dog = [Dog new];\n    if ([dog respondsToSelector:sel]) {\n        [anInvocation invokeWithTarget:dog];\n    } else {\n        [super forwardInvocation:anInvocation];\n    }\n}\n```\n\n4. 如果3失败，那么doNotRecognizeSelector\n```\n- (void)doesNotRecognizeSelector:(SEL)aSelector {\n    NSLog(@\"消息无法处理\");\n}\n```\n\n### 4. 实现多继承\noc没有多继承，但是可以通过runtime实现多继承\n\n1.  ClassA (\\:NSObject) \\\n    `.h`中声明`ProtocolA`,其中有`MethodA` \\\n    `.m`中实现`MethodA`\n2.  ClassB (\\:NSObject) \\\n    `.h`中声明`ProtocolB`,其中有`MethodB` \\\n    `.m`中实现`MethodB`\n3.  ClassC (\\:NSProxy) \\\n    `.h`中遵循`ProtocolA`和`ProtocolB` \\\n    `.m`中，实例化`init`，实例化一个成员变量`methodsMap`。\n\n```\n+ (instancetype)takeOutProxy {\n    return [[ClassC alloc] init];\n}\n\n- (instancetype)init {\n    _methodsMap = [NSMutableDictionary dictionary];\n    [self registerMethodsWithTarget:[ClassA new]];\n    [self registerMethodsWithTarget:[ClassB new]];\n    return self;\n}\n\n- (void)registerMethodsWithTarget:(id)target {\n    unsigned int count = 0;\n    Method *method_list = class_copyMethodList([target class], &count);\n    for (int i = 0; i \u003C count; i++) {\n        Method method = method_list[i];\n        SEL sel = method_getName(method);\n        [_methodsMap setObject:target forKey:NSStringFromSelector(sel)];\n    }\n    free(method_list);\n}\n\n- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    id target = _methodsMap[methodName];\n    if (target && [target respondsToSelector:aSelector]) {\n        return [target methodSignatureForSelector:aSelector];\n    } else {\n        return [super methodSignatureForSelector:aSelector];\n    }\n}\n\n- (void)forwardInvocation:(NSInvocation *)anInvocation {\n    SEL sel = [anInvocation selector];\n    NSString *methodName = NSStringFromSelector(sel);\n    id target = _methodsMap[methodName];\n    if (target && [target respondsToSelector:aSelector]) {\n        [anInvocation invokeWithTarget:target];\n    } else {\n        [super forwardInvocation:anInvocation];\n    }\n}\n```\n\n调用：\n```\nClassC *classC = [ClassC takeOutProxy];\n[classC methodA];\n[classC methodB];\n```","这篇文章通过对iOS中的消息发送、序列化、hook等知识点的解读，进一步让大家来认识iOS的runtime原理和思想。","",499668042977349,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"13121171998","全栈老韩",1,"https://image.xinwei.ltd/images/IMG_5430.JPG","全栈工程师，擅长iOS App开发、前端（vue、react、nuxt、小程序&Taro）开发、Flutter、React Native、后端（midwayjs、golang、express、koa）开发、docker容器、seo优化等。","2024-01-01T16:14:30.305Z",2,{"id":21,"name":23},"IT技术",3,{"id":24,"name":26,"parentId":21},"iOS",[],27,0,"ios,ios msg,ios序列化,ios message send"]