I have a data structure that I have created that caches items using a combination of a Dictionary and a Queue. The idea is that items are added to the "Cache" and once it reaches its limit, the oldest item in the cache is removed:
struct Cache<A: Hashable, B> {
fileprivate var theCache = [A:B]()
fileprivate var keyQueue = Queue<A>()
fileprivate var maxItems: Int!
init(maxItems: Int) {
self.maxItems = maxItems
}
subscript(key : A?) -> B? {
get {
if key != nil {
return theCache[key!]
}
return nil
}
set(newValue) {
// If value being added to cache, check that the max no. of items isn't exceeded.
// If it is then remove the first item of the cache queue and add value to queue.
if key != nil {
if (theCache.count + 1 > maxItems) {
removeFirstItem()
}
addValue(key!, value: newValue)
}
}
}
/**
Tells us how many items there are in the cache
- returns: The count of the cache
*/
var count: Int {
return theCache.count
}
mutating func clear() {
theCache.removeAll()
for _ in 0..<keyQueue.count {
_ = keyQueue.dequeue()
}
}
fileprivate mutating func addValue(_ key: A, value: B?) {
theCache[key] = value // Memory leak 2
keyQueue.enqueue(key)
}
fileprivate mutating func removeFirstItem() {
let firstItemKey = keyQueue.dequeue()
if firstItemKey != nil {
theCache.removeValue(forKey: firstItemKey!)
}
}
}
I'm using this to cache UIImages in a UICollectionView, and have a strong reference to this cache from my model class.
Inside my cellForItemAt method, I have the following code to check for cached images. If there is a cached image then it adds that to the UICollectionCell otherwise it downloads it, adds it to the cache, then adds it to the UICollectionCell:
if let cellImage = model.imageCache[urlString] {
cell.imageView.image = cellImage
}
else {
model.downloadImage(
urlString,
completion: { [weak self] (error, image) -> () in
if let strongSelf = self {
if error == nil {
// Store the image in to our cache
strongSelf.model.imageCache[urlString] = image
// Update the cell with our image
if let cellToUpdate = collectionView.cellForItem(at: indexPath) as? MyCollectionCell {
cellToUpdate.imageView.image = image
}
}
else {
print("Error downloading image: \(error!.localizedDescription)")
}
}
}
)
}
However, according to Xcode 8 this whole process is causing a memory leak for the UIImages. Can anyone help me out with where the memory leak is occurring and how I can prevent it?
EDIT
Here's my downloadImage function. I've also added "Memory leak 1" and "Memory leak 2" as comments next to my code to show the backtrace of the memory leak xcode shows me:
Alamofire.request(urlString, method: .get)
.authenticate(usingCredential: credentials!)
.validate()
.downloadProgress { bytesRead, totalBytesRead, totalBytesExpectedToBeRead in
DispatchQueue.main.async {
progress?(Float(totalBytesRead)/Float(totalBytesExpectedToBeRead))
}
}
.responseData { response in
// Convert the downloaded data in to a UIImage object
var image: UIImage?
if response.result.error == nil {
image = UIImage(data: response.data!) // Memory leak 1
}
completion(response.result.error, image)
}
Related
I've received an array object from the server, and then I want to download images with one property on that object. then I want to update UI with array objects and images (view model). I'm downloading images on a background thread, but I'm getting images with delay and the object doesn't fills at all, whats I'm doing wrong?
func presentCoinse(_ list: Home.Models.CoinseListResponse) {
var coins = [Home.Models.coinsViewModel]()
for item in list {
getImage(symbol: item.symbol) { image in
let i = Home.Models.coinsViewModel(image: image,
symbol: item.symbol,
name: item.name,
buyPrice: item.buyPrice,
sellPrice: item.sellPrice,
change24Hource: item.symbol)
coins.append(i)
}
}
viewController?.displayCoinsList(viewModel: coins)
}
private func getImage(symbol: String, complation: #escaping(_ image: Data?) -> Void) {
queue.async {
if let url = URL(string: "\(CDN_URL)\(symbol).png") {
let data = try? Data(contentsOf: url)
DispatchQueue.main.async {
complation(data)
}
}
}
}
The getImage method contains async operations, in this situation the closure will be called after the return of the method.
And, the method displayCoinsList(viewModel:) is called before every async getImage.
You can use the DispatchGroup class.
func presentCoinse(_ list: Home.Models.CoinseListResponse) {
var coins = [Home.Models.coinsViewModel]()
let dispatchGroup = DispatchGroup() // add a dispatch group
for item in list {
dispatchGroup.enter() // increment group counter before async call
getImage(symbol: item.symbol) { image in
defer {
dispatchGroup.leave() // decrease group counter in every condition with 'defer'
}
let i = Home.Models.coinsViewModel(image: image,
symbol: item.symbol,
name: item.name,
buyPrice: item.buyPrice,
sellPrice: item.sellPrice,
change24Hource: item.symbol)
coins.append(i)
}
}
dispatchGroup.notify(queue: .main) {
// wait all async calls are completed
viewController?.displayCoinsList(viewModel: coins)
}
}
private func getImage(symbol: String, complation: #escaping(_ image: Data?) -> Void) {
queue.async {
if let url = URL(string: "\(CDN_URL)\(symbol).png") {
let data = try? Data(contentsOf: url)
complation(data)
} else {
complation(nil)
}
}
}
NOTE: In getImage Method is required to call complation In every condition. If you forgot to call it the DispatchGroup can’t receive a notify and you’ll be blocked.
Pay attention that you're setting coins to empty list, and then you call displayCoinstList with that empty list. the list is appened and updated asyncronically.
You should trigger the view controller to reload the coins list when it's ready. I'd make the coins list prior going to fetecg the images, and then upon receiving the images - the UIImageView will render itself.
func presentCoinse(_ list: Home.Models.CoinseListResponse) {
// async part srarts here
var coins = [Home.Models.coinsViewModel]()
for item in list {
getImage(symbol: item.symbol) { image in
let i = Home.Models.coinsViewModel(image: image,
symbol: item.symbol,
name: item.name,
buyPrice: item.buyPrice,
sellPrice: item.sellPrice,
change24Hource: item.symbol)
coins.append(i)
}
}
// async part ends here
viewController?.displayCoinsList(viewModel: coins)
}
What you did is equivalent to:
func presentCoinse(_ list: Home.Models.CoinseListResponse) {
var coins = [Home.Models.coinsViewModel]()
viewController?.displayCoinsList(viewModel: coins) // coins in empty at this point)
for item in list {
getImage(symbol: item.symbol) { image in
let i = Home.Models.coinsViewModel(image: image,
symbol: item.symbol,
name: item.name,
buyPrice: item.buyPrice,
sellPrice: item.sellPrice,
change24Hource: item.symbol)
coins.append(i)
}
}
}
I've written module based on RxSwift with Viewcontroller and ViewModel. ViewModel contains gesture's observers and images observables. Everything works well, except situation when application didBecameActive directly to mentioned module. Subscriptions of gestures don't work and imageView become blank.
They are set inside subscription to observable based on BehaviorSubjects, inside view:
func subscribePhotos(observerable: Observable<[(Int, UIImage?)]>) {
disposeBag = DisposeBag()
observerable.subscribeOnNext { [weak self] array in
array.forEach { identifier, image in
if let pictureView = self?.subviews.first(where: { view -> Bool in
guard let view = view as? PictureView else {
return false
}
return view.identifier == identifier
}) as? PictureView {
pictureView.set(image)
}
}
}.disposed(by: disposeBag)
}
In viewModel I set Observable:
var imagesObservable: Observable<[(Int, UIImage?)]> {
do {
let collection = try photosSubject.value()
if let photosObservables = collectionCreator?.getPhotosDetailsObservables(identifiers: collection.photoIdentifiers) {
let photosObservable = Observable.combineLatest(photosObservables)
return Observable.combineLatest(photosSubject, photosObservable,
resultSelector: { collection, currentArray -> [(Int, UIImage?)] in
var newArray = [(Int, UIImage?)]()
currentArray.forEach { stringIdentifier, image in
if let picture = grid.pictures.first(where: { $0. stringIdentifier == stringIdentifier }) {
newArray.append((picture.identifier, image))
}
}
return newArray
})
}
} catch { }
return Observable<[(Int, UIImage?)]>.never()
}
}
photosSubject is initialized in viewModel's init
photosSubject = BehaviorSubject<PictureCollection>(value: collection)
photosObservale
func createImageObservableForAsset(asset: PHAsset, size: CGSize) -> Observable<UIImage?> {
return Observable.create { obs in
PHImageManager.default().requestImage(for: asset,
targetSize: size,
contentMode: .aspectFit,
options: nil,
resultHandler: { image, _ in
obs.onNext(image)
})
return Disposables.create()
}
}
And in ViewController I connect them by calling method of view:
myView.pictureView.subscribePhotos(observerable: viewModel.imagesObservable)
After didBecameActive pictureView's property image of type UIImage isn't nil, but they disappear. I could listen notification didBecameActive and invoke onNext on observer, but I’m not sure is it correct way to figure out problem. Any idea what's reason of that?
Finally, I solved out this issue. Reason wasn't connected with Rx. Method drawing pictures draw(_:CGRect) was called after didBecomeActive and cleared myView. I changed method's body and now everything works well :)
I'm running into a weird issue where my tableView is reloading too early after retrieving JSON data. The strange thing is sometimes it reloads after getting all the required data to fill the tableView and other times it reloads before it can acquire the data. I'm not entirely sure why it's doing this although I do notice sometimes the data is returned as nil. Here is what I use to retrieve the data:
var genreDataArray: [GenreData] = []
var posterStringArray: [String] = []
var posterImageArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
GenreData.updateAllData(urlExtension:"list", completionHandler: { results in
guard let results = results else {
print("There was an error retrieving genre data")
return
}
self.genreDataArray = results
for movie in self.genreDataArray {
if let movieGenreID = movie.id
{
GenrePosters.updateGenrePoster(genreID: movieGenreID, urlExtension: "movies", completionHandler: {posters in
guard let posters = posters else {
print("There was an error retrieving poster data")
return
}
for poster in posters {
if let newPoster = poster {
if self.posterStringArray.contains(newPoster){
continue
} else {
self.posterStringArray.append(newPoster)
self.networkManager.downloadImage(imageExtension: "\(newPoster)",
{ (imageData)
in
if let image = UIImage(data: imageData as Data){
self.posterImageArray.append(image)
}
})
break// Use to exit out of array after appending the corresponding poster string
}
} else {
print("There was a problem retrieving poster images")//This gets called sometimes if the poster returns nil
continue
}
}
})
}
}
DispatchQueue.main.async {
self.genresTableView.reloadData()//This is reloading too early before the data can be retrieved
}
})
}
The data is being retrieved asynchronously, and thus your table view can sometimes reload without all the data. What you can do is have the table view reload at the end of the asynchronous data retrieval, or you can reload the cells individually as they come in instead of the whole table using
let indexPath = IndexPath(item: rowNumber, section: 0)
tableView.reloadRows(at: [indexPath], with: .top)
TRY THIS-:
var genreDataArray: [GenreData] = []
var posterStringArray: [String] = []
var posterImageArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
genredataArray.removeAll()
posterStringArray.removeAll()
posterImageArray.removeAll()
NOW HERE CALL YOUR CLASS FUNCTION AS ABOVE
}
I guess in that case, you should use
dispatch_async(dispatch_get_main_queue(),{
for data in json as! [Dictionary<String,AnyObject>]
{
//take data from json. . .
}
//reload your table -> tableView.reloadData()
})
You should get the main queue of the thread.
Requirement - I have a requirement in which I am receiving a JSON dictionary from which I am retrieving an array of images and content text. Then I have to display all the images with corresponding contents in a collection view.
Update - Above all I need to calculate the cell size based on image size scaled to the a constant width for which I fell that(may not be correct) I need all images to be downloaded completely then reload collection view
Problem - But the problem is that when I download the images in background thread and populate in separate arrays.Then the image cannot be added in the same order as they were in the JSON Dictionary since I am downloading them in a concurrent queue.
My Solution - So I thought of downloading them by putting everything in a serial queue which has made my retrieving data very slow. What can be an efficient alternative for this?
Code -
let serialQueue = dispatch_queue_create("my serial queue", nil)
dispatch_async(serialQueue, {
print("This is first Method")
for var i=0;i<self.resultArr.count;i++//resultArr is my array of data's in the jsonDic
{
sleep(2)
print(self.resultArr[i].valueForKey("profile_pic")! as! String)
if self.resultArr[i].valueForKey("profile_pic")! as! String != "Null" && self.resultArr[i].valueForKey("profile_pic")! as! String != "null" && self.resultArr[i].valueForKey("profile_pic")! as! String != "NULL" && self.resultArr[i].valueForKey("profile_pic")! as! String != ""
{
let imageUrl = UrlClass.imageUrlWithoutExtension + String(self.resultArr[i].valueForKey("profile_pic")!)
print(imageUrl)
let url = NSURL(string: imageUrl)
let imageData = NSData(contentsOfURL: url!)
self.contentlabelArr.insertObject(String(self.resultArr[i].valueForKey("content")!), atIndex: i)
if imageData != nil && imageData?.length > 0
{
print("this is \(i) image")
print(UIImage(data: imageData!))
self.imageArr.insertObject(UIImage(data: imageData!)!, atIndex: i)
}
else
{
print("\(i) image has nill")
self.imageArr.insertObject(UIImage(named: "logo.png")!, atIndex: i)
}
}
else
{
print("\(i) image has nill")
self.contentlabelArr.insertObject(String(self.resultArr[i].valueForKey("content")!), atIndex: i)
self.imageArr.insertObject(UIImage(named: "logo.png")!, atIndex: i)
}
print("\(i) times 5 is \(i * 5)")
if self.imageArr.count==self.resultArr.count
{
print(self.resultArr.count)
print(self.imageArr.count)
dispatch_async(dispatch_get_main_queue(),
{
print(self.resultArr.count)
print(self.imageArr.count)
print(self.imageArr)
print(self.contentlabelArr)
self.collectionView?.reloadData()
})
}
A more efficient way would be to create a data model object which will represent you image link and the optional UIImage. Something like this:
class NetworkImage {
let imageURL: String!
let image: UIImage?
}
Now when you receive your JSON with image links array, you can create your data model array, which will respect the order:
let dataModel: [NetworkImage]
So when you will retrieve your images asynchronously, you can update your dataModel with your image, so no order will be affected.
The idea can be evolved suiting your needs.
You should never use sync operations for this kind of jobs.
you definitely can keep the order if you use a concurrent queue. i think your code as it stands pretty much doesnt use the queue correctly at all (and why is there a sleep(2)?) your concurrent queue should be inside the forloop so it can fire off the different blocks at the same time, and they will use the correct index of the for loop that was assigned to them to place the resulting image in the correct array location
let sema = dispatch_semaphore_create(2); //depending how many downloads you want to go at once
for i in 0..<self.resultArr.count {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//download images here, order of execution will not be guaranteed, but after they are finished, they will always put the images in the array at 'i' so it doesnt matter
dispatch_semaphore_signal(sema);
})
}
You may play around with this sample solution, utilising dispatch groups:
//: Playground - noun: a place where people can play
import UIKit
import Dispatch
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class Record {
init(text: String, imageURL: String) {
self.text = text
self.imageURL = imageURL
self.image = nil
}
var text: String
var imageURL: String
var image: String?
}
extension Record: CustomStringConvertible {
var description: String {
return "text: \(text), imageURL: \(imageURL), image: \(image)"
}
}
// Fetch text and image url, but no image.
func fetchRecords(completion: ([Record]?, ErrorType?) -> ()) {
let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayInNanoSeconds, dispatch_get_global_queue(0, 0)) {
let result: [Record] = [
Record(text: "Aaa", imageURL: "path/image1"),
Record(text: "Bbb", imageURL: "path/image2"),
Record(text: "Ccc", imageURL: "path/image3")
]
completion(result, nil)
}
}
// fetch an image
func fetchImage(url: String, completion: (String?, ErrorType?) -> () ) {
let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayInNanoSeconds, dispatch_get_global_queue(0, 0)) {
let image = url
completion(image, nil)
}
}
// Put everything together:
// 1) Fetch an array of records, omitting the image
// 2) When this is finished, in parallel, for each record
// fetch each image.
// 3) When all is finished, call the completion handler containing
// the records including the images
func fetchRecordsWithImages(completion: ([Record]?, ErrorType?) -> () ) {
fetchRecords { (result, error) in
if let records = result {
let grp = dispatch_group_create()
records.forEach { record in
dispatch_group_enter(grp)
fetchImage(record.imageURL) { (image, error) in
if let image = image {
record.image = image
}
dispatch_group_leave(grp)
}
}
dispatch_group_notify(grp, dispatch_get_global_queue(0, 0)) {
completion(records, nil)
}
}
}
}
fetchRecordsWithImages() { (records, error) in
if let records = records {
print("Records: \(records)")
}
}
Console:
Records: [text: Aaa, imageURL: path/image1, image: Optional("path/image1"), text: Bbb, imageURL: path/image2, image: Optional("path/image2"), text: Ccc, imageURL: path/image3, image: Optional("path/image3")]
I have an array in my class that I'm trying to fill within a closure. However, when I try to access/print the array contents, it seems to be empty outside of the closure. How do I store the data for use outside of the closure?
for index in 0..<6 {
let picNumber = index + 1
if let pica = currentuser.objectForKey("pic\(picNumber)") as? PFFile {
pica.getDataInBackgroundWithBlock({ (data:NSData!, error:NSError!) -> Void in
if error == nil {
self.pic1 = UIImage(data: data)
var imageS = scaleImage(self.pic1!, and: 200)
self.imagesForSection0.append(imageS)
}
})
println(self.imagesForSection0)
}
}
It is not empty outside the Closure
The method getDataInBackgroundWithBlock is async,it means that it will return first.So,you see nothing in print function.
Document
Asynchronously gets the data from cache if available or fetches its contents from the network.
Edit
for index in 0..<6 {
let picNumber = index + 1
if let pica = currentuser.objectForKey("pic\(picNumber)") as? PFFile {
pica.getDataInBackgroundWithBlock({ (data:NSData!, error:NSError!) -> Void in
if error == nil {
self.pic1 = UIImage(data: data)
var imageS = scaleImage(self.pic1!, and: 200)
self.imagesForSection0.append(imageS)
}
println(self.imagesForSection0)
//Then call reload data
})
}
}