iOS开发笔记之Runtime

一:基础概念

RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是:
1、通过 Objective-C 源代码;
2、通过 Foundation 框架的NSObject类定义的方法;
3、通过对 runtime 函数的直接调用。
大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。

二:runtime的具体实现

我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。

iOS开发笔记之Runtime_第1张图片
image

当我们用OC写下这样一段代码
[tableView cellForRowAtIndexPath:indexPath];
在编译时RunTime会将上述代码转化成[发送消息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常见方法

1、获取属性列表
//People.h
@interface People : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;

@end

//People.m
@interface People() {
    NSString *aaa;
}

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People {
    NSString *bbb;
}

@end

//获取所有属性
-(void)getAllProperty {
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList([People class], &count);
    for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);
    }
}

打印结果:

获取属性列表

结论:
1)、不管是在.h文件中定义的属性还是在.m文件中定义的属性,都可以通过获取属性列表方法来进行获取;
2)、成员变量不同于属性,不能通过该方法来获取;
3)、先输出的是.m文件中的属性,然后才是.h文件中的属性,并且是按照属性定义的先后顺序来保存。

2、获取方法列表
//People.h
@interface People : NSObject

@property (nonatomic, strong) NSString *name;

-(void)iPrintName;
+(void)cPrintName;
@end

//People.m
@interface People() {
    NSString *aaa;
}

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People {
    NSString *bbb;
}

//-(void)printName {
//    
//}

+(void)cPrintName {
    
}

-(void)printAge {
    
}

@end

//获取方法(不包括类方法)列表
-(void)getAllMethod {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList([People class], &count);
    for (unsigned int i=0; i%@", NSStringFromSelector(method_getName(method)));
    }
}
获取方法列表

结论:
1)、类方法不能通过这个函数去获取到;
2)、只有在.m文件中实现了的方法才能被获取到,在.h文件中定义了,但是.m中没有实现的并不能获取到;
3)、对于使用@property定义的属性,会自动生成setter和getter方法,同样能被这个方法获取到;
4)、.m实现中还隐藏了一个.cxx_destruct也就是oc中常见delloc方法;
5)、保存顺序是优先保存用户在.m文件中实现的,其次是.m属性自动生成的getter和setter方法,然后是隐藏的delloc方法,最后是.h属性自动生成的getter和setter方法。

3、获取成员变量列表
//People.h
@interface People : NSObject {
    NSString *cccc;
}

@property (nonatomic, strong) NSString *name;

@end

//People.m
@interface People() {
    NSString *aaa;
}

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People {
    NSString *bbb;
}

@end
-(void)getAllIvar{
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([People class], &count);
    for (unsigned int i=0; i%@", [NSString stringWithUTF8String:ivarName]);
    }
}

打印结果:

获取成员变量列表

结论:
1)、成员变量的保存是从.h文件开始,然后才是.m文件中的成员变量;
2)、用@property 定义的属性,会自动生成以_开头的成员变量,也是先保存.h文件生成的,再保存.m文件生成的。


4、获取协议列表
//People.h
@protocol PeopleDelegate 

-(void)people;

@end

@interface People : NSObject

@property (nonatomic, strong) NSString *name;
//@property (nonatomic, weak) id  delegate;
@end

//People.m
@interface People()

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People

@end

//ViewController.m
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self getProtocal];
}
@end
//获取协议列表
-(void)getProtocal {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);  //这里变成了self
    for (unsigned int i = 0; i%@", [NSString stringWithUTF8String:protocolName]);
    }
}

打印结果:

获取协议列表

结论:
1)、只要声明遵循该协议,在引用的时候,就能获取到该类包含的协议列表,哪怕你没有指定代理的对象,也没有去实现协议的方法

5、获取类方法与实例方法以及方法交换
//People.h
@interface People : NSObject

@property (nonatomic, strong) NSString *name;

-(void)printInstanceMethod;
+(void)printClassMethod;

@end

//People.m
@interface People()

@property (nonatomic, strong) NSString *fatherName;

@end

@implementation People
-(void)printInstanceMethod{
    NSLog(@"我是实例方法");
}

+(void)printClassMethod {
    NSLog(@"我是类方法");
}
@end

示例

-(void)getMethod {
    
    People * p1 = [[People alloc] init];
    
    Method m1 = class_getInstanceMethod([p1 class], @selector(printInstanceMethod));
    Method m2 = class_getClassMethod([People class], @selector(printClassMethod));
    NSLog(@"测试前:");
    [p1 printInstanceMethod];
    [People printClassMethod];
    
    method_exchangeImplementations(m1, m2);
    NSLog(@"测试后:");
    [p1 printInstanceMethod];
    [People printClassMethod];
}

打印结果

iOS开发笔记之Runtime_第2张图片
获取方法以及交换方法
6、添加方法

当项目中,需要继承某一个类(subclass),但是父类中并没有提供我需要的调用方法,而我又不清楚父类中某些方法的具体实现;或者,我需要为这个类写一个分类(category),在这个分类中,我可能需要替换/新增某个方法(注意:不推荐在分类中重写方法,而且也无法通过 super 来获取所谓父类的方法)。大致在这两种情况下,我们可以通过 class_addMethod 来实现我们想要的效果。参考

//Car+MyCar.h
#import "Car+MyCar.h"
#import 

void startEngine(id self, SEL _cmd, NSString *brand) {
    NSLog(@"my %@ car starts the engine", brand);
}

@implementation Car (MyCar)

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(drive)) {
        class_addMethod([self class], sel, (IMP)startEngine, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

不习惯C语言代码可以替换成以下代码:

 - (void)startEngine:(NSString *)brand {
    NSLog(@"my %@ car starts the engine", brand);
 }

 + (BOOL)resolveInstanceMethod:(SEL)sel {
     if (sel == @selector(drive)) {
       class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "v@:@");
       return YES;
     }
     return [super resolveInstanceMethod:sel];
 }

@end

调用:

-(void)addMethod {
    Car *c = [[Car alloc] init];
    [c performSelector:@selector(drive) withObject:@"BMW"];
}

解释:
在 Objective-C 中,正常的调用方法是通过消息机制(message)来实现的,那么如果类中没有找到发送的消息方法,系统就会进入找不到该方法的处理流程中,如果在这个流程中,我们加入我们所需要的新方法,就能实现运行过程中的动态添加了。这个流程或者说机制,就是 Objective-C 的 Message Forwarding 。
这个机制中所涉及的方法主要有两个:

+ (BOOL)resolveInstanceMethod:(SEL)sel;//实例方法
+ (BOOL)resolveClassMethod:(SEL)sel;//类方法

这个函数在 runtime 环境下,如果没有找到该方法的实现的话就会执行。第一行判断的是传入的 SEL 名称是否匹配,接着调用 class_addMethod 方法,传入相应的参数。其中第三个参数传入的是我们添加的 C 语言函数的实现,也就是说,第三个参数的名称要和添加的具体函数名称一致。第四个参数指的是函数的返回值以及参数内容。

结果:

iOS开发笔记之Runtime_第3张图片
添加方法
装逼:

一开始我以为class_addMethod和class_replaceMethod就等同于method_exchangeImplementations,但是看了下面的代码才发现,其实这俩的适用条件是不一样的,当方法没有实现的时候才能使用class_addMethod和class_replaceMethod套装,但是当方法已存在的时候,就需要使用method_exchangeImplementations,这点从if (didAddMethod)这个判定就可见一斑。

+(void)load{
    NSString *className = NSStringFromClass(self.class);
    NSLog(@"classname %@", className);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要特别注意你替换的方法到底是哪个性质的方法
        // When swizzling a Instance method, use the following:
        //        Class class = [self class];

        // When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

        SEL originalSelector = @selector(systemMethod_PrintLog);
        SEL swizzledSelector = @selector(ll_imageName);

        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);
        }
    });
}

Runtime博大精深,看的越深入,感觉越懵 T_T,浅显的认知,欢迎大家提意见

Demo


资料
runtime详解
runtime奇技淫巧系列
OC最实用的runtime总结,面试、工作你看我就足够了!

你可能感兴趣的:(iOS开发笔记之Runtime)