Home > Software engineering >  Swift: 'self.init' isn't called on all paths before returning from initializer error
Swift: 'self.init' isn't called on all paths before returning from initializer error

Time:11-30

I'm trying to use an extension of UIImage to convert image download URLs to UIImage, but I'm getting this error: 'self.init' isn't called on all paths before returning from initializer

Here is my whole extension:

(https://i.stack.imgur.com/oZGJu.png)

extension UIImage {
    convenience init?(url: URL?) {
        let session = URLSession(configuration: .default)
        let downloadPicTask = session.dataTask(with: url!) { (data, response, error) in
            if let e = error {
                print("Error downloading picture: \(e)")
            } else {
                if let res = response as? HTTPURLResponse {
                    print("Downloaded picture with response code \(res.statusCode)")
                    if let imageData = data {
                        let image = UIImage(data: imageData)
                    } else {
                        print("Couldn't get image: Image is nil")
                    }
                } else {
                    print("Couldn't get response code for some reason")
                }
            }
        }
        downloadPicTask.resume()
    }
}

I don't know what I'm missing or if I should try a new way to convert URL to UIImage. I tried a different way, but it throws an error saying I need to do it asynchronously.

CodePudding user response:

A standard initializer cannot simply return an image that was fetched asynchronously.

Assuming you are not using Swift concurrency with its async-await, you can write a static function with a completion handler closure which will asynchronously return the image:

extension UIImage {
    enum ImageError: Error {
        case notImage
        case unknownError
    }

    @discardableResult
    static func fetchImage(from url: URL, queue: DispatchQueue = .main, completion: @escaping (Result<UIImage, Error>) -> Void) -> URLSessionDataTask? {
        let session = URLSession.shared
        let task = session.dataTask(with: url) { data, response, error in
            guard
                let data = data,
                error == nil,
                let response = response as? HTTPURLResponse
            else {
                queue.async { completion(.failure(error ?? ImageError.unknownError)) }
                return
            }

            print("Downloaded picture with response code \(response.statusCode)")

            guard let image = UIImage(data: data) else {
                queue.async { completion(.failure(ImageError.notImage)) }
                return
            }

            queue.async { completion(.success(image)) }
        }
        task.resume()

        return task
    }
}

And then call this static function with its completion handler, e.g.:

func updateImageView() {
    let url = ...

    UIImage.fetchImage(from: url) { [weak self] result in
        switch result {
        case .failure(let error): print(error)
        case .success(let image): self?.imageView.image = image
        }
    }
}

However, if you were using Swift concurrency with its async-await, you can write an async initializer:

extension UIImage {
    convenience init?(from url: URL) async throws {
        let (data, _) = try await URLSession.shared.data(from: url)
        self.init(data: data)
    }
}

And you can then use this from an asynchronous context, e.g.:

func updateImageView() async throws {
    let url = ...
    imageView.image = try await UIImage(from: url)
}

CodePudding user response:

You’re missing a self.init call For convenience inits you’d write self.init and follow up with a standard init that’s already defined Also you need to return nil where your init fails

  • Related