简体   繁体   中英

Swift: 'self.init' isn't called on all paths before returning from initializer error on UIImage extension

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.

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, eg:

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, eg:

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM