iOS7 Programming Cookbook-Chapter 7-Concurrency(Timer and Thread部分)

来源:互联网 发布:sql 商业智能 编辑:程序博客网 时间:2024/05/21 06:55

本文是笔者自己自学iOS7开发的过程中,练习翻译的《iOS7 Programming Cookbook》,兼做个人的笔记和代码汇总。

由于第七章篇幅过长,拆分成四部分,这是其中的Timer和Thread部分。

7.14 Creating Timer

问题:你希望按照某一特定频率重复执行一个特殊任务,比如你希望在应用运行时每秒钟都更新你的屏幕

解决方法:使用Timer

#import "AppDelegate.h"@interface AppDelegate ()@property (nonatomic, strong) NSTimer *paintingTimer;@end@implementation AppDelegate- (void) paint:(NSTimer *)paramTimer{    /* Do something here */    NSLog(@"Painting");}-(void)startPainting{    self.paintingTimer=[NSTimer                        scheduledTimerWithTimeInterval:1.0                        target:self                        selector:@selector(paint:)                        userInfo:nil repeats:YES];}-(void)stopPainting{    if(self.paintingTimer!=nil){        [self.paintingTimer invalidate];    }}- (void)applicationWillResignActive:(UIApplication *)application{    [self stopPainting];}- (void)applicationDidBecomeActive:(UIApplication *)application{    [self startPainting];}
invalidate方法也将释放timer,这样我们就不用手动释放了。

讨论:

timer是一种以特定时间间隔执行一个事件的对象。timer必须按照一个运行循环进行定时。定义一个NSTimer对象会创建一个未被定时的计时器,这个计时器暂时不会执行任何行为,但是当你希望定时时,它是可用的。一旦你安排了一次调用,比如scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:那么这个计时器就会成为一个被已定时的计时器,将会启动你要求的事件。一个已被定时了的计时器是被添加了一个运行循环的。为了使计时器可以唤起目标事件,我们必须按照一个运行循环为计时器进行定时。这一点会在之后的例子中进一步说明。

一旦计时器被创建且加入了一个运行循环。它将或隐式或显式地每n秒执行一次目标方法。因为n是浮点数,你可以制定一个1秒以下的数值。
有多种创建,初始化以及为定时一个计时器的方法。其中最简单的一种是通过NSTimer的类方法scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 这个方法有几个不同的参数:
  • scheduledTimerWithTimeInterval:唤起事件之前需要等待的时间,秒数。比如你希望每秒调用目标方法两次,那么你就需要将这个参数设为0.5。
  • target:接收事件的目标对象。
  • selector:会接受到事件的目标对象的方法签名。
  • userInfo:这个实体会被保持,为了之后的引用。
  • repeats:指定该计时器是否重复调用目标方法(参数值设为YES),还是只调用一次(参数值设为NO)。

还有其他的创建计时器的方法,其中一个就是NSTimer的类方法scheduledTimerWithTimeInterval:invocation:repeats:  

@interface AppDelegate ()@property (nonatomic, strong) NSTimer *paintingTimer;@end

- (void) stopPainting{    if (self.paintingTimer != nil){        [self.paintingTimer invalidate];    }}- (void) startPainting{    /* Here is the selector that we want to call */    SEL selectorToCall = @selector(paint:);    /* Here we compose a method signature out of the selector. We     know that the selector is in the current class so it is easy     to construct the method signature */    NSMethodSignature *methodSignature =    [[self class] instanceMethodSignatureForSelector:selectorToCall];    /* Now base our invocation on the method signature. We need this     invocation to schedule a timer */    NSInvocation *invocation =    [NSInvocation invocationWithMethodSignature:methodSignature];    [invocation setTarget:self];    [invocation setSelector:selectorToCall];    self.paintingTimer = [NSTimer timerWithTimeInterval:1.0                                             invocation:invocation                                                repeats:YES];;    /* Do your processing here and whenever you are ready,     use the addTimer:forMode instance method of the NSRunLoop class     in order to schedule the timer on that run loop */    [[NSRunLoop currentRunLoop] addTimer:self.paintingTimer                                 forMode:NSDefaultRunLoopMode];}-(void) paint:(NSTimer *)paramTimer{    /* Do something here */    NSLog(@"Painting");}- (void)applicationWillResignActive:(UIApplication *)application{   [self stopPainting];}- (void)applicationDidBecomeActive:(UIApplication *)application{    [self startPainting];}

为一个计时器定时可以比作启动一辆汽车的引擎。已定时的计时器是一个正在运行的汽车引擎,一个未定时的计时器是一个准备启动但尚未运行的汽车引擎,我们可以在应用中在我们希望的任何时候启动或停止计时器,就想我们需要根据情况启动和停止汽车引擎一样。如果你想在应用中手动为一个计时器定时,你可以使用NSTimer的类方法scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:,你准备好了之后就可以将计时器加入你选择的运行循环之中了:
- (void) startPainting{    self.paintingTimer = [NSTimer timerWithTimeInterval:1.0                                                 target:self                                               selector:@selector(paint:) userInfo:nil                                                repeats:YES];    /* Do your processing here and whenever you are ready,     use the addTimer:forMode instance method of the NSRunLoop class     in order to schedule the timer on that run loop */    [[NSRunLoop currentRunLoop] addTimer:self.paintingTimer                                 forMode:NSDefaultRunLoopMode];}

就像你可以使用 scheduledTimerWithTimeInterval:invocation:repeats: 通过invocation创建定时的计时器。你也可以使用NSTimer的类方法 timerWithTimeInterval:invocation:repeats:来使用invocation创建一个为定时的计时器:

- (void) paint:(NSTimer *)paramTimer{    /* Do something here */    NSLog(@"Painting");}- (void) startPainting{    /* Here is the selector that we want to call */    SEL selectorToCall = @selector(paint:);    /* Here we compose a method signature out of the selector. We     know that the selector is in the current class so it is easy     to construct the method signature */    NSMethodSignature *methodSignature =    [[self class] instanceMethodSignatureForSelector:selectorToCall];    /* Now base our invocation on the method signature. We need this     invocation to schedule a timer */    NSInvocation *invocation =    [NSInvocation invocationWithMethodSignature:methodSignature];    [invocation setTarget:self];    [invocation setSelector:selectorToCall];    self.paintingTimer = [NSTimer timerWithTimeInterval:1.0                                             invocation:invocation                                                repeats:YES];;    /* Do your processing here and whenever you are ready,     use the addTimer:forMode instance method of the NSRunLoop class     in order to schedule the timer on that run loop */    [[NSRunLoop currentRunLoop] addTimer:self.paintingTimer                                 forMode:NSDefaultRunLoopMode];}- (void) stopPainting{    if (self.paintingTimer != nil){        [self.paintingTimer invalidate];    }}- (void)applicationWillResignActive:(UIApplication *)application{    [self stopPainting];}- (void)applicationDidBecomeActive:(UIApplication *)application{    [self startPainting];}

timer的目标方法接收到调用它的那个计时器实例作为参数。比如这个paint:方法证明了计时器如何被传入目标方法,并默认作为目标方法唯一的参数:

- (void) paint:(NSTimer *)paramTimer{    /* Do something here */    NSLog(@"Painting");}

这个参数提供给你一个调用该方法的计时器的引用。比如你可以使用invalidate方法防止计时器重复运行,如果需要的话。你也可以调用NSTimer实例的userInfo方法来恢复计时器持有的对象。这个对象被传入NSTimer的初始化方法,直接被传入计时器中以备以后的引用。


7.15 Creating Concurrency with Threads

问题:你希望对于在应用中分离任务有最大限度的控制。比如你希望在UI仍然可以运行并与用户交互的条件下,运行一个复杂运算。

解决方案:

在你的应用中使用线程,像这样:

-(void)downloadNewFile:(id)paramObject{        @autoreleasepool {                NSString* fileURL=(NSString*)paramObject;        NSURL* url=[NSURL URLWithString:fileURL];        NSURLRequest* request=[NSURLRequest requestWithURL:url];        NSURLResponse* response=nil;        NSError* error=nil;        NSData* downloadedData=[NSURLConnection sendSynchronousRequest:request                                                     returningResponse:&response                                                                 error:&error];        if([downloadedData length]>0){                    }else{                    }            }}- (void)viewDidLoad{    [super viewDidLoad];        NSString *fileToDownload = @"http://www.OReilly.com";    [NSThread detachNewThreadSelector:@selector(downloadNewFile:) toTarget:self                           withObject:fileToDownload];}

讨论:

任何一个iOS应用都由一个或多个线程组成。在iOS中,一个通常有一个视图控制器的应用,会由系统类库创建4或5个线程分配给它。无论你是否使用多线程,都至少会有一个线程创建给你的应用。这个线程就被叫做“主UI线程”。

为了理解线程的价值,我们来做一个实验,假设我们有三个循环:

- (void) firstCounter{        NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"First Counter = %lu", (unsigned long)counter);        }}- (void) secondCounter{        NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"Second Counter = %lu", (unsigned long)counter);        }}- (void) thirdCounter{    NSUInteger counter = 0;    for (counter = 0;counter < 1000;counter++){        NSLog(@"Third Counter = %lu", (unsigned long)counter);    }}- (void)viewDidLoad{    [super viewDidLoad];    [self firstCounter];    [self secondCounter];    [self thirdCounter];}

如果我们希望所有的循环计数同时运行呢?当然,我们应该为每个循环分配一个进程。但是我们已经知道了应用载入时都会为我们创建进程,无论我们写了什么代码在应用中,都会在线程中执行。所以我们只需要为其中两个循环计数创建进程,把第三个循环计数留给主线程执行:

- (void) firstCounter{        @autoreleasepool {           NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"First Counter = %lu", (unsigned long)counter);        }    }}- (void) secondCounter{    @autoreleasepool {        NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"Second Counter = %lu", (unsigned long)counter);        }    }}- (void) thirdCounter{    NSUInteger counter = 0;    for (counter = 0;counter < 1000;counter++){        NSLog(@"Third Counter = %lu", (unsigned long)counter);    }}- (void)viewDidLoad{    [super viewDidLoad];    [NSThread detachNewThreadSelector:@selector(firstCounter) toTarget:self                           withObject:nil];    [NSThread detachNewThreadSelector:@selector(secondCounter) toTarget:self                           withObject:nil]; /* Run this on the main thread */    [self thirdCounter];}

每个线程都需要一个自动释放池作为线程中创建的第一个对象,如果你不这么做的话,当线程退出时,你在线程中分配的所有对象都会导致内存泄露。为了更好地理解这点,让我们来看下面的代码:

- (void) autoreleaseThread:(id)paramSender{    NSBundle *mainBundle = [NSBundle mainBundle];    NSString *filePath = [mainBundle pathForResource:@"MacBookAir"                                              ofType:@"png"];    UIImage *image = [UIImage imageWithContentsOfFile:filePath];    /* Do something with the image */    NSLog(@"Image = %@", image);}- (void)viewDidLoad {    [super viewDidLoad];    [NSThread detachNewThreadSelector:@selector(autoreleaseThread:)                             toTarget:self                           withObject:self];}
如果你运行这段代码,就会得到如下输出:
 *** __NSAutoreleaseNoPool(): Object 0x5b2c990 of    class NSCFString autoreleased with no pool in place - just leaking    *** __NSAutoreleaseNoPool(): Object 0x5b2ca30 of    class NSPathStore2 autoreleased with no pool in place - just leaking    *** __NSAutoreleaseNoPool(): Object 0x5b205c0 of    class NSPathStore2 autoreleased with no pool in place - just leaking    *** __NSAutoreleaseNoPool(): Object 0x5b2d650 of    class UIImage autoreleased with no pool in place - just leaking
这段表示我们创建的实例导致了内存泄露,这是因为我们忘记在在线程中创建自动释放池了。下面是正确的代码:
- (void) autoreleaseThread:(id)paramSender{    @autoreleasepool {            NSBundle *mainBundle = [NSBundle mainBundle];    NSString *filePath = [mainBundle pathForResource:@"MacBookAir"                                              ofType:@"png"];    UIImage *image = [UIImage imageWithContentsOfFile:filePath];    /* Do something with the image */    NSLog(@"Image = %@", image);    }}- (void)viewDidLoad {    [super viewDidLoad];    [NSThread detachNewThreadSelector:@selector(autoreleaseThread:)                             toTarget:self                           withObject:self];}


7.16 Invoking Background Methods

问题:你希望不用和线程打交道就可以创建线程的方法

解决方法:

使用NSObject的实例方法performSelectorInBackground:withObject即可

- (void) firstCounter{        @autoreleasepool {                NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"First Counter = %lu", (unsigned long)counter);        }    }}- (void) secondCounter{    @autoreleasepool {        NSUInteger counter = 0;        for (counter = 0;counter < 1000;counter++){            NSLog(@"Second Counter = %lu", (unsigned long)counter);        }    }}- (void) thirdCounter{    NSUInteger counter = 0;    for (counter = 0;counter < 1000;counter++){        NSLog(@"Third Counter = %lu", (unsigned long)counter);    }}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    [self performSelectorInBackground:@selector(firstCounter)                           withObject:nil];    [self performSelectorInBackground:@selector(secondCounter)                           withObject:nil];    [self performSelectorInBackground:@selector(thirdCounter)                           withObject:nil];    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}

讨论:performSelectorInBackground:withObject方法在后台为我们创建了一个新的进程,等同于为选择器创建了一个新的进程。我们需要注意,自定义的selector需要有一个自动释放池就像计数器内存环境里的其他线程一样。

7.17 Exiting Thread and Timers

问题:你希望停止一个线程或者计时器,或者避免其重新开始

解决方法:

对于计时器,使用NSTimer的实例方法invalidate即可。对于线程,则使用cancel方法。要避免使用线程的exit方法,因为这之后没有机会对该线程进行清理,你的应用会因此导致内存泄露:

NSThread* thread=/*Get the reference to your thread here*/;[thread cancel];NSTimer* timer=/*Get the reference to your timer here*/;[timer invalidate];

讨论:

计时器的退出十分直接,调用invalidate方法即可。你可以简单地调用计时器的invalidate实例方法。在你调用该方法之后,计时器不会再唤起其他的目标事件了。

而线程的退出稍微复杂一些,当线程睡眠时,调用取消(cancel)方法,线程的循环会将任务执行完成后才会退出。

来看下面这个例子:

- (void) threadEntryPoint{    @autoreleasepool {        NSLog(@"Thread Entry Point");        while ([[NSThread currentThread] isCancelled] == NO){            [NSThread sleepForTimeInterval:4];            NSLog(@"Thread Loop");        }        NSLog(@"Thread Finished");    }}- (void) stopThread{    NSLog(@"Cancelling the Thread");    [self.myThread cancel];    NSLog(@"Releasing the thread");    self.myThread = nil;}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.myThread = [[NSThread alloc]                     initWithTarget:self                     selector:@selector(threadEntryPoint) object:nil];        [self performSelector:@selector(stopThread) withObject:nil               afterDelay:3.0f];        [self.myThread start];    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}

输出结果为:

 Thread Entry Point    Cancelling the Thread    Releasing the thread    Thread Loop    Thread Finished

于是我们就清楚地看到线程在退出前仍要将当前循环执行完成。这种情况可以通过在循环内判断线程是否被取消来避免,改进代码如下:

- (void) threadEntryPoint{    @autoreleasepool {        NSLog(@"Thread Entry Point");        while ([[NSThread currentThread] isCancelled] == NO){            [NSThread sleepForTimeInterval:4];            if ([[NSThread currentThread] isCancelled] == NO){                NSLog(@"Thread Loop");            }        }        NSLog(@"Thread Finished");    }}- (void) stopThread{    NSLog(@"Cancelling the Thread");    [self.myThread cancel];    NSLog(@"Releasing the thread");    self.myThread = nil;}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.myThread = [[NSThread alloc]                     initWithTarget:self                     selector:@selector(threadEntryPoint) object:nil];        [self performSelector:@selector(stopThread) withObject:nil               afterDelay:3.0f];        [self.myThread start];    self.window = [[UIWindow alloc] initWithFrame:                   [[UIScreen mainScreen] bounds]];    self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];    return YES;}
0 0
原创粉丝点击