如何使用Swift在后台线程中保存文件?

时间:2017-02-28 16:57:49

标签: ios swift multithreading

我正在尝试创建一个使用Web服务的应用程序从目录中获取一些数据,但我还需要将数据保存到设备中,包括图像,这样做我正在使用 Alamofire用于使用Web服务的 AlamofireImage 框架。我使用 Realm 框架将生成的对象保存在数据库中,对于图像,我将 UIImage 保存到文件中。

基本上,ViewController有一个显示de数据的tableView,但由于图像写入文件,它看起来很滞后。

这是我的写作功能:

func saveImage(_ image: UIImage) {
    if let data = UIImagePNGRepresentation(image) {
        let name = "images/person_directory_\(id).png"
        let docsDir = getDocumentsDirectory()
        let filename = docsDir.appendingPathComponent(name)
        let fm = FileManager.default

        if !fm.fileExists(atPath: docsDir.appendingPathComponent("images").path) {
            do {
                try fm.createDirectory(at: docsDir.appendingPathComponent("images"), withIntermediateDirectories: true, attributes: nil)
                try data.write(to: filename)
                try! realm?.write {
                    self.imageLocal = name
                }
            }
            catch {
                print(error)
            }
        }
        else {
            do {
                try data.write(to: filename, options: .atomic)
                try! realm?.write {
                    self.imageLocal = name
                }
            }
            catch {
                print(error)
            }

        }
    }
}

我在 Alamofire 下载图片时调用此功能

if person.imageLocal != nil,  let image = person.loadLocalImage() {
        print("Load form disk: \(person.imageLocal)")
        cell.imgProfile.image = image
    }
    else if !(person.image?.isEmpty)! {
        Alamofire.request(person.image!).responseImage(completionHandler: { (response) in
            if response.result.isSuccess {
                if let image = response.result.value {
                    person.saveImage(image)
                    cell.imgProfile.image = image
                    print("Downloaded: \(person.imageLocal)")
                }
            }
        })
    }

但是滚动时tableView看起来很迟钝,我试图将写入操作变成一个不同的线程,因此可以通过使用 DispatchQeue

来编写它而不会影响应用程序性能
DispatchQueue.global(qos: .background).async {
                    do {
                        try data.write(to: filename)
                    }
                    catch {
                        print(error)
                    }
                }

但即便如此,应用程序仍然很滞后。

更新:

我试着像Rob建议的那样:

func saveImage(_ image: UIImage) {
    if let data = UIImagePNGRepresentation(image) {
        let name = "images/person_directory_\(id).png"
        do {
            try realm?.write {
                self.imageLocal = name
            }
        }
        catch {
            print("Realm error")
        }
        DispatchQueue.global().async {
            let docsDir = self.getDocumentsDirectory()
            let filename = docsDir.appendingPathComponent(name)
            let fm = FileManager.default

            if !fm.fileExists(atPath: docsDir.appendingPathComponent("images").path) {
                do {
                    try fm.createDirectory(at: docsDir.appendingPathComponent("images"), withIntermediateDirectories: true, attributes: nil)
                }
                catch {
                    print(error)
                }
            }
            do {
                try data.write(to: filename)
            }
            catch {
                print(error)
            }
        }
    }
}

我无法发送Realm写作因为Realm不支持多线程。 它仍然滚动迟滞,但没有第一次那么多。

1 个答案:

答案 0 :(得分:1)

所以@Rob给出的正确答案是

   DispatchQueue.global().async {
      do {
         try data.write(to: filename)
      }
      catch {
         print(error)
      }
    }

但同样重要的是永远不要从异步调用中使用对UITableViewCell的引用(再次使用@Rob)。例如,从代码的异步部分设置单元格值。

                cell.imgProfile.image = image

UITableViewCells被重复使用,因此您不知道原始单元格仍在同一索引中使用。对于测试,请快速滚动列表以便加载图像,如果您看到图像出现在错误的单元格中,则会出现重复使用问题。

因此,从异步回调中,您需要确定新图像的单元格索引是否可见,请获取该索引的当前单元格以设置其图像。如果索引不可见,则存储/缓存图像,直到其索引滚动到视图中。此时,它的单元格将使用UITableView.cellForRowAtIndexPath创建,您可以在那里设置图像。