我正在尝试使用Alamofire上传图片。此外,我正在使用ReachabilitySwift来了解互联网连接的状态。现在,当我尝试上传图像并在中间关闭网络时,我删除了所有的alamofire请求。以下是代码:
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach { $0.cancel() }
uploadTasks.forEach { $0.cancel() }
downloadTasks.forEach { $0.cancel() }
}
当互联网再次启动时,我再次开始上传过程。这是代码:
func internetAvailable(){
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async{
self.uploadImage()
}
}
}
func uploadImagesToServer(){
DispatchQueue.main.async{
self.uploadImage()
}
}
首先在viewDidLoad
中,uploadImagesToServer()
被调用。在该方法的中间,当它仍然上传图像时,互联网被关闭。当互联网重新启动时,会转到internetAvailable()
,上传图片,但是当我尝试重新加载表时,会转到numberOfRowsInSection
,但不会转到cellForRow
方法。
以下是我尝试的内容:
numberOfRowsInSection
中的计数,这是正确的。尝试使用以下方法在主线程中调用tableview.reloadData()
:
DispatchQueue.main.async {
self.tableView.reloadData()
}
TableView代码:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfImages
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//places image onto imageView
}
任何帮助都将不胜感激。
答案 0 :(得分:0)
可达性的替代方法是使用后台URLSession
进行上传。这样,您无需对Reachability执行任何操作。当重新建立连接时,即使您的应用程序未运行,也会自动发送您启动的上传。
后台会话涉及一些限制:
上传任务必须使用基于文件的上传(不是Data
或Stream
)。这意味着一旦您构建了请求,就必须在上传之前将其保存到文件中。 (如果您构建了一个分段上传请求,Alamofire会为您执行此操作。)
后台会话的整体想法是,即使您的应用已暂停(或终止),它们也会继续运行。因此,您无法使用我们熟悉的完成处理程序模式(因为这些关闭可能在上载请求时被丢弃)。因此,您必须依赖taskDidComplete
关闭SessionDelegate
来确定请求是否成功完成。
您必须在app委托中实施handleEventsForBackgroundURLSession
,保存完成处理程序。如果上传完成后您的应用程序未运行,操作系统将调用此方法,该方法必须在处理完成时调用。您必须向Alamofire提供此完成处理程序,以便它可以为您执行此操作。
如果您忽略保存此完成处理程序(因此如果Alamofire无法代表您调用它),您的应用程序将被立即终止。确保保存此完成处理程序,以便您的应用程序可以在上传完成后透明地执行所需操作,然后再次正常暂停。
警告您:如果用户强制退出您的应用(双击主页按钮并向上滑动应用),则会取消所有待处理的后台上传。下次启动应用程序时,它会通知您任何已取消的任务。
但是,如果用户刚刚离开您的应用(例如,只需点击主页按钮),任何待处理的上传都将成功存活。您的应用甚至可以在其正常生命周期内终止(例如,由于用户可能随后使用的其他应用的内存压力),并且上传将不会被取消。并且,当重新建立网络连接时,这些上传将开始,您的应用将以后台模式启动,以便在完成后通知您。
但是我已经完成了以下内容,并且在重新建立连接时会自动发送上传内容。
import Alamofire
import os.log
import MobileCoreServices
/// The `OSLog` which we'll use for logging
///
/// Note, this class will log via `os_log`, not `print` because it's possible that the
/// app will have terminated (and thus not connected to Xcode debugger) by the time the
/// upload is done and we want to see our log statements.
///
/// By using `os_log`, we can watch what's going on in an app that is running
/// in background on the device through the macOS `Console` app. And I specify the
/// `subsystem` and `category` to simplify the filtering of the `Console` log.
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "BackgroundSession")
/// Background Session Singleton
///
/// This class will facilitate background uploads via `URLSession`.
class BackgroundSession {
/// Singleton for BackgroundSession
static var shared = BackgroundSession()
/// Saved version of `completionHandler` supplied by `handleEventsForBackgroundURLSession`.
var backgroundCompletionHandler: (() -> Void)? {
get { return manager.backgroundCompletionHandler }
set { manager.backgroundCompletionHandler = backgroundCompletionHandler }
}
/// Completion handler that will get called when uploads are done.
var uploadCompletionHandler: ((URLSessionTask, Data?, Error?) -> Void)?
/// Alamofire `SessionManager` for background session
private var manager: SessionManager
/// Dictionary to hold body of the responses. This is keyed by the task identifier.
private var responseData = [Int: Data]()
/// Dictionary to hold the file URL of original request body. This is keyed by the task identifier.
///
/// Note, if the app terminated between when the request was created and when the
/// upload later finished, we will have lost reference to this temp file (and thus
/// the file will not be cleaned up). But the same is true with Alamofire's own temp
/// files. You theoretically could save this to (and restore from) persistent storage
/// if that bothers you.
///
/// This is used only for `uploadJSON` and `uploadURL`. The `uploadMultipart` takes
/// advantage of Alamofire's own temp file process, so we don't have visibility to
/// the temp files it uses.
private var tempFileURLs = [Int: URL]()
private init() {
let configuration = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!)
manager = SessionManager(configuration: configuration)
// handle end of task
manager.delegate.taskDidComplete = { [unowned self] session, task, error in
self.uploadCompletionHandler?(task, self.responseData[task.taskIdentifier], error)
if let fileURL = self.tempFileURLs[task.taskIdentifier] {
try? FileManager.default.removeItem(at: fileURL)
}
self.responseData[task.taskIdentifier] = nil
self.tempFileURLs[task.taskIdentifier] = nil
}
// capture body of response
manager.delegate.dataTaskDidReceiveData = { [unowned self] session, task, data in
if self.responseData[task.taskIdentifier] == nil {
self.responseData[task.taskIdentifier] = data
} else {
self.responseData[task.taskIdentifier]!.append(data)
}
}
}
let iso8601Formatter = ISO8601DateFormatter()
/// Submit multipart/form-data request for upload.
///
/// Note, Alamofire's multipart uploads automatically save the contents to a file,
/// so this routine doesn't do that part.
///
/// Alamofire's implementation begs a few questions:
///
/// - It would appear that Alamofire uses UUID (so how can it clean up the file
/// if the download finishes after the app has been terminated and restarted ...
/// it doesn't save this filename anywhere in persistent storage, AFAIK); and
///
/// - Alamofire uses "temp" directory (so what protection is there if there was
/// pressure on persistent storage resulting in the temp folder being purged
/// before the download was done; couldn't that temp folder get purged before
/// the file is sent?).
///
/// This will generate the mimetype on the basis of the file extension.
///
/// - Parameters:
/// - url: The `URL` to which the request should be sent.
/// - parameters: The parameters of the request.
/// - fileData: The contents of the file being included.
/// - filename: The filename to be supplied to the web service.
/// - name: The name/key to be used to identify this file on the web service.
func uploadMultipart(url: URL, parameters: [String: Any], fileData: Data, filename: String, name: String) {
manager.upload(multipartFormData: { multipart in
for (key, value) in parameters {
if let string = value as? String {
if let data = string.data(using: .utf8) {
multipart.append(data, withName: key)
}
} else if let date = value as? Date {
let string = self.iso8601Formatter.string(from: date)
if let data = string.data(using: .utf8) {
multipart.append(data, withName: key)
}
} else {
let string = "\(value)"
if let data = string.data(using: .utf8) {
multipart.append(data, withName: key)
}
}
multipart.append(fileData, withName: name, fileName: filename, mimeType: self.mimeType(for: URL(fileURLWithPath: filename)))
}
}, to: url, encodingCompletion: { encodingResult in
switch(encodingResult) {
case .failure(let error):
os_log("encodingError: %{public}@", log: log, type: .error, "\(error)")
case .success:
break
}
})
}
/// Determine mime type on the basis of extension of a file.
///
/// This requires MobileCoreServices framework.
///
/// - parameter url: The file `URL` of the local file for which we are going to determine the mime type.
///
/// - returns: Returns the mime type if successful. Returns application/octet-stream if unable to determine mime type.
private func mimeType(for url: URL) -> String {
let pathExtension = url.pathExtension
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue(),
let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
return "application/octet-stream";
}
/// Submit JSON request for upload.
///
/// - Parameters:
/// - url: The `URL` to which the request should be sent.
/// - parameters: The parameters of the request.
func uploadJSON(url: URL, parameters: [String: Any]) {
upload(url: url, parameters: parameters, encoding: JSONEncoding.default)
}
/// Submit `x-www-form-urlencoded` request for upload.
///
/// - Parameters:
/// - url: The `URL` to which the request should be sent.
/// - parameters: The parameters of the request.
func uploadURL(url: URL, parameters: [String: Any]) {
upload(url: url, parameters: parameters, encoding: URLEncoding.default)
}
/// Starts a request for the specified `urlRequest` to upload a file.
///
/// - Parameters:
/// - fileURL: The file `URL` of the file on your local file system to be uploaded.
/// - urlRequest: The `URLRequest` of request to be sent to remote service.
func uploadFile(fileURL: URL, with urlRequest: URLRequest) {
manager.upload(fileURL, with: urlRequest)
}
/// Starts a request for the specified `URL` to upload a file.
///
/// - Parameters:
/// - fileURL: The file `URL` of the file on your local file system to be uploaded.
/// - url: The `URL` to be used when preparing the request to be sent to remote service.
func uploadFile(fileURL: URL, to url: URL) {
manager.upload(fileURL, to: url)
}
/// Submit request for upload.
///
/// - Parameters:
/// - url: The `URL` to which the request should be sent.
/// - parameters: The parameters of the request.
/// - encoding: Generally either `JSONEncoding` or `URLEncoding`.
private func upload(url: URL, parameters: [String: Any], encoding: ParameterEncoding) {
let request = try! URLRequest(url: url, method: .post)
var encodedRequest = try! encoding.encode(request, with: parameters)
let fileURL = BackgroundSession.tempFileURL()
guard let data = encodedRequest.httpBody else {
fatalError("encoding failure")
}
try! data.write(to: fileURL)
encodedRequest.httpBody = nil
let actualRequest = manager.upload(fileURL, with: encodedRequest)
if let task = actualRequest.task {
tempFileURLs[task.taskIdentifier] = fileURL
}
}
/// Create URL for temporary file to hold body of request.
///
/// - Returns: The file `URL` for the temporary file.
private class func tempFileURL() -> URL {
let folder = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(Bundle.main.bundleIdentifier! + "/BackgroundSession")
try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
return folder.appendingPathComponent(UUID().uuidString)
}
}
然后您可以使用multipart/form-data
在后台上传文件,如下所示:
let parameters = ["foo": "bar"]
guard let imageData = UIImagePNGRepresentation(image) else { ... }
BackgroundSession.shared.uploadMultipart(url: url, parameters: parameters, fileData: imageData, filename: "test.png", name: "image")
或者您可以使用JSON在后台上传文件,如下所示:
let parameters = [
"foo": "bar",
"image": imageData.base64EncodedString()
]
BackgroundSession.shared.uploadJSON(url: url, parameters: parameters)
例如,如果您希望在上传完成后通知您的视图控制器,则可以使用uploadCompletionHandler
:
override func viewDidLoad() {
super.viewDidLoad()
BackgroundSession.shared.uploadCompletionHandler = { task, data, error in
if let error = error {
os_log("responseObject: %{public}@", log: log, type: .debug, "\(error)")
return
}
if let data = data {
if let json = try? JSONSerialization.jsonObject(with: data) {
os_log("responseObject: %{public}@", log: log, type: .debug, "\(json)")
} else if let string = String(data: data, encoding: .utf8) {
os_log("responseString: %{public}@", log: log, type: .debug, string)
}
}
}
}
仅记录结果,但您可以随心所欲地做任何事情。