-2

I have the following code to download some images, copy them into a [UIImage] array, and then I am supposed to create views from them:

class ViewControllerGallery: UIViewController {

    @IBOutlet weak var  m_loader:UIActivityIndicatorView?

    var m_num_images:Int = 0
    var m_total_images:Int = 0

    var m_str_json:String = ""

    var m_images = [UIImage]()

    var m_views = [UIImageView]()




    override func viewDidLoad() {
        super.viewDidLoad()

        // BEGIN-CODE--4
        self.m_loader?.startAnimating()

        let jsonArray = jsonToArray(jsonString: m_str_json) //implementation of this function was omited for my post


        if let _ = jsonArray {
            m_total_images = (jsonArray?.count)!
        }

        for url in jsonArray! {
            OpenImage(str_url: url)

        }

        if m_total_images != m_num_images {
            print("the function finished without loading all images!")
        } else {
            self.m_loader?.stopAnimating()
        }



    }


    func CreateViews()
    {

        print("createViews was called :) ")

    }




    func OpenImage(str_url:String) {

        let url:URL = URL(string: str_url)!

        getDataFromUrl(url: url){ data, response, error in
            guard let data = data, error == nil else {return}
            DispatchQueue.main.async {
                let image = UIImage(data: data)
                print(image!)


                self.m_images.append(image!)
                print(self.m_images.count)
                self.m_num_images += 1
            }
        }

        //2.3.3 Crear vistas cuando se han cargado todas las imagenes
        print(self.m_total_images == self.m_num_images)
        if self.m_total_images == self.m_num_images {
            print("se igualan las imagenes")
            self.performSelector(onMainThread: #selector(CreateViews), with: nil, waitUntilDone: false)
        }


    }



    //Funciones auxiilares



    func getDataFromUrl(url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            completion(data, response, error)
            }.resume()
    }

}

I expect the code to run from view did load and when it gets to the line when it calls the function OpenImage it should execute the whole code contained inside OpenImage(). If I run it, the console shows (thanks to the print() statements) that it jumps back and forward strangely:

false
false
false
the function finished without loading all images!
<UIImage: 0x60c0000afc00>, {1024, 725}
1
<UIImage: 0x60c0000afcc0>, {960, 720}
2
<UIImage: 0x60c0000afc60>, {640, 801}
3

I would expect that it first downloads all the images and appends them into the m_images array. Then it should execute the performSelector method and be done.

For some reason, as you see, it first jumps to the print statement where it compares the number of loaded images with the total number of images, it does that three times, then it goes out of the for statement and prints "the function finished without loading all images" and then it goes into the for loop again and actually loads the images! Why? How can I make it work in the right order?

By the way, I know I am supposed to use camelCase, but I received the class already with snake_case.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
DavidVV
  • 225
  • 1
  • 4
  • 12
  • 1
    `getDataFromUrl(` works asynchronously. The code in the closure is executed later than the *2.3.3* code. Put the *2.3.3* code in the closure, too. And yes, use camelCase! – vadian Nov 09 '17 at 10:16

2 Answers2

1

You are calling OpenImages, which makes an async call, in a loop. For the loop to wait properly for each iteration to return, you can use dispatch groups as expalined perfectly in this post.

Your code should look like this:

override func viewDidLoad() {
    super.viewDidLoad()
    // Some code...

    let myGroup = DispatchGroup()
    for url in jsonArray! {
        myGroup.enter()
        let url:URL = URL(string: str_url)!
        // You can cut your OpenImages in two
        // here we keep the part that has the async call
        getDataFromUrl(url: url){ data, response, error in
            guard let data = data, error == nil else {return}
            DispatchQueue.main.async {
                let image = UIImage(data: data)
                print(image!)
                self.m_images.append(image!)
                print(self.m_images.count)
                self.m_num_images += 1
                myGroup.leave()
            }
        }
    }

    myGroup.notify(queue: .main) {
        // All the async call have finished
        // Here you can finish what you started in OpenImages()
        // and the rest of your code: "if m_total_images != m_num_images"
    }

}
standousset
  • 1,092
  • 1
  • 10
  • 25
0

That's because you were calling an async API(function)

getDataFromUrl(url: url)

Async API normally are those network functions which need much more time to do their tasks. So your code won't stop at this line. Otherwise, your UI would be blocked by this line which provides poor user experience. Instead of waiting "getDataFromUrl" finish its job and then execute next line of code. Calling async API would let you execute your next line immediately. When "getDataFromUrl" finally finish its task, below callback block is then executed

guard let data = data, error == nil else {return}
            DispatchQueue.main.async {
                let image = UIImage(data: data)
                print(image!)


                self.m_images.append(image!)
                print(self.m_images.count)
                self.m_num_images += 1
            }
Wu Yuan Chun
  • 1,116
  • 11
  • 11