一.
主线程RunLoop:
保证App不退出 负责监听所有的事件
(触摸事件)(网络事件)(定时器事件)无事件 RunLoop睡觉😴 RunLoop在同一时间 只能响应一种模式下的事件
例如:
拖拽textView时 NSTimer事件会停止 为什么停??
{
RunLoop的mode 每个model处理三种事件 Source Obsever Timer 如下图:
61D03D52-39B5-46DB-9370-8579836832F3.png
<<RunLoop在同一时间 只能响应一种模式下的事件
RunLoop有多种模式 哪种模式下有事件 就去那种模式处理 当既有Default模式下的定时器事件 又有Track模式下的UI拖拽事件时>>
UITrackingRunLoopMode优先 所以定时器停了
}
怎么让定时器不停??
{
-
那么把定时器的代码改成如下
D138D073-1F87-4EED-B36C-6F2B550BD219.png
是否可以??
运行程序 发现不能行 因为
UITrackingRunLoopMode只有UI事件才能触发
也就是只有当你拖动 触摸屏幕时 主线程的RunLoop切换到UITrackingRunLoopMode
RunLoop才能处理你的定时器事件
主线程的RunLoop默认在NSDefaultRunLoopMode下 所以运行上图代码 定时器不能行
2.那么把代码改成如下图 为什么行??
B677B9FA-1732-4266-A4A8-32CAAD1DC0C6.png
NSRunLoopCommonModes = NSDefaultRunLoopMode && UITrackingRunLoopMode CommonModes更像一个集合NSSet 里面包含了Default和Tracking模式 所以当你运行程序不触摸屏幕时 主线程RunLoop模式是Default定时器可以
拖动textView即使此时主线程的RunLoop切换到Tracking了 定时器依然好使
}
二.
当不涉及到多线程时 以上方案是OK的 但是毕竟他们全是在主线程处理事情 而主线程时不能被卡住的。
0E2A02B1-1BE4-46C1-BB02-2299EB0FAF6F.png
如上图
假如定时器里有耗时操作呢?主线程是不是卡住了 那么此时拖拽textView就比较卡顿 因为主线程在处理定时器里的耗时操作。
所以要开子线程
三.
引入常驻线程
之前对常驻线程的理解如下
RunLoop的常驻线程就是 开一个子线程 而且这个子线程不会做完一个任务后就消亡 可以一直在这个特定的子线程里做一些事情
生成这么一个线程的方式 就是两行代码
[[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]run];
这样就保证了这个子线程可以一直做事情 而不消亡
但是这有什么具体的应用场景呢
目前想到的是 可以在
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
}
开启这么一个子线程 一直请求百度网址 用来实时监控app的联网状态
但是我这么做不也可以吗
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
NSLog(@"一直请求百度网址检测联网情况..");
}
});
}
就是这个runloop的常驻线程 具体有哪些 非他不可 的事情呢
就是具体实战怎么用
应用场景是什么 就是这种场景下 不用Runloop的常驻线程就解决不了 或者效果没常驻线程的效果好
四.
经过CocoChina大神jian8138指点
先对常驻线程总结如下
160BA9D7-B6EA-4C09-86DE-06F1BF601E04.png
如上图 这么写 定时器的@selector(update)方法是不执行的
为什么不执行??
子线程中的RunLoop默认是不开启的
当线程创建完_timer后就消亡了而定时器的事件是注册到RunLoop中的所以RunLoop并没给你去处理定时器的事件
当我调用_timer 的fire方法时 可以有一次打印 这个是把定时器的事件处理提前了和RunLoop没关系
五.
如果让上图的定时器一直打印 需要引入RunLoop的常驻线程
六.
Runloop的常驻线程可以帮我们在子线程中不阻塞地监听处理各种事件。在子线程中运行定时器的同时,还要有一个按钮,一点击按钮,就要在这条子线程中处理一个事件(例如NSLog一下)。
七.
对于二中的问题可以从下图解决了 利用常驻线程 把耗时操作放到子线程中去做 主线程的拖拽事件(在主线程的RunLoop<Tracking模式>)不受影响。
E3F4CD12-56A0-4C75-8C71-34E8C4867309.png
每一条线程上面都有一个RunLoop
主线程的Runloop默认是开启的
子线程的Runloop默认不开启 执行完任务就挂掉 节约cpu
如果让他不挂 就要给子线程Run RunLoop让他死循环
那么此时这个子线程还会被销毁吗?
NO
直到进程销毁!
如果满足了某个条件
让这个常驻线程挂掉 怎么搞?
1.直接杀死线程
[NSThread exit]; 在常驻线程调。。。
2.控制子线程的生命
直接操纵RunLoop
代码实现如下
873E86B9-B758-452F-83E4-CB7C9B25A45D.png
3.或者如果你能确定让这个RunLoop运行确定的时间再推出的话就可以直接这么写
[[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
NSDate*nowDate = [NSDate dateWithTimeIntervalSinceNow:5];
[[NSRunLoop currentRunLoop]runUntilDate:nowDate];
五秒之后 RunLoop退出 子线程挂掉 在子线程处理事件不被允许...
RunLoop的模式里的处理的三种事件:
- Source 《输入源 事件源 Timer某种意义上来说 也是一种Source 是Source中的一种》
{
所有的事件 都是Source
可分两大类
(1).source0
开发者定义的事件。如: Button addTarget 用户点击
(2).source1
内核事件
系统内核
}
所以-->source0 基于 source1
2.Obsever 《RunLoop的观察者》
3.Timer
八.实际应用 在项目中 有哪些应用的场景
性能优化之 ____ 更新UI如果是个耗时操作 需要RunLoop优化
【链接】CFRunloop优化TableView加载高清大图UI卡顿问题。
http://blog.csdn.net/ZY_FlyWay/article/details/72921158
其实这篇文章说的已经相当详细了
以下为班门弄斧的个人理解记录:
主线程的Runloop 只是让着个runloop一次不要处理太多事件 给他分批次处理 ,观察runloop的状态 每次进入等待状态 丢一个imageView的任务给他 主线程的runloop是默认开启的 主线程肯定不会消亡 但是主线程的runloop会进入休眠runloop休眠了就不处理事件了 所以我还得搞个空循环 让主线程的runloop不进入休眠状态..........
在每次RunLoop循环的时候 只渲染一张图片 而不是12张+的图片 这样一次RunLoop处理的事件越少 卡顿感就越弱
具体实现如下
#import "ViewController.h"
#import "LDCell.h"
typedef void(^runloopTask)(void);
@interface ViewController ()<UITableViewDataSource,UITableViewDelegate>
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic,strong) UITableView *tableView;
//存放任务的数组
@property (nonatomic, strong) NSMutableArray *TaskMarr;
//最大任务数 任务数据只保留展示到用户眼前的页面的任务
@property (nonatomic, assign) NSInteger maxTasksNumber;
//任务执行的代码块
@property (copy, nonatomic) runloopTask task;
@end
static NSString * const cellID = @"cellID";
@implementation ViewController
#pragma mark-- 懒加载
-(NSMutableArray *)TaskMarr{
if (!_TaskMarr) {
_TaskMarr = [NSMutableArray array];
}
self.maxTasksNumber = 12;
return _TaskMarr;
}
- (UITableView *)tableView
{
if (!_tableView) {
_tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
[_tableView registerNib:[UINib nibWithNibName:@"LDCell" bundle:nil] forCellReuseIdentifier:cellID];
_tableView.dataSource = self;
_tableView.delegate = self;
}
return _tableView;
}
#pragma mark --- tableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1000;
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
LDCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
cell.ldLabel.text = [NSString stringWithFormat:@"这是第%ld行cell 😁😁😁",(long)indexPath.row];
#if 0
cell.ldLeftImageV.image = [UIImage imageNamed:@"666"];
cell.ldRightImageV.image = [UIImage imageNamed:@"777"];
#endif
__weak typeof(self) weakSelf = self;
[self addTasks:^{
[weakSelf addLeftImageView:cell];
}];
[self addTasks:^{
[weakSelf addRightImageView:cell];
}];
return cell;;
}
//添加任务进数组保存
-(void)addTasks:(runloopTask)taskBlock{
[self.TaskMarr addObject:taskBlock];
//超过每次最多执行的任务数就移出当前数组
if (self.TaskMarr.count > self.maxTasksNumber) {
[self.TaskMarr removeObjectAtIndex:0];
}
}
- (void)addLeftImageView:(LDCell*)cell
{
cell.ldLeftImageV.image = [UIImage imageNamed:@"666"];
}
- (void)addRightImageView:(LDCell*)cell
{
cell.ldRightImageV.image = [UIImage imageNamed:@"777"];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [UIScreen mainScreen].bounds.size.height / 6;
}
#pragma mark --系统回调
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self addRunLoopObserver];
//给runloop一个事件源,让Runloop不断的运行执行代码块任务。
NSTimer * timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
//NSLog(@"..因为RunLoop没任务做时就会休眠 要让Runloop不断的运行,直到我的任务结束。..");
}];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
//回调
void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
ViewController * vcSelf = (__bridge ViewController *)(info);
if (vcSelf.TaskMarr.count > 0) {
//获取一次数组里面的任务并执行
runloopTask task = vcSelf.TaskMarr.firstObject;
task();
[vcSelf.TaskMarr removeObjectAtIndex:0];
}else
{
return;
}
}
- (void)addRunLoopObserver
{
CFRunLoopRef runloop = CFRunLoopGetCurrent(); //获取当前的RunLoop 返回的就是一个RunLoop对象 既可以运行在Default模式上也可以运行在UITracking模式上
static CFRunLoopObserverRef defaultModelObserver;
//定义上下文
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
//创建观察者
defaultModelObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &CallBack,&context);
//添加当前RunLoop的观察者
CFRunLoopAddObserver(runloop, defaultModelObserver, kCFRunLoopCommonModes);
CFRelease(defaultModelObserver);
}
九.通过RunLoop的NSMachPort进行线程间通信 虽然我并不知道费劲巴列的这么干有🐱用<用GCD一句代码就实现了>但是是可以这么玩耍的....
这么玩的Demo
参考http://url.cn/59QSg7Y
鉴于 能力有限 水平一般 理解有误之处 望大侠不吝指教
ThankYou😁
网友评论