3

I have an array of PFFiles downloaded from Parse, and I am trying to convert them in to an array of NSData (imageDataArray) in order to save them to Core Data. The only problem I seem to be having now is that the elements of imageDataArray are being added in the wrong order, which means the wrong image is found when I search through Core Data.

I found this question (Stack Overflow Question) which seems to explain the reason for the problem as being that the block is completing the tasks at different times, therefore the order of the array is based on whichever is finished first. They then suggest using Grand Central Dispatch, but it is all in Obj C and, as a newbie to coding, I am struggling to convert it to Swift for my project.

Could you please explain to me how I would use GCD (or any other method) to create the imageDataArray in the same order as the original PFFile array?

    override func viewDidLoad() {
    super.viewDidLoad()


    let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

    let context: NSManagedObjectContext = appDel.managedObjectContext


   let dealsQuery = PFQuery(className: ("Deals"))
    dealsQuery.orderByAscending("TrailId")

    dealsQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
        if let objects = objects {

            self.trailID.removeAll(keepCapacity: true)
            self.trailStep.removeAll(keepCapacity: true)
            self.dealNumber.removeAll(keepCapacity: true)
            self.imageFile.removeAll(keepCapacity: true)

            for object in objects {

                self.trailID.append(object["TrailID"] as! Int)
                self.trailStep.append(object["TrailStep"] as! Int)
                self.dealNumber.append(object["dealNumber"] as! Int)
                self.imageFile.append(object["dealImage"] as! PFFile!)

            }

        }


            var counter = 0

            for file in self.imageFile {

            let dealImage = file

            dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in

                if error == nil {

                    weak var aBlockSelf = self

                    let image = UIImage(data: imageData!)
                    aBlockSelf!.imageDataArray.append(imageData!)
                    self.imagesArray.append(image!)

                    if counter == self.trailStep.count - 1{

                        print(self.trailID.count)
                        print(self.trailStep.count)
                        print(self.dealNumber.count)
                        print(self.imageDataArray.count)

                        print(self.trailStep[0])
                        print(self.dealNumber[0])
                        let image = UIImage(data: self.imageDataArray[0])
                        self.imageView.image = image

                    } else {counter++}
                }

            })

Sorry for posting all of my code here. I guess the issue is at the bottom, but as a beginner I thought I might have made a mistake somewhere else so thought I would be best posting it all.

Thanks a lot for your help.


Update 1

I have tried adding a prefilled array (imageArray) and trying to put the data in to that, but it still comes out random when searching for trailStep and dealNumber. What am I missing? Thanks

 var i = 0

            var imageArray = [NSData!](count: self.trailStep.count, repeatedValue: nil)

            for file in self.imageFile {

            let dealImage = file

            dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in

                if error == nil {

                    //weak var aBlockSelf = self

                    let image = UIImage(data: imageData!)
                    imageArray[i] = imageData!

                    if i == self.trailStep.count - 1{

                        print(self.trailID.count)
                        print(self.trailStep.count)
                        print(self.dealNumber.count)
                        print(self.imageDataArray.count)

                        print(self.trailStep[3])
                        print(self.dealNumber[3])
                        let image = UIImage(data: imageArray[3])
                        self.imageView.image = image

                    } else {i++}
                }

            })

Update 2

I have been researching and having a play around with GCD and serial dispatches, and here is where I am at the moment. I think I am taking a far too simplistic view of GCD but can't quite get my head around how it works and how it can get my block to add to imageDataArray in the right order.

var counter = 0

        var imageArray = [NSData!](count: self.trailStep.count, repeatedValue: nil)

        weak var aBlockSelf = self

        var serialQueue = dispatch_queue_create("serial", nil)

        for file in self.imageFile {

            let dealImage = file

            var imageDataArray = [NSData!](count: self.imageFile.count, repeatedValue: nil)

            dispatch_async(serialQueue, { () -> Void in

            dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in



                    if error == nil {

                        let imageIndex = self.imageFile.indexOf(file)
                        let image = UIImage(data: imageData!)
                        imageDataArray.insert(imageData!, atIndex: imageIndex!)

                        if counter == self.trailStep.count - 1{

                            print(self.trailID.count)
                            print(self.trailStep.count)
                            print(self.dealNumber.count)
                            print(imageDataArray.count)

                            print(self.trailStep[0])
                            print(self.dealNumber[0])
                            let image = UIImage(data: imageDataArray[4])
                            self.imageView.image = image

                        } else {counter++}

                        return
                    }

                })

This code is returning an error of "unexpectedly found nil while unwrapping an Optional value" on the line " let image = UIImage(data: imageDataArray[4])". This is the same error that I was getting before I tried to implement the GCD...

Community
  • 1
  • 1
Jon Buckley
  • 117
  • 1
  • 12

2 Answers2

3

This should be fairly simple. Although GCD dispatch group would be ideal choice, you can solutionize this by keeping a counter of for loop.

In your completion block, deduce the index of the object you are currently inside the for loop using the parent array. In your final image array, add object at the index captured by this index.

EDIT: Post OP question - here is a quick sneak peek of how I would like it to be:

weak var aBlockSelf = self

for file in self.imageFile {

    let dealImage = file

    dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in

        if error == nil {
            let imageIndex = self.imageFile.indexOf(file)
            let image = UIImage(data: imageData!)
            aBlockSelf.imageDataArray.insert(imageData!, atIndex: imageIndex)
            aBlockSelf.imagesArray.insert(image!, atIndex: imageIndex)

As a side not, do not declare self weak reference in the for loop. You should declare it outside.

Abhinav
  • 37,684
  • 43
  • 191
  • 309
  • Thanks a lot for your help. How exactly would I deduce the index of the parent array? I thought I was doing that with the i variable in the updated code...or does it not work like that? – Jon Buckley Oct 31 '15 at 08:47
  • Hi @Abhinav. I have tried your code but it seems to be giving me an "Array Index Out Of Range" error on the line "aBlockSelf!.imageDataArray.insert(imageData!, atIndex: imageIndex!)". I seemed to be getting this error a lot when I was playing around with trying to sort out this issue. What do you think the problem is? I appreciate your side note by the way. I think I need a quick research of what a self weak reference really is. Thanks for the help. – Jon Buckley Oct 31 '15 at 19:42
  • I made sure the imageDataArray was filled and the same error moved to the "aBlockSelf.imagesArray.insert(image!, atIndex: imageIndex)" line. I thought this array wasn't really necessary so I deleted it, but now I am getting an "unexpectedly found nil while unwrapping an Optional value" error on the line "let image = UIImage(data: imageDataArray[3])" line. Is this because this line is now being actioned before the elements are being added to the array? – Jon Buckley Oct 31 '15 at 19:55
  • Probably, as I had suggested you should use this array only after last iteration of for loop. Try that out. Frankly, I would go with GCD with dependent tasks in this case. – Abhinav Nov 01 '15 at 02:37
  • I think I am using that array after the last iteration of the for loop, in the `if i == self.trailStep.count - 1` part. It is in the code in Update 1 in my question. Is that what you mean? I will do a bit of research in to GCD with dependent tasks and will give it a try. I'm thinking a might need some advice though :/ Thanks again for your time. – Jon Buckley Nov 01 '15 at 04:51
  • Hi again @Abhinav. I spent yesterday looking over GCD but didn't have much luck. I played around with `dispatch_sync` within the block but it resulted in the page loading continuously. Could you please give me a bit more guidance on how I can use GCD to get over this issue? I seemed to be able to get over a lot of problems previously with the course I am taking and some research, but this one seems a bit trickier. Thanks again. I really appreciate your help. – Jon Buckley Nov 02 '15 at 03:18
  • Sorry, I have now updated my original question with my attempt at GCD but I don't think I understand it enough. I figure I need an async serial queue? What do you think? – Jon Buckley Nov 02 '15 at 07:36
  • I would advise you to go through this [Apple Documentation](https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW25). Also, this objective c [thread](http://stackoverflow.com/questions/11909629/waiting-until-two-async-blocks-are-executed-before-starting-another-block) is worth going through! – Abhinav Nov 02 '15 at 10:31
  • Thanks. I will take a look and let you know how I get on. – Jon Buckley Nov 02 '15 at 11:15
  • Sure @JonBuckley. Good luck! – Abhinav Nov 02 '15 at 11:18
  • So after a massive deal of research and trying to work out how to use GCD, I started my app fresh, and your original answer worked. Still no idea what the problem was but thanks for your help. – Jon Buckley Nov 16 '15 at 10:34
0

You don't need to use GCD for this; you just need an array (or map, if you want to associate each image with its filename) to stick each image in when it's retrieved. In other words, if you have files "f1", "f2", "f3", and "f4", in that order, create an array to hold the retrieved images, and insert them at whatever index corresponds to their filename's index in the list of filenames.

NRitH
  • 13,441
  • 4
  • 41
  • 44
  • Hi, thanks a lot for your comment. I have updated the original question with the change I have made based on your comments, but it is still coming out in a random order. Have I got something wrong? – Jon Buckley Oct 30 '15 at 05:53