事件循环NSRunLoop程序员
一、run loop概念web
NSRunLoop类封装了线程进入事件循环的过程,一个runloop实例就表示了一个线程的事件循环。更具体的说,在iOS开发框架中,线程每次执行完成程序员自定义的代码以后,都会检查当前线程对应的run loop中是否还有其余事件源,若是还有,那么线程就不会终止。网络
处于事件循环的线程接收的事件源有两种:input source 和 timer source。线程调用便利函数 [NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:] 在建立一个NSTimer实例的同时,以默认模式Default mode在当前线程的run loop中注册了一个timer source,而且把新建立的timer添加到run loop中,做为事件的观察者。app
不过每一个线程在建立定时器的时候不马上把它添加到run loop中,只须要调用 [NSTimer timerWithTimeInterval: target: selector: userInfo: repeats:],或者能够调用 [[NSTimer alloc] initWithFireDate:interval:target:selector:userInfo:repeats: ],两种方法等效。而后再使用[NSRunloop currentRunLoop]获取对应的事件循环对象,再调用 [runloop addTimer: forMode:] 方法,那么就会注册一个定时事件,在这个定时器失效以前,当前线程就回去检查事件源,不会直接终止,而是会处于等待事件发生的状态。框架
以主线程为例,当线程执行完成程序员定义的代码以后,就会检查run loop中的事件,而不会终止。不过须要注意,若是主线程请求了同步信号量,也会阻塞,能够实现同步网络请求,防止main thread和web thread之外的线程更新界面,致使crash。函数
二、run loop mode类型oop
在不一样run loop mode下运行的线程,运行过程有所不一样,线程只会检查当前mode下的事件源。例如:编码
- (void)viewDidLoad { [super viewDidLoad]; NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printMessage:) userInfo:nil repeats:YES]; }
这个时候若是咱们在界面上滚动一个scrollview,那么咱们会发如今中止滚动前,控制台不会有任何输出,就好像scrollView在滚动的时候将timer暂停了同样,这其实就是应为run loop处于不一样的mode。atom
添加一个NSTimer到当前的runloop中的同时,还必需要设定事件源的run loop mode,而当scrollView滚动的时候,当前的MainRunLoop对象是处于UITrackingRunLoopMode的模式下,在这个模式下,并不会处理NSDefaultRunLoopMode的消息(由于线程所处的run loop mode和事件源的run loop mode不匹配),要想在scrollView滚动的同时也接受其它runloop的消息,咱们须要改变二者之间的run loop mode.spa
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
三、定时器NSTimer
上文中其实已经讲解了定时器的某些用法,也就是可使用NSTimer的实例在一个NSRunLoop实例中注册一个定时事件源,而且把这个timer实例注册为这个事件的观察者。
一个定时器和定时事件是绑定的,使用定时器中的fire方法和invalidate方法来控制一个timer的生命周期,无重复的定时器在fire后当即invalidate,对于不断重复的timer来讲,就须要手动invalidate。或者能够更改一个定时器触发的日期,若是触发日期是distant past,那么就会当即触发。如
//关闭定时器
[myTimer setFireDate:[NSDate distantFuture]];
//开启定时器
[myTimer setFireDate:[NSDate distantPast]];
当一个做为监听者的NSTimer实例被触发的时候,线程将会调用这个NSTimer实例的动做回调方法,有一种方法能够方便的传递多个参数,那就是使用调用类NSInvocation类的实例。
#import <Foundation/Foundation.h> #import "MyClass.h" int main (int argc, const char * argv[]) { @autoreleasepool{ MyClass *myClass = [[MyClass alloc] init]; NSString *myString = @"My string"; //普通调用 NSString *normalInvokeString = [myClass appendMyString:myString]; NSLog(@"The normal invoke string is: %@", normalInvokeString); //NSInvocation调用 SEL mySelector = @selector(appendMyString:); NSMethodSignature * sig = [[myClass class] instanceMethodSignatureForSelector: mySelector]; NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature: sig]; [myInvocation setTarget: myClass]; [myInvocation setSelector: mySelector]; [myInvocation setArgument: &myString atIndex: 2]; NSString * result = nil; [myInvocation retainArguments]; [myInvocation invoke]; [myInvocation getReturnValue: &result]; NSLog(@"The NSInvocation invoke string is: %@", result); return 0; }
}
前两个参数是隐藏参数self和_cmd,对应target和selector,因此自定义参数从索引2开始。
四、日期对象 NSDate, NSDateFormatter
NSDate的实例表示一个日期,线程能够借助于NSDateFormatter的实例实现NSDate对象和NSString对象的相互转换。
// date方法返回的就是当前时间(now) NSDate *date = [NSDate date]; // now: 11:12:40 // date: 11:12:50 date = [NSDate dateWithTimeIntervalSinceNow:10];//返回当前时间10秒后的时间 // 从1970-1-1 00:00:00开始 date = [NSDate dateWithTimeIntervalSince1970:10];//返回1970-1-1 00:00:00时间10秒后的时间 // 随机返回一个比较遥远的将来时间 date = [NSDate distantFuture]; // 随机返回一个比较遥远的过去时间 date = [NSDate distantPast]; // 返回1970-1-1开始走过的毫秒数 NSTimeInterval interval = [date timeIntervalSince1970]; // 跟其余时间进行对比 NSDate *date2 = [NSDate date]; // 返回比较早的那个时间 [date earlierDate:date2]; // 返回比较晚的那个时间 [date laterDate:date2]; //获取两个时间的时间差 [date1 timeIntervalSinceDate date2]; NSDate *date = [NSDate date]; // 2015-04-07 11:14:45 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; // HH是24进制,hh是12进制 formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; // formatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"] autorelease]; NSString *string = [formatter stringFromDate:date]; NSDate *date2 = [formatter dateFromString:@"2016-03-09 13:14:56"];
关于KVC和KVO
一、KVC 键值编码
在什么场景下须要KVC?最简单的一种应用场景,若是一个控件的属性被声明为@property(nonatomic,readonly)只读,那么就只能经过KVC去修改这个属性,好比当咱们须要用自定义tabBar替换UITabBarController中的原始tabBar的时候。
二、KVO 键值监听
Cocoa开发框架实现了通知机制,程序员无需编写太多代码就能够建立观察者,能够实现数据改变后对每一个观察者的通知,应用场景能够是数据模型被一个控制器改变后,通知其它控制器。
- (void)setFinished:(BOOL)finished2 { [self willChangeValueForKey:@"isFinished"]; _finished = finished2; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing2 { [self willChangeValueForKey:@"isExecuting"]; _executing = executing2; [self didChangeValueForKey:@"isExecuting"]; }
要实现KVO,通常的步骤为:
(1)假设PersonObject但愿可以觉察到BankObject对象的accountBalance属性的任何变化。
(2)那么 PersonObject必须发送一个“addObserver:forKeyPath:options:context:”消息,注册成为 BankObject的accountBalance属性的观察者。“addObserver:forKeyPath:options:context:”方法在指定对象实例之间创建了一个链接。
(3)为了可以响应消息,观察者必须实现 “observeValueForKeyPath:ofObject:change:context:”方法。这个方法实现如何响应变化的消息。在这个方法里面咱们能够跟本身的状况,去实现应对被观察对象属性变更的相应逻辑。
(4)若是遵循KVO规则的话,当被观察的属性改变时调用willChangeValueForKey和didChangeValueForKey,那么方法 “observeValueForKeyPath:ofObject:change:context:”会自动被调用。
关于ARC
众所周知iOS中的采用引用计数来实现垃圾回收,有两种状况不是ARC可以解决的:循环强引用和经过C代码分配堆内存,这就须要程序员的注意。
如今先忙别的,过几天再写。此外过几天再写与block相关的ARC问题,好比下面
一、循环引用
最多见的循环引用错误出如今block中:当block中捕获了self指针的时候,只要block存在,那么self表示的对象就永远不会被release。
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
__weak typeof(self) weakSelf = self;self.completionBlock = ^ { if (weakSelf.success) { weakSelf.success(weakSelf.responseData); }};