对于一个iOS开发者,如果你的水平只是停留在会用API的级别,那说明你与大神还是慢慢长路,本文章大家一块学习一些深层次的东西,RunLoop和Runtime。Runloop官方文档
你肯定写过一个按钮点击事件,点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让它干活的时候,它就能立刻响应。其实,这就是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作用
发送消息
消息机制原理:对象根据方法编号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
- 思路:利用运行时,遍历模型中所有的属性,根据模型的属性名,去字典中查找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