使用ReachabilitySwift时无法重新加载表

时间:2017-08-26 15:15:56

标签: ios swift uitableview

我正在尝试使用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方法。

以下是我尝试的内容:

  1. 检查numberOfRowsInSection中的计数,这是正确的。
  2. 尝试使用以下方法在主线程中调用tableview.reloadData()

    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
    
  3. TableView代码:

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return numberOfImages
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //places image onto imageView
    }
    

    任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:0)

可达性的替代方法是使用后台URLSession进行上传。这样,您无需对Reachability执行任何操作。当重新建立连接时,即使您的应用程序未运行,也会自动发送您启动的上传。

后台会话涉及一些限制:

  1. 上传任务必须使用基于文件的上传(不是DataStream)。这意味着一旦您构建了请求,就必须在上传之前将其保存到文件中。 (如果您构建了一个分段上传请求,Alamofire会为您执行此操作。)

  2. 后台会话的整体想法是,即使您的应用已暂停(或终止),它们也会继续运行。因此,您无法使用我们熟悉的完成处理程序模式(因为这些关闭可能在上载请求时被丢弃)。因此,您必须依赖taskDidComplete关闭SessionDelegate来确定请求是否成功完成。

  3. 您必须在app委托中实施handleEventsForBackgroundURLSession,保存完成处理程序。如果上传完成后您的应用程序未运行,操作系统将调用此方法,该方法必须在处理完成时调用。您必须向Alamofire提供此完成处理程序,以便它可以为您执行此操作。

    如果您忽略保存此完成处理程序(因此如果Alamofire无法代表您调用它),您的应用程序将被立即终止。确保保存此完成处理程序,以便您的应用程序可以在上传完成后透明地执行所需操作,然后再次正常暂停。

  4. 警告您:如果用户强制退出您的应用(双击主页按钮并向上滑动应用),则会取消所有待处理的后台上传。下次启动应用程序时,它会通知您任何已取消的任务。

    但是,如果用户刚刚离开您的应用(例如,只需点击主页按钮),任何待处理的上传都将成功存活。您的应用甚至可以在其正常生命周期内终止(例如,由于用户可能随后使用的其他应用的内存压力),并且上传将不会被取消。并且,当重新建立网络连接时,这些上传将开始,您的应用将以后台模式启动,以便在完成后通知您。

  5. 但是我已经完成了以下内容,并且在重新建立连接时会自动发送上传内容。

    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)
                }
            }
        }
    }
    

    仅记录结果,但您可以随心所欲地做任何事情。