OS 多线程NSThread、NSOperation、GCD详解
iOS有三种多线程编程的技术,分别是:
1、NSThread
2、Cocoa NSOperation (iOS多线程编程之NSOperation和NSOperationQueue的使用)
3、GCD 全称:Grand Central Dispatch( iOS多线程编程之Grand Central Dispatch(GCD)介绍和使用)
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
这篇我们主要介绍和使用NSThread,后面会继续2、3 的讲解和使用。
1.2 三种方式的有缺点介绍:
NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread实现的技术有下面三种:
Technology |
Description |
---|---|
Cocoa threads | Cocoa implements threads using the NSThread class. Cocoa also provides methods on NSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.” |
POSIX threads | POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads” |
Multiprocessing Services | Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in OS X only and should be avoided for any new development. Instead, you should use the NSThread class or POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide. |
一般使用cocoa thread 技术。
Cocoa operation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。
GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到6了,所以不用担心该技术不能使用。
介绍完这三种多线程编程方式,我们这篇先介绍NSThread的使用。
2、NSThread的使用
2.1 NSThread 有两种直接创建方式:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
第一个是实例方法,第二个是类方法
1
2
3
|
[
NSThread
detachNewThreadSelector
:
@
selector
(
doSomething
:
)
toTarget
:
self
withObject
:
nil
]
;
NSThread*
myThread
=
[
[
NSThread
alloc
]
initWithTarget
:
selfselector
:
@
selector
(
doSomething
:
)
object
:
nil
]
;
[
myThread
start
]
;
|
2.2参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
target :selector消息发送的对象
argument:传输给target的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息
2.3 PS:不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
2.4 下载图片的例子:
2.4.1 新建singeView app
新建项目,并在xib文件上放置一个imageView控件。按住control键拖到viewControll
er.h文件中创建imageView IBOutlet
ViewController.m中实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
//
// ViewController.m
// NSThreadDemo
//
// Created by rongfzh on 12-9-23.
// Copyright (c) 2012年 rongfzh. All rights reserved.
//
#import "ViewController.h"
#define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"
@
interface
ViewController
(
)
@
end
@
implementation
ViewController
-
(
void
)
downloadImage
:
(
NSString *
)
url
{
NSData *
data
=
[
[
NSData
alloc
]
initWithContentsOfURL
:
[
NSURL
URLWithString
:
url
]
]
;
UIImage *
image
=
[
[
UIImage
alloc
]
initWithData
:
data
]
;
if
(
image
==
nil
)
{
}
else
{
[
self
performSelectorOnMainThread
:
@
selector
(
updateUI
:
)
withObject
:
image
waitUntilDone
:
YES
]
;
}
}
-
(
void
)
updateUI
:
(
UIImage*
)
image
{
self
.
imageView
.
image
=
image
;
}
-
(
void
)
viewDidLoad
{
[
super
viewDidLoad
]
;
// [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];
NSThread *
thread
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
downloadImage
:
)
object
:
kURL
]
;
[
thread
start
]
;
}
-
(
void
)
didReceiveMemoryWarning
{
[
super
didReceiveMemoryWarning
]
;
// Dispose of any resources that can be recreated.
}
@
end
|
2.4.2线程间通讯
线程下载完图片后怎么通知主线程更新界面呢?
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:
用:performSelector:onThread:withObject:waitUntilDone:
运行下载图片:
图片下载下来了。
2.3 线程同步
我们演示一个经典的卖票的例子来讲NSThread的线程同步:
.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#import
@
class
ViewController
;
@
interface
AppDelegate
:
UIResponder
<
UIApplicationDelegate
>
{
int
tickets
;
int
count
;
NSThread*
ticketsThreadone
;
NSThread*
ticketsThreadtwo
;
NSCondition*
ticketsCondition
;
NSLock *
theLock
;
}
@
property
(
strong
,
nonatomic
)
UIWindow *
window
;
@
property
(
strong
,
nonatomic
)
ViewController *
viewController
;
@
end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
-
(
BOOL
)
application
:
(
UIApplication *
)
application
didFinishLaunchingWithOptions
:
(
NSDictionary *
)
launchOptions
{
tickets
=
100
;
count
=
0
;
theLock
=
[
[
NSLock
alloc
]
init
]
;
// 锁对象
ticketsCondition
=
[
[
NSCondition
alloc
]
init
]
;
ticketsThreadone
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
run
)
object
:
nil
]
;
[
ticketsThreadone
setName
:
@
"Thread-1"
]
;
[
ticketsThreadone
start
]
;
ticketsThreadtwo
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
run
)
object
:
nil
]
;
[
ticketsThreadtwo
setName
:
@
"Thread-2"
]
;
[
ticketsThreadtwo
start
]
;
self
.
window
=
[
[
UIWindow
alloc
]
initWithFrame
:
[
[
UIScreen
mainScreen
]
bounds
]
]
;
// Override point for customization after application launch.
self
.
viewController
=
[
[
ViewController
alloc
]
initWithNibName
:
@
"ViewController"
bundle
:
nil
]
;
self
.
window
.
rootViewController
=
self
.
viewController
;
[
self
.
window
makeKeyAndVisible
]
;
return
YES
;
}
-
(
void
)
run
{
while
(
TRUE
)
{
// 上锁
// [ticketsCondition lock];
[
theLock
lock
]
;
if
(
tickets
>=
0
)
{
[
NSThread
sleepForTimeInterval
:
0.09
]
;
count
=
100
-
tickets
;
NSLog
(
@
"当前票数是:%d,售出:%d,线程名:%@"
,
tickets
,
count
,
[
[
NSThread
currentThread
]
name
]
)
;
tickets
--
;
}
else
{
break
;
}
[
theLock
unlock
]
;
// [ticketsCondition unlock];
}
}
|
如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。
线程的顺序执行
他们都可以通过
[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
#import "AppDelegate.h"
#import "ViewController.h"
@
implementation
AppDelegate
-
(
BOOL
)
application
:
(
UIApplication *
)
application
didFinishLaunchingWithOptions
:
(
NSDictionary *
)
launchOptions
{
tickets
=
100
;
count
=
0
;
theLock
=
[
[
NSLock
alloc
]
init
]
;
// 锁对象
ticketsCondition
=
[
[
NSCondition
alloc
]
init
]
;
ticketsThreadone
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
run
)
object
:
nil
]
;
[
ticketsThreadone
setName
:
@
"Thread-1"
]
;
[
ticketsThreadone
start
]
;
ticketsThreadtwo
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
run
)
object
:
nil
]
;
[
ticketsThreadtwo
setName
:
@
"Thread-2"
]
;
[
ticketsThreadtwo
start
]
;
NSThread *
ticketsThreadthree
=
[
[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
run3
)
object
:
nil
]
;
[
ticketsThreadthree
setName
:
@
"Thread-3"
]
;
[
ticketsThreadthree
start
]
;
self
.
window
=
[
[
UIWindow
alloc
]
initWithFrame
:
[
[
UIScreen
mainScreen
]
bounds
]
]
;
// Override point for customization after application launch.
self
.
viewController
=
[
[
ViewController
alloc
]
initWithNibName
:
@
"ViewController"
bundle
:
nil
]
;
self
.
window
.
rootViewController
=
self
.
viewController
;
[
self
.
window
makeKeyAndVisible
]
;
return
YES
;
}
-
(
void
)
run3
{
while
(
YES
)
{
[
ticketsCondition
lock
]
;
[
NSThread
sleepForTimeInterval
:
3
]
;
[
ticketsCondition
signal
]
;
[
ticketsCondition
unlock
]
;
}
}
-
(
void
)
run
{
while
(
TRUE
)
{
// 上锁
[
ticketsCondition
lock
]
;
[
ticketsCondition
wait
]
;
[
theLock
lock
]
;
if
(
tickets
>=
0
)
{
[
NSThread
sleepForTimeInterval
:
0.09
]
;
count
=
100
-
tickets
;
NSLog
(
@
"当前票数是:%d,售出:%d,线程名:%@"
,
tickets
,
count
,
[
[
NSThread
currentThread
]
name
]
)
;
tickets
--
;
}
else
{
break
;
}
[
theLock
unlock
]
;
[
ticketsCondition
unlock
]
;
}
}
|
wait是等待,我加了一个 线程3 去唤醒其他两个线程锁中的wait
其他同步
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。
1
2
3
4
5
6
7
|
-
(
void
)
doSomeThing
:
(
id
)
anObj
{
@
synchronized
(
anObj
)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
|
还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,可以自己看官方文档学习
iOS多线程之NSOperation
1.NSOperation的理解
NSOperation本身是一个抽象的基类,我们要自己子类化NSOpeartion并封装我们要完成的任务,系统提供了两个比较方便的子类NSInvocationOperation和NSBlockOperation,NSInvocationOperation方便我们以现有的方法来初始化一个operation,NSBlockOperation是方便我们从Block来初始化operation。所有的NSOperation都有如下特征:
1. 支持NSOperation对象之间建立依赖关系,当一个operation的所有依赖的operation都执行完了自己才会执行。
2. 支持可选的completion block,当operation的主任务完成之后调用。
3. 支持通过KVO通知来监控operation的执行状态。
4. 支持指定operaion的优先级来安排operation的相对指向顺序。
5. 支持operation的取消操作来停止operation执行。
2.NSOperation的执行
通常情况下我们要并发执行的话只要简单的把operation添加到operation queue中就行,operation queue负责创建线程并发地执行operation任务。
1
2
3
|
MyOperation *
operation
=
[
MyOperation
alloc
]
init
]
;
NSOperationQueue*
aQueue
=
[
[
NSOperationQueue
alloc
]
init
]
;
[
aQueue
addOperation
:
operation
]
;
|
我们也可以直接通过NSOpeartion的start方法来处理operation任务,这样执行的话任务的处理是在start调用的线程中同步执行的。
1
2
|
MyOperation *
operation
=
[
MyOperation
alloc
]
init
]
;
[
operation
start
]
;
|
isConcurrent属性返回的就是operation要执行的任务相对于调用start的线程是同步的还是异步的,isConcurrent默认返回NO。当然我们也可以自己创建线程来执行NSOperation的start方法达到并发执行的效果。
3.NSInvocationOperation的使用
1
2
3
4
5
6
7
8
9
10
11
12
|
@
implementation
MyCustomClass
-
(
NSOperation*
)
taskWithData
:
(
id
)
data
{
NSInvocationOperation*
theOp
=
[
[
NSInvocationOperation
alloc
]
initWithTarget
:
self
selector
:
@
selector
(
myTaskMethod
:
)
object
:
data
]
;
return
theOp
;
}
// This is the method that does the actual work of the task.
-
(
void
)
myTaskMethod
:
(
id
)
data
{
// Perform the task.
}
@
end
|
4.NSBlockOperation的使用
1
2
3
4
5
|
NSBlockOperation*
theOp
=
[
NSBlockOperation
blockOperationWithBlock
:
^
{
NSLog
(
@
"Beginning operation.\n"
)
;
// Do some work.
}
]
;
|
NSBlockOperation可以通过addExecutionBlock:方法添加多个block,只有所有block都执行完成的时候,operation才算是完成。
5.自定义NSOperation
NSOperation提供了一个基本结构,具体相关的并发执行的方法、operation依赖、KVO通知和取消操作还是需要我们自己实现的。整个NSOperation子类实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
@
interface
MyOperation
:
NSOperation
{
BOOL
executing
;
BOOL
finished
;
}
-
(
void
)
completeOperation
;
@
end
@
implementation
MyOperation
-
(
id
)
init
{
self
=
[
super
init
]
;
if
(
self
)
{
executing
=
NO
;
finished
=
NO
;
}
return
self
;
}
-
(
void
)
start
{
// Always check for cancellation before launching the task.
if
(
[
self
isCancelled
]
)
{
// Must move the operation to the finished state if it is canceled.
[
self
willChangeValueForKey
:
@
"isFinished"
]
;
finished
=
YES
;
[
self
didChangeValueForKey
:
@
"isFinished"
]
;
return
;
}
// If the operation is not canceled, begin executing the task.
[
self
willChangeValueForKey
:
@
"isExecuting"
]
;
[
self
main
]
;
executing
=
YES
;
[
self
didChangeValueForKey
:
@
"isExecuting"
]
;
}
-
(
void
)
main
{
@
autoreleasepool
{
BOOL
isDone
=
NO
;
while
(
!
[
self
isCancelled
]
&&
!
isDone
)
{
// Do some work and set isDone to YES when finished
}
[
self
completeOperation
]
;
}
}
-
(
void
)
completeOperation
{
[
self
willChangeValueForKey
:
@
"isFinished"
]
;
[
self
willChangeValueForKey
:
@
"isExecuting"
]
;
executing
=
NO
;
finished
=
YES
;
[
self
didChangeValueForKey
:
@
"isExecuting"
]
;
[
self
didChangeValueForKey
:
@
"isFinished"
]
;
}
-
(
void
)
cancel
{
[
super
cancel
]
;
//取消网络请求
}
-
(
BOOL
)
isConcurrent
{
return
YES
;
}
-
(
BOOL
)
isExecuting
{
return
executing
;
}
-
(
BOOL
)
isFinished
{
return
finished
;
}
@
end
|
并发operation需要实现以下方法:
1. start
2. main
3. isConcurrent
4. isExecuting
5. isFinished
NSOperation支持KVO的属性有:
1. isCancelled
2. isConcurrent
3. isExecuting
4. isFinished
5. isReady
6. dependencies
7. queuePriority
8. completionBlock
不是所有的属性都要我们发送KVO通知,如果我们覆盖了start方法,就要像上面的代码一样在适当的地方发出isExecuting和isFinished的通知。如果我们实现了自己的addDependency:或者removeDependency:,就要适当的地方发送dependencies的通知。
6.配置NSOperation
6.1依赖管理
我们可以通过addDependency:给当前operation添加依赖operation,依赖关系不限于operation是否在一个队列里。注意我们不要自己生成循环依赖,这样会造成死锁。
6.2优先级管理
在队列里的operation的执行顺序取决于operation的状态(是否准备就绪)和相对优先级。是否准备就绪取决于operation的依赖operation是否完成,执行队列会优先考虑operation,如果高优先级的operation没有准备就绪,执行队列就会先执行低优先级的operation。operation的相对优先级只针对同一队列,就是说一个低优先级的operation可能比另一个队列里高优先级的operation优先执行。
6.3底层线程优先级管理
我们可以通过setThreadPriority:设置operation系统级别的线程优先级,优先级由0.0到1.0浮点数指定,默认是0.5。我们自己设置的系统级别的线程优先级只针对main方法执行期间有效,其他方法都是按照默认优先级执行。
IOS多线程GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
dispatch queue分成以下三种:
1)运行在主线程的Main queue,通过dispatch_get_main_queue获取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/*!
* @function dispatch_get_main_queue
*
* @abstract
* Returns the default queue that is bound to the main thread.
*
* @discussion
* In order to invoke blocks submitted to the main queue, the application must
* call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
* thread.
*
* @result
* Returns the main queue. This queue is created automatically on behalf of
* the main thread before main() is called.
*/
__OSX_AVAILABLE_STARTING
(
__MAC_10_6
,
__IPHONE_4_0
)
DISPATCH_EXPORT
struct
dispatch_queue_s
_dispatch_main_q
;
#define dispatch_get_main_queue() \
DISPATCH_GLOBAL_OBJECT
(
dispatch_queue_t
,
_dispatch_main_q
)
|
可以看出,dispatch_get_main_queue也是一种dispatch_queue_t。
2)并行队列global dispatch queue,通过dispatch_get_global_queue获取,由系统创建三个不同优先级的dispatch queue。并行队列的执行顺序与其加入队列的顺序相同。
3)串行队列serial queues一般用于按顺序同步访问,可创建任意数量的串行队列,各个串行队列之间是并发的。
当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。
serial queues通过dispatch_queue_create创建,可以使用函数dispatch_retain和dispatch_release去增加或者减少引用计数。
GCD的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
// 后台执行:
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
)
,
^
{
// something
}
)
;
// 主线程执行:
dispatch_async
(
dispatch_get_main_queue
(
)
,
^
{
// something
}
)
;
// 一次性执行:
static
dispatch_once_t
onceToken
;
dispatch_once
(
&
onceToken
,
^
{
// code to be executed once
}
)
;
// 延迟2秒执行:
double
delayInSeconds
=
2.0
;
dispatch_time_t
popTime
=
dispatch_time
(
DISPATCH_TIME_NOW
,
delayInSeconds *
NSEC_PER_SEC
)
;
dispatch_after
(
popTime
,
dispatch_get_main_queue
(
)
,
^
(
void
)
{
// code to be executed on the main queue after delay
}
)
;
// 自定义dispatch_queue_t
dispatch_queue_t
urls_queue
=
dispatch_queue_create
(
"blog.devtang.com"
,
NULL
)
;
dispatch_async
(
urls_queue
,
^
{
// your code
}
)
;
dispatch_release
(
urls_queue
)
;
// 合并汇总结果
dispatch_group_t
group
=
dispatch_group_create
(
)
;
dispatch_group_async
(
group
,
dispatch_get_global_queue
(
0
,
0
)
,
^
{
// 并行执行的线程一
}
)
;
dispatch_group_async
(
group
,
dispatch_get_global_queue
(
0
,
0
)
,
^
{
// 并行执行的线程二
}
)
;
dispatch_group_notify
(
group
,
dispatch_get_global_queue
(
0
,
0
)
,
^
{
// 汇总结果
}
)
;
|
一个应用GCD的例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
NSURL *
url
=
[
NSURL
URLWithString
:
@
"http://www.baidu.com"
]
;
NSError *
error
;
NSString *
data
=
[
NSString
stringWithContentsOfURL
:
url
encoding
:
NSUTF8StringEncoding
error
:
&
error
]
;
if
(
data
!=
nil
)
{
dispatch_async
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@
"call back, the data is: %@"
,
data
)
;
}
)
;
}
else
{
NSLog
(
@
"error when download:%@"
,
error
)
;
}
}
)
;
|
GCD的另一个用处是可以让程序在后台较长久的运行。
在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
让程序在后台长久运行的示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// AppDelegate.h文件
@
property
(
assign
,
nonatomic
)
UIBackgroundTaskIdentifier
backgroundUpdateTask
;
// AppDelegate.m文件
-
(
void
)
applicationDidEnterBackground
:
(
UIApplication *
)
application
{
[
self
beingBackgroundUpdateTask
]
;
// 在这里加上你需要长久运行的代码
[
self
endBackgroundUpdateTask
]
;
}
-
(
void
)
beingBackgroundUpdateTask
{
self
.
backgroundUpdateTask
=
[
[
UIApplication
sharedApplication
]
beginBackgroundTaskWithExpirationHandler
:
^
{
[
self
endBackgroundUpdateTask
]
;
}
]
;
}
-
(
void
)
endBackgroundUpdateTask
{
[
[
UIApplication
sharedApplication
]
endBackgroundTask
:
self
.
backgroundUpdateTask
]
;
self
.
backgroundUpdateTask
=
UIBackgroundTaskInvalid
;
}
|