Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attempted to read an unowned reference but object was already deallocated Swift 5

Tags:

xcode

ios

swift

I have a problem with a deallocation of a variable: cache

This is from the tutorial Reusable Image Cache in Swift

enter image description here

Error:

Fatal error: Attempted to read an unowned reference but object 0x280208080 was already deallocated Fatal error: Attempted to read an unowned reference but object 0x280208080 was already deallocated

Code:

final class ImageLoader {

private let cache = ImageCache()

func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never> {
    if let image = cache[url] {
        return Just(image).eraseToAnyPublisher()
    }
    return URLSession.shared.dataTaskPublisher(for: url)
        .map { UIImage(data: $0.data) }
        .catch { error in return Just(nil) }
        .handleEvents(receiveOutput: {[unowned self] image in
            guard let image = image else { return }
            self.cache[url] = image
        })
        .subscribe(on: DispatchQueue.global(qos: .background))
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}
}
like image 794
Hunter Avatar asked Feb 15 '20 08:02

Hunter


2 Answers

This error has a pretty simple explanation:

by the time URLSession is finishing its work, the instance of ImageLoader does not exist, because nobody keeps a reference to it. It may happen when you just create that instance in function scope variable. (Maybe in some function like viewDidLoad). This crash is useful, as it says that loader is using in the wrong way. In case of usage of weak self or capturing the whole instance, the crash will not happen, but you will have a lot of ImageLoaders with its own caches with one image there. Thus there would be no caching in its meaning.

To solve that, after the creation of ImageLoader instance, you should keep the reference to it in a class/struct variable which is consuming it and pass it to another consumer, who needs the same cache. (a dependency injection technic is a good approach for that). Thus one cache with some amount of items will exist and works.

Or the simplest way is to make a shared instance of ImageLoader and use it only, thus it also guaranties one instance of it with one filled cache.

like image 67
Dren Avatar answered Oct 10 '22 15:10

Dren


replace unowned to weak

.handleEvents(receiveOutput: {[weak self] image in
            guard let image = image else { return }
            self.cache[url] = image
        })
like image 23
Nupur Sharma Avatar answered Oct 10 '22 13:10

Nupur Sharma