RunTime

2023-07-13  本文已影响0人  万万万万万万一

OC是一门动态语言

动态语言是指程序可以在运行时可以改变其结构:添加新的函数、属性,删除已有的函数、属性等结构上的变化,在运行时做类型的检查。

编译时:源代码被编译成机器可以识别的代码的过程。
运行时:用户可以运行编译过的程序,程序运行的过程。

OC的动态性由runtime支持的。
runtime是一个C语言的库,提供API创建类、添加方法、删除方法、交换方法等。

isa指针(is a what?)

objc_object *id;

struct objc_object {
  Class isa;
�}

objc_class *Class;

struct objc_class {
  super_class,
  name,
  version,
  info,
  instance_size,
  ivars,
  methodLists,
  cache,
  protocols
}

isa指向流程

实例对象isa指向类对象
类对象isa指向元类
类对象superClass指向父类指向的类对象
所有元类isa指向NSObject对象的元类(根元类)
根元类isa指向自己
根元类的superClass指向NSObject的类对象
元类的superClass指向对应父类的元类

Runtime 消息发送机制:示例图

对象方法调用

IMG_0111.PNG

类方法调用

IMG_0112.PNG

1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, ...),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数。

2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步;

3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步;

4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;

Person 类:
@interface Person : NSObject
-(void)sayHello;
-(void)sayString:(NSString*)str;
@end
@implementation Person

- (instancetype)init{
   if (self = [super init]) {
       NSLog(@"%s",__func__);
   }
    [self copyContent];
   return self;
}

-(void)sayHello{
    NSLog(@"Hello");
}
-(void)sayString:(NSString*)str{
    NSLog(@"sayString---%@",str);
}
@end

//Son类
@interface Son : Person

@end
@implementation Son
stancetype)init{
   if (self = [super init]) {
       NSLog(@"%s",__func__);
   }
   return self;
}

@end

//使用
Son *son = [[Son alloc] init];
    [son sayString:@"Hello"];    
objc_msgSend(son, sel_registerName("sayHello"));
    objc_msgSend(son, @selector(sayHello));  
 void (*glt_msgsend)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))objc_msgSend;  
 glt_msgsend(son, @selector(sayString:), @"123");


//打印:
sayString---Hello
2022-05-13 12:19:48.643281+0800 BasicKnowledge[49582:1277604] Hello
2022-05-13 12:19:48.643383+0800 BasicKnowledge[49582:1277604] Hello
2022-05-13 12:19:48.643497+0800 BasicKnowledge[49582:1277604] sayString---123

//Son 没有这个方法时,向它的父类查找

Runtime消息转发机制

IMG_0113.PNG

1.消息动态解析

+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveIClassMethod:(SEL)selector;
IMG_0114.PNG

2消息接受者重定向

- (id)forwardingTargetForSelector:(SEL)selector;
IMG_0115.PNG

3消息重定向

- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
IMG_0116.PNG IMG_0117.PNG

4最后消息未能处理的时候,还会调用

- (void)doesNotRecognizeSelector:(SEL)aSelector抛出异常

1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步

2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步;

3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步

4)报错 unrecognized selector sent to instance。

怎么在项目里全局解决"unrecognized selector sent to instance"这类crash?

可以在消息转发(forwardingTargetForSelector)里面处理,防止崩溃,具体处理办法是:

+ (void)defenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
    swizzlingClassMethod(targetClass, originalSelector, swizzledSelector);
}

+ (void)defenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
    swizzlingInstanceMethod(targetClass, originalSelector, swizzledSelector);
}

// 交换两个类方法的实现
void swizzlingClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {

    Method originalMethod = class_getClassMethod(class, originalSelector);
    Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

// 交换两个对象方法的实现
void swizzlingInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

//建立 NSObject catgory
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 拦截 `+forwardingTargetForSelector:` 方法,替换自定义实现
        [NSObject defenderSwizzlingClassMethod:@selector(forwardingTargetForSelector:)
                                       withMethod:@selector(defend_forwardingTargetForSelector:)
                                        withClass:[NSObject class]];
        
        // 拦截 `-forwardingTargetForSelector:` 方法,替换自定义实现
        [NSObject defenderSwizzlingInstanceMethod:@selector(forwardingTargetForSelector:)
                                          withMethod:@selector(defend_forwardingTargetForSelector:)
                                           withClass:[NSObject class]];
        
    });
}

// 自定义实现 `+defend_forwardingTargetForSelector:` 方法
+ (id)defend_forwardingTargetForSelector:(SEL)aSelector {
    SEL forwarding_sel = @selector(forwardingTargetForSelector:);
    
    // 获取 NSObject 的消息转发方法
    Method root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel);
    // 获取 当前类 的消息转发方法
    Method current_forwarding_method = class_getClassMethod([self class], forwarding_sel);
    
    // 判断当前类本身是否实现第二步:消息接受者重定向
    BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
    
    // 如果没有实现第二步:消息接受者重定向
    if (!realize) {
        // 判断有没有实现第三步:消息重定向
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);
        Method root_methodSignature_method = class_getClassMethod([NSObject class], methodSignature_sel);
        
        Method current_methodSignature_method = class_getClassMethod([self class], methodSignature_sel);
        realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
        
        // 如果没有实现第三步:消息重定向
        if (!realize) {
            // 创建一个新类
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
        
            NSLog(@"*** Crash Message: +[%@ %@]: unrecognized selector sent to class %p ***",errClassName, errSel, self);
            
            
            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);
            
            // 如果类不存在 动态创建一个类
            if (!cls) {
                Class superClsss = [NSObject class];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                // 注册类
                objc_registerClassPair(cls);
            }
            // 如果类没有对应的方法,则动态添加一个
            if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
            }
            // 把消息转发到当前动态生成类的实例对象上
            return [[cls alloc] init];
        }
    }
    return [self defend_forwardingTargetForSelector:aSelector];
}

// 自定义实现 `-defend_forwardingTargetForSelector:` 方法
- (id)defend_forwardingTargetForSelector:(SEL)aSelector {
    
    SEL forwarding_sel = @selector(forwardingTargetForSelector:);
    
    // 获取 NSObject 的消息转发方法
    Method root_forwarding_method = class_getInstanceMethod([NSObject class], forwarding_sel);
    // 获取 当前类 的消息转发方法
    Method current_forwarding_method = class_getInstanceMethod([self class], forwarding_sel);
    
    // 判断当前类本身是否实现第二步:消息接受者重定向
    BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
    
    // 如果没有实现第二步:消息接受者重定向
    if (!realize) {
        // 判断有没有实现第三步:消息重定向
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);
        Method root_methodSignature_method = class_getInstanceMethod([NSObject class], methodSignature_sel);
        
        Method current_methodSignature_method = class_getInstanceMethod([self class], methodSignature_sel);
        realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
        
        // 如果没有实现第三步:消息重定向
        if (!realize) {
            // 创建一个新类
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
    
            NSLog(@"*** Crash Message: -[%@ %@]: unrecognized selector sent to instance %p ***",errClassName, errSel, self);
            
            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);
            
            // 如果类不存在 动态创建一个类
            if (!cls) {
                Class superClsss = [NSObject class];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                // 注册类
                objc_registerClassPair(cls);
            }
            // 如果类没有对应的方法,则动态添加一个
            if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
            }
            // 把消息转发到当前动态生成类的实例对象上
            return [[cls alloc] init];
        }
    }
    return [self defend_forwardingTargetForSelector:aSelector];
}

// 动态添加的方法实现
static int Crash(id slf, SEL selector) {
    return 0;
}

Category

objc_category *Category;
struct objc_category {
  category_name,
  class_name,
  instance_methods,
  class_methods,
  protocols
}

在程序运行时
实例方法整合到主类中
类方法整合到元类中
协议同时整合到主类和元类中

在类的+laod方法中可以调用category里声明的方法吗?

可以,因为附加Category到类的工作先于+load方法的执行

类和category的+load方法调用的顺序

先类,后category。而各个category的+load方法按照编译的顺序执行

关联对象存在哪?

所有的关联对象都由AssociationsManager管理,AssociationsManager里面由一个静态AssociationsHashMap来存储所有的关联对象的。

在category里可以添加属性吗?

category中只能添加方法,不能添加实例变量。类的内存大小是在编译时确定的,而category是在运行时被添加的,此时再添加实例变量会破坏内存结构。
在category中添加属性,通过关联对象实现setter、getter方法。

类和category的同名方法调用的顺序

category并不是完全替换掉主类的同名方法,只是类的方法列表中会出现名字一样的方法且category的方法会排在前面,多个category中的同名方法按编译的顺序排。runtime查找方法按照顺序,一旦找到就return。
遍历类的方法列表,列表里最后一个同名的方法,即是原方法。

category

运行时决议
有单独的.h和.m文件
可以为系统类添加分类
看不到源码的类可以添加分类
只能添加方法,不能添加实例变量

extension

编译时决议
以声明的方式存在,寄生于主类.m文件
不可以为系统类添加extension
没有.m源码的类不可以extension
可以添加方法,可添加实例变量,默认为@private

KVC

是一种可以通过key来访问类属性的机制。而不是通过调用Setter、Getter方法访问。

可以在运行时动态访问和修改对象的属性

// 赋值
[person1 setValue:@"jack" forKey:@"name"];

// 取值
NSString *name = [person1 valueForKey:@"name"];

forKeyPath 是对更“深层”的对象进行访问。如数组的某个元素,对象的某个属性。
[myModel setValue:@"beijing" forKeyPath:@"address.city"];

// 返回所有对象的name属性值
NSArray *names = [array valueForKeyPath:@"name"];

(区别)NSMutableDictionary中的setObject:forKey:与 setVale:forKey:方法有什么区别?问?

答:1.setObject:forKey:中的value是不能够为nil的 ,不然会报错
2.setVale:forKey:中的value是可以为nil的 ,当value为nil时,会自动调用removeObject:forKey方法。
3..setVale:forKey: 中的key参数类型只能是NSString类型的,setObject:forKey:中的key可以任何类型。

setValue:ForKey:(kvc 原理)

按照setKey、_setKey的顺序查找方法,找到了就传递参数,调用方法
如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用setValue:forUndefinedKey抛出NSUnknownKeyException异常
如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接赋值
如果没找到,则调用setValue:forUndefinedKey抛出异常

valueForKey:

按照getKey、_getKey的顺序查找方法,找到了就直接调用方法
如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用value:forUndefinedKey抛出NSUnknownKeyException异常
如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接取值
如果没找到,则调用value:forUndefinedKey抛出异常

KVO

KVO:key value observing,键值监听,可以监听对象某个属性值的变化

给对象添加监听
通过runtime动态创建一个子类,修改对象的isa指向子类
子类重写set方法,内部执行顺序

willChangeValueForKey
[super setKey]
didChangeValueForKey
在didChangeValueForKey中调用KVO的回调方法:observeValueForKeyPath:ofObject:change:context:

归档解档(NSCoding)

1.归档是指用某种格式来保存一个或多个对象,以便以后还原这些对象的过程。归档是将数据持久化的一种方式(所谓数据持久化,就是指在IOS开发过程中,将数据保存到本地,能够让程序的运行更加流畅)。

2.归档与解档是iOS中一种序列化与反序列化的方式。想要归档的数据对象,需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法。

3.归档就是将临时数据保存成本地文件。

4.归档的缺点:归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。

通过class_copyIvarList获得对象的属性列表
通过ivar_getName(ivar)获取到属性的C字符串名称

NSString *key = [NSString stringWithUTF8String:name];转成对应的OC名称
利用KVC进行归档 [corder encodeObject: [self valueForKey:key] forKey: key];

解档 id value = [coder decodeObjectForKey];
利用KVC进行赋值[self setValue:value ForKey:key];

XML归档

1.局限:数据类型只支持 NSString、NSDictionary、NSArayy、NSData、NSNumber(如果你想的话,可以将基本数据类型转换为NSNumber再进行归档)。

2.比较方便,设置好归档路径,一句话归档,一句话解档。

3.归档文件格式:一般保存.plist文件。

/**** NSString和NSMutableString XML归解档 ****/
NSString *str = @"hello world";
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"hello.txt"];
 // atomically:这个参数意思是如果为YES,则保证文件的写入原子性。就是说会先创建一个临时文件,直到文件内容写入成功再导入到目标文件里.如果为NO,则直接写入目标文件里.
[str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
// 这里会覆盖原来的内容
[@"hello world 2" writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSString *str2 = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
/**** NSData和NSMutableData XML归解档 ****/
// 任何对象都可以转化为NSData
NSData *data = [@"hello world" dataUsingEncoding:NSUTF8StringEncoding];
NSString *path2 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"data.txt"];
//归档
[data writeToFile:path2 atomically:YES];
//解档
[NSData dataWithContentsOfFile:path2];
/**** NSArray及NSMutableArray XML归解档 ****/
NSArray *array = @[@"test",@"test2"];
NSString *path3 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"array.plist"];
// 归档
[array writeToFile:path3 atomically:YES];
// 解档
NSArray *array = [NSArray arrayWithContentsOfFile:path3];
/**** NSArray XML归解档 ****/
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"one",@"1",@"two",@"2", nil];
NSString *path4 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"dic.plist"];
// 归档
[dic writeToFile:path4 atomically:YES];
// 解档
NSDictionary *ddic = [NSDictionary dictionaryWithContentsOfFile:path3];
上一篇下一篇

猜你喜欢

热点阅读