您的位置:首页 > 移动开发 > IOS开发

ios开发中的runloop

2016-06-14 19:42 295 查看
</pre>一直觉得ios中的runloop特别重要,想要理解一下,然后在开发过程中希望能够用runloop在某些地方写些跟优雅的代码。<p></p><p>在网上看了些资料,自己总结如下吧!</p><p>很早以前,我研究过windows编程,其中就认识到原来应用可以一直处于运行状态是因为里边有一个while死循环,让应用根本没办法从main入口函数中退出!真是人才,也很容易理解这一点吧!而这样的情况放在ios中就是我们接下来要讲的runloop。</p><p>runloop的作用:</p><p>1.保持程序不会退出</p><p style="text-align:justify">2.程序不退出,就可以随时处理app中的各种事件,包括人为的事件,定时器事件,selector事件等等</p><p style="text-align:justify">3.节省cup资源,提高程序的性能,有事情做的时候就唤醒,没事情做的时候就睡眠状态</p><p style="text-align:justify"></p><pre name="code" class="objc">int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
其中UIApplicationMain这个函数就有一个Runloop,这个Runloop就是主线程中的Runloop,所以主线程才一直不会退出,所以应用也不会结束.

Foundation框架中的NSRunLoop与Core Foundation中的CFRunLoop

RunLoop与线程有直接的非常不同寻常的关系,形影不离
1.没一条线程都有一个唯一的与之对应的RunLoop对象
2.主线程的RunLoop是由系统自己创建的,然后子线程的RunLoop对象我们如果要使用就必须自己去创建
3.一个线程结束了,那么这个线程所对应的RunLoop对象就会销毁

下面来分析下RunLoop的结构

一个RunLoop中可以拥有多个不同的Mode,但是RunLoop在同一时间运行只能去使用其中某一个Mode,如果要切换Mode,必须先退出之前的那个Mode,才能进入新的Mode中。
这里所说的Mode,其实就是指RunLoop的不同的运行模式,它有五种运行模式,如下

1.NSDefaultRunLoopMode   主线程的RunLoop就是在这种模式下运行
2.UITrackingRunLoopMode   界面跟踪模式,当用户与界面交互的时候会在这种模式下运行RunLoop
3.NSRunLoopCommonModes  模式占位,上边的两种模式都可以运行RunLoop
4.UIInitializationRunLoopMode  程序启动时处于这个模式下,程序一点启动完毕就不会在这个模式下了
5.GSEventReceiveRunLoopMode  接受系统事件的内部模式

每个CFRunLoopModeRef中又包含多个Source,Timer,Observer,包含的这三个东东,其实就是去触发RunLoop唤醒不死线程的作用(Observer好像不是这个作用,我暂且就这么理解吧!),下边我们来一一讲解:
CFRunLoopTimerRef:
屁话不多说,其实就是个NSTimer

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];<span style="font-family: Arial, Helvetica, sans-serif;">  </span>
<span style="font-family: Arial, Helvetica, sans-serif;">//将创建的timer加入到DefaultRunLoopMode中,而这个timer在其他模式,比如TrackingRunLoopMode下会停止工作</span>
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
</pre><pre name="code" class="objc">
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 拖拽UI界面时出发定时器,在默认模式(NSDefaultRunLoopMode)下不工作
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSRunLoopCommonModes仅仅是标记NSTimer在两种模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能运行,但一个RunLoop中同一时间内只能运行一种模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 默认已经添加到主线程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
</pre>看过别人写的简书的以为作者<a target=_blank target="_blank" class="author-name blue-link" href="http://www.jianshu.com/users/244aa1f48d1c" style="color:rgb(64,148,199); text-decoration:none; margin:0px 5px; font-family:'lucida grande','lucida sans unicode',lucida,helvetica,'Hiragino Sans GB','Microsoft YaHei','WenQuanYi Micro Hei',sans-serif; line-height:20px">YotrolZ </a>写过的一篇文章中讲到:<p></p><p style="text-align:justify">GCD中的定时器跟NSTimer计时器是不一样的,GCD定时器不受Mode的yin'xiang</p><p style="text-align:justify"></p><p style="text-align: justify;"></p><pre name="code" class="objc">/** 定时器对象 */
@property (nonatomic, strong)dispatch_source_t timer;
// 需要一个强引用
NSLog(@"开始");
// 获取队并发队列,定时器的回调函数将会在子线程中执行
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

// 获取主队列,定时器的回调函数将会在子线程中执行
dispatch_queue_t queue = dispatch_get_main_queue();

self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

// 该时间表示从现在开始推迟两秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);

// 设置定时器的开始时间,间隔时间
dispatch_source_set_timer(self.timer, start, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------%@", [NSThread currentThread]);
});
dispatch_resume(self.timer);

/* 参数说明:
// 设置定时器的一些属性
dispatch_source_set_timer(dispatch_source_t source, // 定时器对象
dispatch_time_t start, // 定时器开始执行的时间
uint64_t interval, // 定时器的间隔时间
uint64_t leeway // 定时器的精度
);

*/


CFRunLoopSourceRef

1.Port-based Sources : 内核相关
2.Custom Input Sources : 与自定义Sources相关
3.Cocoa Perform Selector Sources : 与self performSelector方法相关
4.Source0 : 非基于port
5. Source1 : 基于port

CFRunLoopObserverRef:
CFRunLoopObserverRef是RunLoop的观察者,可以通过CFRunLoopObserverRef来监听RunLoop的状态改变.

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),         // 状态值:1,表示进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1),  // 状态值:2,表示即将处理NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 状态值:4,表示即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 状态值:32,表示即将休眠
kCFRunLoopAfterWaiting = (1UL << 6),  // 状态值:64,表示从休眠中唤醒
kCFRunLoopExit = (1UL << 7),          // 状态值:128,表示退出RunLoop
kCFRunLoopAllActivities = 0x0
bdb5
FFFFFFFU // 表示监听上面所有的状态
};


那么如何监听RunLoop的状态啦?

1.先创建CFRunLoopObserverRef

// 第一个参数用于分配该observer对象的内存
// 第二个参数用以设置该observer所要关注的的事件,详见回调函数myRunLoopObserver中注释
// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
// 第四个参数用于设置该observer的优先级,一般为0
// 第五个参数用于设置该observer的回调函数
// 第六个参数observer的运行状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
2.将观察者添加到对应的RunLoop上

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
3.C语言中的对象要主动释放

CFRelease(observer);


RunLoop处理逻辑


RunLoop的具体使用

1.图片刷新(假如界面要刷新N多图片(渲染),此时用户拖拽UI控件就会出现卡的效果,我们可以通过RunLoop实现,只在RunLoop默认Mode下下载,也就是拖拽Mode下不刷新图片)

- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"0"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}

2.保证一个线程永远不死
方案一:用一个强引用引用住线程(这种方案是不可行的),原因如下:
#import "ViewController.h"
#import "YCThread.h"
@interface ViewController ()

/*
思路:用一个强引用线程,当点击屏幕的时候再让他启动,结果是不可行!!!!

因为,线程执行完内部的任务后就会自动死亡,你如果用一个强引用引用这个线程,
即使内存还在,但是该线程也已经处于死亡状态(线程状态),是不能再次启动的,
如果再次启动一个死亡状态的线程,就会
报错--reason: '*** -[YCThread start]: attempt(视图) to start the thread again'
*/
/** 线程对象 */
@property (nonatomic, strong)YCThread *thread; // 强引用

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// 创建子线程
self.thread = [[YCThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 启动子线程
[self.thread start];
}

- (void)run {
NSLog(@"----------");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 点击屏幕再次启动线程
[self.thread start];
}

@end

方案二:(死循环+RunLoop),不建议此做法,不是太好
#import "ViewController.h"

@interface ViewController ()
/** 线程对象 */
@property (nonatomic, strong)NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 创建子线程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];

[self.thread start];
}

- (void)run {

NSLog(@"run--%@", [NSThread currentThread]);

// 利用死循环(不建议此做法)
while (1) {
[[NSRunLoop currentRunLoop] run];
}
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
}
- (void)test {

NSLog(@"test--%@", [NSThread currentThread]);
}

@end
方案三:(子线程中加入RunLoop+RunLoop源)建议采用此方案
#import "ViewController.h"
/*
思路:为了保证线程不死,我们考虑在子线程中加入RunLoop,
但是由于RunLoop中没有没有源,就会自动退出RunLoop,
所以我们要为子线程添加一个RunLoop,
并且为这个RunLoop添加源(保证RunLoop不退出)
*/
@interface ViewController ()

/** 线程对象 */
@property (nonatomic, strong)NSThread *thread;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 创建子线程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];

//启动子线程
[self.thread start];

}

- (void)run {

NSLog(@"run--%@", [NSThread currentThread]); // 子线程

// 给子线程添加一个RunLoop,并且加入源
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 启动RunLoop
[[NSRunLoop currentRunLoop] run];

NSLog(@"------------"); // RunLoop启动,这句没有执行的机会
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// 在子线程中调用test方法,如果子线程还在就能够调用成功
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
}

- (void)test {
NSLog(@"test--%@", [NSThread currentThread]); // 子线程
}

@end
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios RunLoop