你真的了解 Runtime 吗?

2018-11-03  本文已影响9人  Lin__Chuan

怎么思考这个问题呢?
在之前的那篇文章中, 我们详细介绍了 LLVM 编译代码的过程, 我们可以把代码编译成 C++ 代码, 查看内部运行的过程.

1. 编译源代码成 C++ 代码

命令: $ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
解释: 以真机模式编译目标文件, 在当前目录生成cpp文件.

源代码: 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LCPerson *p = [[LCPerson alloc] init];
        [p testHeight];
    }
    return 0;
}
目标代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ 
   { __AtAutoreleasePool __autoreleasepool; 

        LCPerson *p = ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"));
        
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("testHeight"));
    }
    return 0;
}

整理一下: 去掉类型转换

LCPerson *p = [[LCPerson alloc] init];
==> LCPerson *p = (objc_msgSend)((id)(objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"));

[p testHeight];        
==> (objc_msgSend)((id)p, sel_registerName("testHeight"));

2. Runtime 是什么?

众所周知, Objective-C 是一门动态语言

Objective-C 中的方法调用在底层都是转成了objc_msgSend 函数的调用,给receiver (方法调用者)发送了一条消息(selector方法名)
objc_msgSend 底层有3大阶段:
消息发送, 动态方法解析, 消息转发.

消息发送
动态方法解析
消息转发

这里面有几点我需要说明一下:

method_t.jpg

3. Runtime 的应用

API_类

动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls) 

销毁一个类
void objc_disposeClassPair(Class cls)

获取isa指向的Class
Class object_getClass(id obj)

设置isa指向的Class
Class object_setClass(id obj, Class cls)

判断一个OC对象是否为Class
BOOL object_isClass(id obj)

判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)

获取父类
Class class_getSuperclass(Class cls)

**API_ 成员变量 **

获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

API_属性

获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)

拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

API_方法

获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
1. 查看私有成员变量
// 获取成员变量列表
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++){
      const char *ivarName = ivar_getName(ivarList[i]);
      LCLog(@"ivarName%d:==>%@\n",i,[NSString stringWithUTF8String:ivarName]);
}

// 直接为 textfield 设置 placeholderLabel 的颜色
[tf setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
2. 字典转模型

通过获取到 model 的成员变量, 将字典中的值传入给成员变量.

+(instancetype)objectWithJSON:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        
        // 设值
        id value = json[name];
        [obj setValue:value forKey:name];
    }
    
    // 手动释放内存
    free(ivars);
    
    return obj;
}
3. 关联对象, 为分类中的属性添加setter 和 getter 方法

说明:

-(void)setName:(NSString *)name
{
    //参数: 关联对象, 唯一的地址值, value, 关联策略
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @selector(name));
}
4. 交换方法
+(void)lc_exchageMethodWithOriginal: (SEL)originalSel newSel: (SEL)newSel class: (Class)dClass isClassMethod: (BOOL)isClassMethod
{    
    Method originalMethod = isClassMethod ? class_getClassMethod(dClass, originalSel) : class_getInstanceMethod(dClass, originalSel);
    Method newMethod = isClassMethod ? class_getClassMethod(dClass, newSel) : class_getInstanceMethod(dClass, newSel);
    
    // Method中包含IMP函数指针, 通过替换IMP, 使Sel调用不同的方法实现.
    // 为originalMethod添加新的方法实现
    BOOL isAdd = class_addMethod(dClass,
                                 originalSel,
                                 method_getImplementation(newMethod),
                                 method_getTypeEncoding(newMethod));
    // class_addMethod: 如果发现源方法已经有实现, 则会添加失败, 加此判断, 可以避免源方法没有实现
    if (isAdd) {
        // 为 originalSel 添加完实现后, newSel 的实现就没有了
        // 所以需要为 newSel 添加实现, 他俩的实现是一样的.
        class_replaceMethod(dClass,
                            newSel,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    }else {
        // 添加失败: 说明源方法已经有实现, 直接将两个方法的实现替换
        method_exchangeImplementations(originalMethod, newMethod);
    }
}
5. 交换方法_dealloc

交换 dealloc 并不能用上面的那种方法, 因为 @selector(dealloc) 编译器会报错.
所以我们需要更深层次的来交换方法.

+(void)swizzleDeallocIfNeeded:(NSString *)classStr
{
    Class classToSwizzle = NSClassFromString(classStr);
    SEL deallocSelector = sel_registerName("dealloc");
    SEL newDeallocSelector = sel_registerName("dealloc_zombie");
    
    id newDealloc = ^(__unsafe_unretained id self){
        struct objc_super superInfo = {
            .receiver = self,
            .super_class = class_getSuperclass(classToSwizzle)
        };
        
        void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
        msgSend(&superInfo, newDeallocSelector);
    };
    
    IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
    
    // 当系统的 dealloc 没有实现时
    // 为系统的 dealloc 添加新的实现
    class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:");
}

在 NSObject+ZombieObject.m 中

-(void)dealloc_zombie
{
      NSLog(@"来调我啊");
}

这里我说明几点

6. HotFix(热更新)_Aspects + JavaScriptCore

Aspects: hook 目标方法
JavaScriptCore: 对 JS 进行解析, 并提供执行环境.

在 MightyCrash 类中有一个实例方法, 由于做除法运算, 分子不能为0, 所以在传入参数 0 的时候, 程序会崩溃.
解决方法:

fixInstanceMethodReplace('MightyCrash', 'divideUsingDenominator:', function(instance, originInvocation, originArguments){
    if (originArguments[0] == 0) {
    console.log('zero goes here');
    } else {
      runInvocation(originInvocation);
    }
});
@implementation MightyCrash

// 传入 0 就会报错
-(float)divideUsingDenominator:(NSInteger)denominator
{
    return 1.f / denominator;
}

@end
-(void)hotFix
{
    JSContext *context = [[JSContext alloc] init];
    [context setExceptionHandler:^(JSContext *context, JSValue *value) {
        NSLog(@"Oops: %@", value);
    }];
    
    context[@"fixInstanceMethodReplace"] = ^(NSString *instanceName, NSString *selectorName, JSValue *fixImpl) {
        
        Class klass = NSClassFromString(instanceName);
        
        SEL sel = NSSelectorFromString(selectorName);
        
        [klass aspect_hookSelector:sel withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo){
            // 按照JS方式执行
            [fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];
            
        } error:nil];
    };
    
    context[@"runInvocation"] = ^(NSInvocation *invocation) {
        [invocation invoke];
    };
    
    // helper
    [context evaluateScript:@"var console = {}"];
    context[@"console"][@"log"] = ^(id message) {
        NSLog(@"Javascript log: %@",message);
    };
    
    // 执行脚本
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"fixFile" ofType:@""];
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    [context evaluateScript:str];
}
MightyCrash *mc = [MightyCrash new];
float result = [mc divideUsingDenominator:3];  
NSLog(@"result3: %f", result);  ---> 正常输出 0.33333
result = [mc divideUsingDenominator:0];
NSLog(@"won't crash");   ---> 不会报错

clang常用语法介绍
JavaScriptCore详解

上一篇下一篇

猜你喜欢

热点阅读