【发布时间】:2016-07-25 08:42:00
【问题描述】:
我想制作一个上传管理器单例,它每 10 分钟上传一次分段数据。上传本身很清楚,但我怎样才能在后台创建一个在这个时间间隔内上传数据的类?
只是指定:
我要上传的数据是一个包含对象数组的模型。每个对象都有一个标志,当设置此标志时,对象就可以上传了。无论我在哪个 ViewController 上,都应该调用整个“同步功能”并每 10 分钟重复一次。有谁知道我该怎么做?
【问题讨论】:
我想制作一个上传管理器单例,它每 10 分钟上传一次分段数据。上传本身很清楚,但我怎样才能在后台创建一个在这个时间间隔内上传数据的类?
只是指定:
我要上传的数据是一个包含对象数组的模型。每个对象都有一个标志,当设置此标志时,对象就可以上传了。无论我在哪个 ViewController 上,都应该调用整个“同步功能”并每 10 分钟重复一次。有谁知道我该怎么做?
【问题讨论】:
代码使用了一些外部框架。它基于递归。
*上面的框架并不重要。我只是用它来加快开发过程。
同步管理器
import Foundation
import Alamofire
let SyncMangerIdentifier = "com.example.background.syncmanger"
class SyncManager: Alamofire.Manager{
static let instance = SyncManager()
private var pendingTasks = [SyncTask]() // SyncTask is a class with 3 variables [image,audio,[tags]] that are being uploading to server
private var request: Request?
private var isSyncing = false
private init(){
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(SyncMangerIdentifier)
configuration.allowsCellularAccess = Config.cellularAccess
super.init(configuration: configuration)
}
// CALL THIS FUNCTION TO START THE SYNC
// variable isSyncing guards multiple execution of syncManager
func start(){
guard !isSyncing else {
// WE ARE ALREADY SYNCING
return
}
// CALL TO PREPARE FUNCTION TO EVALUATE WHETHER WE CAN SYNC OR NOT
prepare()
}
/*
initialize the syncItem variable with the first entry from SyncTask
if we are stopping return
if syncTask isEmpty stop
if there are no items in first syncTask remove the task and restart the process.
*/
private func prepare(){
// I use a database query to store & retrieve pendingTasks
guard !pendingTasks.isEmpty else{
// todo no more data to sync
isSyncing = false // syncing process ended
// Notify app that your long running task has finished
(UIApplication.sharedApplication().delegate as? AppDelegate)?.endBackgroundSyncTask()
return
}
isSyncing = true // we are in syncing process
// Notify app that our long running task has begun
(UIApplication.sharedApplication().delegate as? AppDelegate)?.beginBackgroundRestoreTask()
// Call function to start the first upload
uploadFileOrData()
}
}
/**
upload the files & data from array recursively
*/
private func uploadFileOrData(){
var task = pendingTasks[0]
let imageUrl = task.imageUrl
let audioUrl = task.audioUrl
let tags = task.tags.reduce(""){ prev, next in
if prev.isEmpty{
return next.text
}
return "\(prev),\(next.text)"
}
let form : (MultipartFormData) -> () = { data in
if imageUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: imageUrl, name: "image")
}
if audioUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: audioUrl, name: "audio")
}
data.appendBodyPart(data: tags.dataUsingEncoding(NSUTF8StringEncoding,allowLossyConversion: false)!, name: "tags")
}
upload(.POST, Api.fileUploadUrl, multipartFormData: form ,encodingCompletion: {
// Call function to process the response
self.processUploadFileResponse($0)
})
}
private func processUploadFileResponse(result: Manager.MultipartFormDataEncodingResult){
switch result {
case .Success(let upload, _, _):
// PERFORM ACTION ON SUCCESS
// MOVE TO NEXT LOCATION
self.moveToNextTask()
case .Failure(_):
// PERFORM ACTION ON FALIURE
// MOVE TO NEXT LOCATION
self.moveToNextTask()
}
}
private func moveToNextTask(){
// DELETE pendingTasks[0] & CALL prepare() function
// If you want to repeat after every 10 MINUTE
// Then wrap your function call 'prepare()' inside dispatch_after
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(10 * 60 * Double(NSEC_PER_SEC)) // 10 * 60 to convert seconds into minute
),
dispatch_get_main_queue(), {
self.prepare()
})
}
AppDelegate 类
// bind the alamofire backgroundCompletionHandler
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
// NSLog("handle events for background: \(identifier)")
if identifier == SyncMangerIdentifier{
SyncManager.instance.backgroundCompletionHandler = completionHandler
}
}
// Identifier for long running background task for SyncManager class
var backgroundSyncTask: UIBackgroundTaskIdentifier?
// Call this at the beginning of syncing
func beginBackgroundSyncTask() {
backgroundRestoreTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundRestoreTask()
})
}
// Call this when syncing process ends
func endBackgroundSyncTask() {
guard backgroundSyncTask != nil else {
return
}
UIApplication.sharedApplication().endBackgroundTask(self.backgroundSyncTask!)
self.backgroundSyncTask = UIBackgroundTaskInvalid
}
注意
为了在您的应用进入后台时继续请求,您可能需要从应用的 Capabilities 部分启用 BackGroundFetchMode
【讨论】:
由于您希望在所有 ViewControllers(VC) 中调用上传方法,因此一种方法是:-
extension UIViewController
{
func uploadData(parameters) ->Bool
{
return true/false;
}
}
然后在所有 ViewControllers(VCs) 中,您可以在 viewDidLoad 或特定函数中调用 uploadData 方法,例如:-
if(self.uploadData(parameters)) // if true method is called i.e. new objects available to upload or 10mins have passed as per your requirement
{
}
第二种方法是定义 NSTimer 部分,它检查 AppDelegate 中是否已经过去了 10 分钟,并创建一个空的 Swift 文件,该文件执行 Upload 并调用 AppDelegate 中 didFinishLaunchingWithOptions 中的方法。
有很多方法可以解决这个问题,但这取决于您的应用中需要如何进行流程。
注意:- 使用 NSURLSessionUploadTask -> 上传和 NSTimer -> 检查是否已经过了 10 分钟
【讨论】: