Reputation: 94
I'm trying to make an application that uses a web service to get some data from a directory, but I also need to save the data into the device, include images, to do so I'm using Alamofire and AlamofireImage framework for consuming the webservice. I save the generated objects in a database with Realm framework and for images I save the UIImage into a file.
Basically, the ViewController has a tableView which displays de data, but it seems laggy because of the images writing into files.
This is my writing function:
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)
}
}
}
}
I call this function when Alamofire downloads the image
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)")
}
}
})
}
But the tableView looks laggy when scrolled and I was trying to make the writing operation into a diferent thread so it could get written without affecting the application performance by using DispatchQeue
DispatchQueue.global(qos: .background).async {
do {
try data.write(to: filename)
}
catch {
print(error)
}
}
But even so the applications stills laggy.
UPDATE:
I tryed this as Rob suggested:
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)
}
}
}
}
I can't dispatch the Realm writing becase Realm doesn't suport multithreading. It stills scrolling laggy but not as much as the first time.
Upvotes: 1
Views: 5997
Reputation: 3807
So the proper answer as @Rob gave it is
DispatchQueue.global().async {
do {
try data.write(to: filename)
}
catch {
print(error)
}
}
But just as important is to never use a reference to a UITableViewCell from an asynchronous call (again credit @Rob). For example, setting a cell value from the asynchronous parts of your code.
cell.imgProfile.image = image
UITableViewCells are re-used, so you don't know that the original cell is still used at the same index. For a test, scroll your list really fast so your images get loaded, if you see images appear in the wrong cell, its the re-use problem.
So from an asynchronous callback you need to figure out if the cell index for the new image is visible, go get the current cell for that index to set it's image. If the index isn't visible, then store/cache the image until it's index is scrolled into view. At that point it's cell will be created with UITableView.cellForRowAtIndexPath, and you can set the image there.
Upvotes: 2