博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS开发之Runloop和Runtime
阅读量:4146 次
发布时间:2019-05-25

本文共 13260 字,大约阅读时间需要 44 分钟。

RunLoop

对于一个iOS开发者,如果你的水平只是停留在会用API的级别,那说明你与大神还是慢慢长路,本文章大家一块学习一些深层次的东西,RunLoop和Runtime。

你肯定写过一个按钮点击事件,点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让它干活的时候,它就能立刻响应。其实,这就是run loop的功劳。

Runloo的作用

  • 保持程序的持续运行,保持线程的持续运行,并接受用户输入。
  • 处理app中的各种事件(AutoreleasePool、事件响应、手势识别、界面更新、定时器、PerformSelecter、关于GCD、关于网络请求)
  • 调用解耦(Message Queue)
  • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

RunLoop与线程

  • 每个线程(包括主线程)都有一个对应的RunLoop对象
  • 我们并不能自己创建Runloop对象,但是可以获取到系统提供的RunLoop对象
  • 主线程的RunLoop默认是启动的,用于接收各种输入sources;其他线程的RunLoop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动
  • RunLoop在第一次获取时由系统自动创建,在线程结束时销毁

Runloop运行模式

  • 一个Runloop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
  • 每次RunLoop启动时,只能制定其中一个Mode,这个Mode被称作CurrentMode
  • 如果需要切换Mode,只能退出Loop,再从新指定一个Mode进入系统默认模式

系统默认注册了5个mode:

  1. NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. NSRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode。可以看成模式组,默认情况下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)两种模式.

RunLoop对象

iOS中有2套API来访问和使用RunLoop,
1. Foundation

获取当前线程的RunLoop对象[NSRunLoop currentRunLoop]获取主线程的RunLoop对象[NSRunLoop mainRunLoop]

2.Core Foundation

获取当前线程的RunLoop对象CFRunLoopGetCurrent()获取主线程的RunLoop对象CFRunLoopGetMain()
/** *  1:Runloop和线程的关系:        1:一一对应,主线程的runloop已经默认创建,但是子线程的需要手动创建:创建子线程的runloop:        NSRunLoop *run = [NSRunLoop currentRunLoop];        currentRunLoop懒加载的,在同一个子线程中创建多个runloop,则返回的都是同一个对象,因为其是懒加载模式的 2:在runloop中有多个运行模式,但是runloop只能选择一种模式运行,mode里面至少要有一个timer或者是source 2:        1.获得主线程对应的runloop:        NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];        2:获得当前线程对应的runLoop:        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; 3:CFRunLoop:        1:获得主线程对应的runloop:        CFRunLoopGetMain()         2:获得当前线程对应的runLoop:        CFRunLoopGetCurrent() * */- (void)viewDidLoad {    [super viewDidLoad];    }- (void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event { //1.获得主线程对应的runloop NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop]; //2.获得当前线程对应的runLoop NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; NSLog(@"%p---%p",mainRunLoop,currentRunLoop); // NSLog(@"%@",mainRunLoop); //Core NSLog(@"CFRunLoopGetMain()=%p",CFRunLoopGetMain()); NSLog(@"CFRunLoopGetCurrent()=%p",CFRunLoopGetCurrent()); NSLog(@"mainRunLoop.getCFRunLoop=%p",mainRunLoop.getCFRunLoop); //Runloop和线程的关系 //一一对应,主线程的runloop已经创建,但是子线程的需要手动创建 NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil]; //开启线程 [thread start]; }//在runloop中有多个运行模式,但是runloop只能选择一种模式运行//mode里面至少要有一个timer或者是source-(void)run{ //如何创建子线程对应的runLoop,currentRunLoop懒加载的 NSLog(@"[NSRunLoop currentRunLoop]=%@",[NSRunLoop currentRunLoop]); NSLog(@"[NSThread currentThread]---%@",[NSThread currentThread]);}- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning];}

RunLoop应用

1.main函数中的RunLoop

#import 
#import "AppDelegate.h"int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}

UIApplicationMain函数内部就启用了一个RunLoop,所以,UIApplicationMain函数一直没有返回,保持了程序的持续运行。

2.RunLoop与定时器

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController/** * 1:NSLog(@"%@",[NSRunLoop currentRunLoop]); 打印当前线程的RunLoop,懒加载模式,一条线程对应一个RunLoop对象,有返回,没有创建,主线程的RunLoop默认创建,子线程的RunLoop需要手动创建,[NSRunLoop currentRunLoop],同一个线程中若是创建多个RunLoop,则返回的都是同一个RunLoop对象,一个RunLoop里会有多个mode运行模式(系统提供了5个),但运行时只能指定一个RunLoop,若是切换RunLoop,则需要退出当前的RunLoop 2:定时器NSTimer问题:1:若是创建定时器用timerWithTimeInterval,则需要手动将定时器添加到NSRunLoop中,指定的运行模式为default,但是如果有滚动事件的时候,定时器就会停止工作。    解决办法:更改NSRunLoop的运行模式,UITrackingRunLoopMode界面追踪,此模式是当只有发生滚动事件的时候才会开启定时器。若是任何时候都会开启定时器: NSRunLoopCommonModes, NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode 占用,标签,凡是添加到NSRunLoopCommonModes中的事件爱你都会被同时添加到打上commmon标签的运行模式上  3:1:scheduledTimerWithTimeInterval此方法创建的定时器默认加到了NSRunLoop中,并且设置运行模式为默认。    2:若是想在子线程开启NSRunLoop:需要手动开启:NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];等到线程销毁的时候currentRunloop对象也随即销毁。2:在子线程的定时器,需要手动加入到runloop:不要忘记调用run方法  NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];  //该方法内部自动添加到runloop中,并且设置运行模式为默认 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];  //开启runloop [currentRunloop run];  */- (void)viewDidLoad {    [super viewDidLoad];    [self timer1];}-(void)timer1{    //1.创建定时器    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];//    [timer setFireDate:[NSDate distantPast]];        //2.添加定时器到runLoop中,指定runloop的运行模式为NSDefaultRunLoopMode    /*     第一个参数:定时器     第二个参数:runloop的运行模式     */    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];        //UITrackingRunLoopMode:界面追踪//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];        //    NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode    //占用,标签,凡是添加到NSRunLoopCommonModes中的事件爱你都会被同时添加到打上commmon标签的运行模式上    /*     0 : 
{contents = "UITrackingRunLoopMode"} 2 :
{contents = "kCFRunLoopDefaultMode" */ // [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];}-(void)timer2{ //该方法内部自动添加到runloop中,并且设置运行模式为默认 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //开启runloop NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop]; [currentRunloop run];}-(void)run{ NSLog(@"run-----%@---%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);}- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning];}

3.ImageView显示

在特定模式下执行某些操作,图片设置与拖拽分别在不同模式

4.常驻线程

某些操作,需要重复开辟子线程,重复开辟内存过于消耗性能,可以设定子线程常驻。

Runtime

runtime简介

Runtime有两个版本, Legacy(早期版本Objective-C 1.0) 和Modern(现行版本Objective-C 2.0)。

  • Runtime简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最重要的事消息机制。
  • 对于C语言,函数的调用在编译的时候会决定调用哪一个函数
  • 对于OC的函数,属于动态动用过程,在编译的时候并不能决定真正调用哪个函数,只有在运行的时候才会根据函数的名称找到对应的函数来调用。
  • 事实证明:
    • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
    • 在编译阶段,C语言调用未实现的函数就会报错。
      runtime调用方式:
      oc代码调用、framework调用 、RuntimeAPI调用
      runtime作用

发送消息

消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

    • 方法调用的本质就是让对象发送消息
    • objc_msgSend,只用对象才能发送消息,因此以objc开头。
    • 使用消息机制前提,必须导入 #import <objc/message.h>
// 创建person对象Person *p = [[Person alloc] init];// 调用对象方法[p eat];// 本质:让对象发送消息objc_msgSend(p, @selector(eat));// 调用类方法的方式:两种// 第一种通过类名调用[Person eat];// 第二种通过类对象调用[[Person class] eat];// 用类名调用类方法,底层会自动把类名转换成类对象调用// 本质:让类对象发送消息objc_msgSend([Person class], @selector(eat));

交换方法

开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。

  • 方式一.继承系统的类,重写方法。

  • 方式二.使用runtime,交换方法。

#import "ViewController.h"#import "UIImage+Image.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    //    UIImage *image = [UIImage imageNamed:@"123"];    // 1.每次使用,都需要导入头文件    // 2.当一个项目开发太久,使用这个方式不靠谱    [UIImage imageNamed:@"123"];        // imageNamed:    // 实现方法:底层调用xz_imageNamed        // 本质:交换两个方法的实现imageNamed和xz_imageNamed方法    // 调用imageNamed其实就是调用xz_imageNamed            // imageNamed加载图片,并不知道图片是否加载成功    // 以后调用imageNamed的时候,就知道图片是否加载}
#import "UIImage+Image.h"#import 
@implementation UIImage (Image)// 加载这个分类的时候调用+ (void)load{ // 交换方法实现,方法都是定义在类里面 // class_getMethodImplementation:获取方法实现 // class_getInstanceMethod:获取对象 // class_getClassMethod:获取类方法 // IMP:方法实现 // imageNamed // Class:获取哪个类方法 // SEL:获取方法编号,根据SEL就能去对应的类找方法 Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:)); // xmg_imageNamed Method xz_imageNamedMethod = class_getClassMethod([UIImage class], @selector(xz_imageNamed:)); // 交换方法实现 method_exchangeImplementations(imageNameMethod, xz_imageNamedMethod); }// 运行时// 先写一个其他方法,实现这个功能// 在分类里面不能调用super,分类木有父类//+ (UIImage *)imageNamed:(NSString *)name//{// [super im]//}+ (UIImage *)xz_imageNamed:(NSString *)imageName{ // 1.加载图片 UIImage *image = [UIImage xz_imageNamed:imageName]; // 2.判断功能 if (image == nil) { NSLog(@"加载image为空"); } return image;}

动态添加方法

  1. 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
  2. 经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
// performSelector:动态添加方法    Person *p = [[Person alloc] init];    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错    // 动态添加方法就不会报错//    [p performSelector:@selector(eat)];    [p performSelector:@selector(eat:) withObject:@111];
#import "Person.h"#import 
@implementation Person// 定义函数// 没有返回值,参数(id,SEL)// void(id,SEL)void aaaa(id self, SEL _cmd, id param1){ NSLog(@"调用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);}// 默认一个方法都有两个参数,self,_cmd,隐式参数// self:方法调用者// _cmd:调用方法的编号// 动态添加方法,首先实现这个resolveInstanceMethod// resolveInstanceMethod调用:当调用了没有实现的方法没有实现就会调用resolveInstanceMethod// resolveInstanceMethod作用:就知道哪些方法没有实现,从而动态添加方法// sel:没有实现方法+ (BOOL)resolveInstanceMethod:(SEL)sel{// NSLog(@"%@",NSStringFromSelector(sel)); // 动态添加eat方法 if (sel == @selector(eat:)) { /* cls:给哪个类添加方法 SEL:添加方法的方法编号是什么 IMP:方法实现,函数入口,函数名 types:方法类型 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd */ // @:对象 :SEL class_addMethod(self, sel, (IMP)aaaa, "v@:@"); // 处理完 return YES; } return [super resolveInstanceMethod:sel];}@end

给分类添加属性

  • 给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值得内存空间添加到内存空间。

这里写图片描述

这里写图片描述

字典转模型:Runtime

  • 思路:利用运行时,遍历模型中所有的属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
  • 步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。
@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 解析Plist文件    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];    NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];    // 获取字典数组    NSArray *dictArr = statusDict[@"statuses"];    // 自动生成模型的属性字符串//    [NSObject resolveDict:dictArr[0][@"user"]];    _statuses = [NSMutableArray array];    // 遍历字典数组    for (NSDictionary *dict in dictArr) {        Status *status = [Status modelWithDict:dict];        [_statuses addObject:status];    }    // 测试数据    NSLog(@"%@ %@",_statuses,[_statuses[0] user]);}@end@implementation NSObject (Model)+ (instancetype)modelWithDict:(NSDictionary *)dict{    // 思路:遍历模型中所有属性-》使用运行时    // 0.创建对应的对象    id objc = [[self alloc] init];    // 1.利用runtime给对象中的成员属性赋值    // class_copyIvarList:获取类中的所有成员属性    // Ivar:成员属性的意思    // 第一个参数:表示获取哪个类中的成员属性    // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值    // 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。    /* 类似下面这种写法     Ivar ivar;     Ivar ivar1;     Ivar ivar2;     // 定义一个ivar的数组a     Ivar a[] = {ivar,ivar1,ivar2};     // 用一个Ivar *指针指向数组第一个元素     Ivar *ivarList = a;     // 根据指针访问数组第一个元素     ivarList[0];     */    unsigned int count;    // 获取类中的所有成员属性    Ivar *ivarList = class_copyIvarList(self, &count);    for (int i = 0; i < count; i++) {        // 根据角标,从数组取出对应的成员属性        Ivar ivar = ivarList[i];        // 获取成员属性名        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];        // 处理成员属性名->字典中的key        // 从第一个角标开始截取        NSString *key = [name substringFromIndex:1];        // 根据成员属性名去字典中查找对应的value        id value = dict[key];        // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型        // 判断下value是否是字典        if ([value isKindOfClass:[NSDictionary class]]) {            // 字典转模型            // 获取模型的类对象,调用modelWithDict            // 模型的类名已知,就是成员属性的类型            // 获取成员属性类型           NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];          // 生成的是这种@"@\"User\"" 类型 -》 @"User"  在OC字符串中 \" -> ",\是转义的意思,不占用字符            // 裁剪类型字符串            NSRange range = [type rangeOfString:@"\""];           type = [type substringFromIndex:range.location + range.length];            range = [type rangeOfString:@"\""];            // 裁剪到哪个角标,不包括当前角标          type = [type substringToIndex:range.location];            // 根据字符串类名生成类对象            Class modelClass = NSClassFromString(type);            if (modelClass) { // 有对应的模型才需要转                // 把字典转模型                value  =  [modelClass modelWithDict:value];            }        }        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.        // 判断值是否是数组        if ([value isKindOfClass:[NSArray class]]) {            // 判断对应类有没有实现字典数组转模型数组的协议            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {                // 转换成id类型,就能调用任何对象的方法                id idSelf = self;                // 获取数组中字典对应的模型                NSString *type =  [idSelf arrayContainModelClass][key];                // 生成模型               Class classModel = NSClassFromString(type);                NSMutableArray *arrM = [NSMutableArray array];                // 遍历字典数组,生成模型数组                for (NSDictionary *dict in value) {                    // 字典转模型                  id model =  [classModel modelWithDict:dict];                    [arrM addObject:model];                }                // 把模型数组赋值给value                value = arrM;            }        }        if (value) { // 有值,才需要给模型的属性赋值            // 利用KVC给模型中的属性赋值            [objc setValue:value forKey:key];        }    }    return objc;}@end

转载地址:http://mwcti.baihongyu.com/

你可能感兴趣的文章
我的vimrc和gvimrc配置
查看>>
hdu 4280
查看>>
禁止使用类的copy构造函数和赋值操作符
查看>>
C++学习路线
查看>>
私有构造函数
查看>>
组队总结
查看>>
TitledBorder 设置JPanel边框
查看>>
DBCP——开源组件 的使用
查看>>
抓包工具
查看>>
海量数据相似度计算之simhash和海明距离
查看>>
DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)
查看>>
DeepLearning tutorial(6)易用的深度学习框架Keras简介
查看>>
DeepLearning tutorial(7)深度学习框架Keras的使用-进阶
查看>>
流形学习-高维数据的降维与可视化
查看>>
Python-OpenCV人脸检测(代码)
查看>>
python+opencv之视频人脸识别
查看>>
人脸识别(OpenCV+Python)
查看>>
6个强大的AngularJS扩展应用
查看>>
网站用户登录系统设计——jsGen实现版
查看>>
第三方SDK:讯飞语音听写
查看>>