사용 방법과시기에 대해 약간 혼란 스럽습니다 beginBackgroundTaskWithExpirationHandler
.
Apple은 예제에서 applicationDidEnterBackground
대리자 에서 사용하고 일반적으로 네트워크 트랜잭션과 같은 중요한 작업을 완료하는 데 더 많은 시간을 확보 하는 것을 보여줍니다 .
내 앱을 볼 때 대부분의 네트워크 항목이 중요한 것처럼 보이며 하나가 시작될 때 사용자가 홈 버튼을 눌렀을 때 완료하고 싶습니다.
따라서 모든 네트워크 트랜잭션을 포장하는 것이 허용 / 좋은 관행 beginBackgroundTaskWithExpirationHandler
입니까 (그리고 큰 데이터 청크 다운로드에 대해 말하는 것이 아니라 대부분 짧은 xml) .
답변
네트워크 트랜잭션이 백그라운드에서 계속되도록하려면 백그라운드 작업으로 래핑해야합니다. endBackgroundTask
작업이 끝나면 전화하는 것도 매우 중요합니다. 그렇지 않으면 할당 된 시간이 만료 된 후 앱이 종료됩니다.
내 경향은 다음과 같습니다.
- (void) doUpdate
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
// Do something with the result
[self endBackgroundUpdateTask];
});
}
- (void) beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
UIBackgroundTaskIdentifier
각 백그라운드 작업에 대한 속성 이 있습니다.
Swift의 동등한 코드
func doUpdate () {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = beginBackgroundUpdateTask()
var response: URLResponse?, error: NSError?, request: NSURLRequest?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
// Do something with the result
endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.shared.endBackgroundTask(taskID)
}
답변
받아 들여진 대답은 매우 도움이되며 대부분의 경우 괜찮을 것입니다. 그러나 두 가지가 저를 괴롭 혔습니다.
-
많은 사람들이 언급했듯이 작업 식별자를 속성으로 저장한다는 것은 메서드가 여러 번 호출되면 덮어 쓸 수 있음을 의미하며 시간 만료시 OS가 강제로 종료 할 때까지 작업이 정상적으로 종료되지 않습니다. .
-
이 패턴에는
beginBackgroundTaskWithExpirationHandler
많은 네트워크 메서드가있는 더 큰 앱이있는 경우 성가신 것처럼 보이는 모든 호출에 대해 고유 한 속성이 필요 합니다.
이러한 문제를 해결하기 위해 모든 배관을 관리하고 사전에서 활성 작업을 추적하는 싱글 톤을 작성했습니다. 작업 식별자를 추적하는 데 필요한 속성이 없습니다. 잘 작동하는 것 같습니다. 사용법은 다음과 같이 단순화됩니다.
//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];
//do stuff
//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];
선택적으로 작업 (내장)을 종료하는 것 이상의 작업을 수행하는 완료 블록을 제공하려면 다음을 호출 할 수 있습니다.
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{
//do stuff
}];
아래에서 사용할 수있는 관련 소스 코드 (간결성을 위해 단일 항목 제외). 의견 / 피드백 환영합니다.
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
@synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
@synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
@synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
}
답변
다음은 백그라운드 작업 실행을 캡슐화 하는 Swift 클래스 입니다.
class BackgroundTask {
private let application: UIApplication
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask) -> ()) {
// NOTE: The handler must call end() when it is done
let backgroundTask = BackgroundTask(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
self.identifier = application.beginBackgroundTaskWithExpirationHandler {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}
}
사용하는 가장 간단한 방법 :
BackgroundTask.run(application) { backgroundTask in
// Do something
backgroundTask.end()
}
종료하기 전에 델리게이트 콜백을 기다려야하는 경우 다음과 같이 사용하세요.
class MyClass {
backgroundTask: BackgroundTask?
func doSomething() {
backgroundTask = BackgroundTask(application)
backgroundTask!.begin()
// Do something that waits for callback
}
func callback() {
backgroundTask?.end()
backgroundTask = nil
}
}
답변
여기와 다른 SO 질문에 대한 답변에서 언급했듯이 beginBackgroundTask
앱이 백그라운드로 전환 될 때만 사용하고 싶지는 않습니다 . 반대로, 당신을 위해 백그라운드 작업을 사용해야합니다 어느 누구의 완료하면 앱이 경우에도 보장하려면 시간이 많이 걸리는 작업 않는 배경으로 이동합니다.
따라서 코드는 호출 beginBackgroundTask
및 endBackgroundTask
일관성을 위해 동일한 상용구 코드의 반복으로 끝날 수 있습니다. 이러한 반복을 방지하려면 상용구를 캡슐화 된 단일 엔티티로 패키지화하는 것이 합리적입니다.
이를 수행하는 기존 답변 중 일부가 마음에 들지만 가장 좋은 방법은 Operation 하위 클래스를 사용하는 것입니다.
-
작업을 모든 OperationQueue에 대기열에 추가하고 적절하다고 판단되는대로 해당 대기열을 조작 할 수 있습니다. 예를 들어 큐에있는 기존 작업을 미리 취소 할 수 있습니다.
-
수행 할 작업이 두 개 이상인 경우 여러 백그라운드 작업 작업을 연결할 수 있습니다. 작업은 종속성을 지원합니다.
-
작업 대기열은 백그라운드 대기열이 될 수 있으며 그래야합니다. 따라서 작업 이 비동기 코드 이기 때문에 작업 내에서 비동기 코드를 수행하는 것에 대해 걱정할 필요가 없습니다 . (사실, 작업 내에서 다른 수준의 비동기 코드 를 실행하는 것은 이치 에 맞지 않습니다. 해당 코드가 시작되기 전에 작업이 완료되기 때문입니다.이 작업을 수행해야하는 경우 다른 작업을 사용합니다.)
다음은 가능한 Operation 하위 클래스입니다.
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
이것을 사용하는 방법은 분명해야하지만 그렇지 않은 경우 전역 OperationQueue가 있다고 상상해보십시오.
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
따라서 일반적인 시간 소모적 인 코드 배치의 경우 다음과 같이 말할 수 있습니다.
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
시간이 많이 걸리는 코드 배치를 여러 단계로 나눌 수있는 경우 작업이 취소되면 조기에 작업을 중단하는 것이 좋습니다. 이 경우 폐쇄에서 조기에 반환하십시오. 클로저 내에서 작업에 대한 참조가 약해야합니다. 그렇지 않으면 유지주기가 발생합니다. 다음은 인공적인 그림입니다.
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
백그라운드 작업 자체가 조기에 취소 된 경우 정리해야 할 경우를 대비하여 선택적 cleanup
처리기 속성 (이전 예제에서는 사용되지 않음)을 제공했습니다. 다른 답변은 그것을 포함하지 않은 것에 대해 비판을 받았습니다.
답변
나는 Joel의 솔루션을 구현했습니다. 다음은 완전한 코드입니다.
.h 파일 :
#import <Foundation/Foundation.h>
@interface VMKBackgroundTaskManager : NSObject
+ (id) sharedTasks;
- (NSUInteger)beginTask;
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
- (void)endTaskWithKey:(NSUInteger)_key;
@end
.m 파일 :
#import "VMKBackgroundTaskManager.h"
@interface VMKBackgroundTaskManager()
@property NSUInteger taskKeyCounter;
@property NSMutableDictionary *dictTaskIdentifiers;
@property NSMutableDictionary *dictTaskCompletionBlocks;
@end
@implementation VMKBackgroundTaskManager
+ (id)sharedTasks {
static VMKBackgroundTaskManager *sharedTasks = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedTasks = [[self alloc] init];
});
return sharedTasks;
}
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
@synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
@synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
@synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
NSLog(@"Task ended");
}
}
}
@end
답변
