zoukankan      html  css  js  c++  java
  • Cocoa Touch(六):App运行机制 NSRunLoop, KVC, KVO, Notification, ARC

    事件循环NSRunLoop

    1、run loop概念

        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中,作为事件的观察者。

        不过每个线程在创建定时器的时候不立刻把它添加到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。

    2、run loop mode类型

        在不同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。

        添加一个NSTimer到当前的runloop中的同时,还必须要设定事件源的run loop mode,而当scrollView滚动的时候,当前的MainRunLoop对象是处于UITrackingRunLoopMode的模式下,在这个模式下,并不会处理NSDefaultRunLoopMode的消息(因为线程所处的run loop mode和事件源的run loop mode不匹配),要想在scrollView滚动的同时也接受其它runloop的消息,我们需要改变两者之间的run loop mode.

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    3、定时器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开始。

    4、日期对象 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

    1、KVC 键值编码

        在什么场景下需要KVC?最简单的一种应用场景,如果一个控件的属性被声明为@property(nonatomic,readonly)只读,那么就只能通过KVC去修改这个属性,比如当我们需要用自定义tabBar替换UITabBarController中的原始tabBar的时候。

    2、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问题,比如下面

    1、循环引用

        最常见的循环引用错误出现在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);
    }
    };

  • 相关阅读:
    bzoj3237[Ahoi2013] 连通图
    bzoj3075[Usaco2013]Necklace
    bzoj1876[SDOI2009] SuperGCD
    bzoj3295[Cqoi2011] 动态逆序对
    BestCoder#86 E / hdu5808 Price List Strike Back
    bzoj2223[Coci 2009] PATULJCI
    bzoj2738 矩阵乘法
    poj 1321 -- 棋盘问题
    poj 3083 -- Children of the Candy Corn
    poj 2488 -- A Knight's Journey
  • 原文地址:https://www.cnblogs.com/xinchrome/p/5348564.html
Copyright © 2011-2022 走看看