2015-10-07 70 views
2

我有一个应用程序,当用户登录时需要下载大量的数据。我想将它的下载部分移动到后台线程,以便用户无需等待下载即可导航应用程序去完成。我曾尝试以下方法,但有些人仍然锁定应用程序等任何用户的倾斜一下,iOS背景下载

dispatch_async(dispatch_get_main_queue(), ^{ 

}); 

也试过

[self performSelectorInBackground:@selector(loadDataThatToBeFetchedInThread:) 
         withObject:objectArrayThatNeedToFetchData]; 

这一次似乎如果我的活动之间移动只是停止。已尝试将其移到AppDelegate方法,但是当我尝试保存到SQlite数据库时,出现一些错误。难道我做错了什么?有人可以帮忙吗?

在此先感谢

回答

2

好,dispatch_get_main_queue()是要给你主要线程,因此这可能不是你想要的。

相反,你应该使用得到背景队列:

dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ ... }); 

然后,它的习惯或者发送一些通知,甚至打电话回主线程直接(在UI)报告成功:

dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ 
    // Do the download... 
    // Download finishes... 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // Call a UI-updating method, or similar 
    }); 
}); 
2

查阅NSURLSessionNSURLSessionDownloadTask。这是苹果公司最新最好的。

2015 WWDC videos2014 WWDC videos中查看核心网络视频(核心网络的新增功能)。

URL Session Programming Guide也是一个很好的资源。

NSURLSession是开箱即用的异步操作 - 这就是您要查找的内容。

作为奖励,NSURLSessionDownloadTask使您可以轻松地在应用程序更改为后台状态(与后台线程完全不同)时继续下载。它还允许您轻松取消和/或恢复下载。

+0

+1提'NSURLSessionDownloadTask'类。虽然与OP所尝试的策略不同,但它几乎肯定是更好的解决方案。 –

1

我推荐使用NSOperationNSOperationQueue来保持它的清洁。

阅读&观看更多:

这是一个基本的设置,你可以自定义,以满足您的需求

免责声明:虽然它看起来很多,但它弥补了更好的API。

首先,让我们定义一个接口来处理我们的API端点:

// Endpoints.swift 

let api_base = "https://myserver.com/" 
let api_path = "api/" 

protocol EndpointGenerator { 
    func URL() -> NSURL 
} 

extension EndpointGenerator { 
    func URL() -> NSURL { 
     return NSURL(string: api_base)! 
    } 
} 

// Represents a null endpoint. It will fail. 
struct NullEndpoint: EndpointGenerator { } 

enum Endpoint: String, EndpointGenerator { 
    case Login = "login" 
    case SignUp = "signup" 

    func URL() -> NSURL { 
     return NSURL(string: api_base + api_path + self.rawValue)! 
    } 
} 

接下来,让我们来构建我们的定制NSOperation

// Operation.swift 
public class Operation: NSOperation { 
    public typealias Completion = Operation ->() 
    public typealias Error = NSError ->() 

    var endpoint: EndpointGenerator { 
     return NullEndpoint() 
    } 

    var headerParams: [String:String]? { 
     return nil 
    } 

    var requestBody: [String:AnyObject]? { 
     return nil 
    } 

    var method: HTTPMethod { 
     return .GET 
    } 

    var networkTask: NSURLSessionTask? 

    var completion: Completion? 
    var error: Error? 
    public var parsedObject = [String:AnyObject]() 

    override public init() { } 

    public init(completion: Completion, error: Error) { 
     self.completion = completion 
     self.error = error 
    } 

    override public func start() { 
     NSURLSessionImplementaion.execute(self) 
    } 

    override public func cancel() { 
     networkTask?.cancel() 
     networkTask = nil 
    } 
} 

是几乎完成,让我们来处理实际的队列:

// OperationQueue.swift 
public class OperationQueue: NSOperationQueue { 
     public static let internalQueue = OperationQueue() 

     public static func addOperation(operation: NSOperation) { 
      internalQueue.addOperation(operation) 
     } 

     public static func addOperations(operations: NSOperation...) { 
      for operation in operations { 
       addOperation(operation) 
      } 
     } 

     public static func cancellAllOperations() { 
      internalQueue.cancelAllOperations() 
     } 
} 

最后,下载部分:

// NSURLSessionImplementation.swift 
enum HTTPMethod: String { 
    case POST = "POST" 
    case GET = "GET" 
    case PATCH = "PATCH" 
} 

public let OSNetworkingErrorDomain = "com.swanros.errordomain" 

class NSURLSessionImplementaion { 
    class func execute(operation: Operation) { 
     let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration()) 
     let request = NSMutableURLRequest(URL: operation.endpoint.URL()) 
     if let headerParams = operation.headerParams { 
      for element in headerParams { 
       request.setValue(element.1, forHTTPHeaderField: element.0) 
      } 
     } 

     if let body = operation.requestBody { 
      do { 
       request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(body, options: .PrettyPrinted) 
      } catch { 
       return 
      } 
     } 

     request.HTTPMethod = operation.method.rawValue 

     let task = session.dataTaskWithRequest(request) { data, response, error in 
      if let e = error { 
       operation.error?(e) 
       return 
      } 

      guard let d = data else { 
       operation.error?(errorWithDescription("No data")) 
       return 
      } 

      do { 
       let json = try NSJSONSerialization.JSONObjectWithData(d, options: .MutableLeaves) as? [String:AnyObject] 
       guard let j = json else { 
        operation.error?(errorWithDescription("Error parsing JSON.")) 
        return 
       } 

       if let errorMessage = string(j, key: "error") { 
        operation.error?(errorWithDescription(errorMessage)) 
        return 
       } 

       operation.parsedObject = j 
       operation.completion?(operation) 
      } catch let jsonError as NSError { 
       operation.error?(jsonError) 
      } 
     } 

     operation.networkTask = task 
     task.resume() 
    } 
} 

func errorWithDescription(desc: String) -> NSError { 
    return NSError(domain: OSNetworkingErrorDomain, code: 0, userInfo: [NSLocalizedDescriptionKey:desc]) 
} 

你是如何实现这个的?假设你想要点击/login端点。子类Operation如下:

// LogInOperation.swift 
public class LogInOperation: Operation { 
    override var endpoint: EndpointGenerator { 
     // A nice way to represent endpoints: use enums and protocols! 
     return Endpoint.Login 
    } 

    // The headers for this particular request. Maybe you need a token here! 
    override var headerParams: [String:String]? { 
     return [ 
      "Content-Type": "application/json", 
      "Application-Id": "bAAvLosWNeSTHrlYilysdeEYoJHUXs88" 
     ] 
    } 

    // The HTTP request body! 
    override var requestBody: [String:AnyObject]? { 
     return [ 
      "mail": mail, 
      "password": password 
     ] 
    } 

    // .GET is default 
    override var method: HTTPMethod { 
     return .POST 
    } 

    private var mail: String 
    private var password: String 

    public init(mail m: String, password p: String, completion: Completion, error: Error) { 
     mail = m 
     password = p 

     super.init(completion: completion, error: error) 
    } 
} 

并且你使用这样的:

// ViewController.swift 

let loginOperation = LogInOperation(
         mail: "[email protected]", 
         password: "123123", 
         completion: { op in 
          // parsedObject would be the user's info 
          print(op.parsedObject?) 
         }, error: { error in 
          print(error.localizedDescription) 
         } 
        ) 
OperationQueue.addOperation(loginOperation)