本文共 13260 字,大约阅读时间需要 44 分钟。
对于一个iOS开发者,如果你的水平只是停留在会用API的级别,那说明你与大神还是慢慢长路,本文章大家一块学习一些深层次的东西,RunLoop和Runtime。
你肯定写过一个按钮点击事件,点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让它干活的时候,它就能立刻响应。其实,这就是run loop的功劳。
Runloo的作用
RunLoop与线程
Runloop运行模式
系统默认注册了5个mode:
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有两个版本, Legacy(早期版本Objective-C 1.0) 和Modern(现行版本Objective-C 2.0)。发送消息
消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现
// 创建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;}
动态添加方法
// 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
@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/