3

I already have the response data that I received from the server. This response data have some bakers data.

Now I want to calculate the distance of the user and bakery and then store it in the same modal class. I have created a function for it. And as this function need to be used in 4,5 view controllers, my plan is to create as an extension of UIViewController

func getDistanceUserBakery(bakeryData : inout [BakeryRecord], completion : @escaping (Int?) -> () ) {

    for index in 0...(bakeryData.count-1) {
        //1
        let googleApiAdd = "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&"
        //2
        let origin = "origins=\(UserLocation.coordinates.latitude),\(UserLocation.coordinates.longitude)"
        //3
        let destination = "&destinations=\(bakeryData[index].location?.coordinates?[1] ?? 0.0),\(bakeryData[index].location?.coordinates?[0] ?? 0.0)"
        //4
        let googleKey = "&key=\(GOOGLE_KEY)"
        //5
        let url = googleApiAdd + origin + destination + googleKey

        let request = URLRequest(url: URL(string: url)!)

        //6 - this line is showing the error.

        let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
            guard let data = data else {
                completion(nil)
                Toast.show(message: "Unable to calculate distance from user to bakery", controller: self)
                return }
            let stringResponse = String(data: data, encoding: .utf8)!
            let dictData = stringResponse.convertToDictionary()
            do {
                let jsonData = try JSONSerialization.data(withJSONObject: dictData as Any, options: .prettyPrinted)
                let decoder = JSONDecoder()
                let model = try decoder.decode(GoogleDistance.self, from: jsonData)
                bakeryData[index].disanceInMiles = model.rows?[0].elements?[0].distance?.text ?? "NaN"
                completion(index)
            } catch let parsingError {
                print("Error data :", parsingError)
                completion(nil)
            }
        }
        task.resume()
    }

This is how I call this function once I have received the data from my server,

  self.getDistanceUserBakery(bakeryData: &self.bakeryData) { index in
                    if index != nil {
                        DispatchQueue.main.async {
                            // here I am thinking as the bakeryData will hold the new value for distanceInMiles, the collectionView will start showing up that result on reload.
                            self.resultCollection.reloadItems(at: [IndexPath(item: index!, section: 0)])
                        }
                    }
                }

Now the Question:

As I know, when you pass parameters as inout, there values can be changed from inside your function, and those changes reflect in the original value outside the function.

But when I try the code , it says Escaping closure captures 'inout' parameter 'bakeryData'. In my code , //6 is producing the error.

How to fix this error?

mutAnT
  • 464
  • 3
  • 17
  • your answer here https://stackoverflow.com/questions/39569114/swift-3-0-error-escaping-closures-can-only-capture-inout-parameters-explicitly ,votes up if helped – Sanad Barjawi Jan 28 '20 at 06:27
  • I would start by saying that an extension of UIViewController isn't the right approach here. This code should be in your data model or similar. Also, why are you using an `inout` parameter? You need to have a very good reason to use `inout` parameters. – Paulw11 Jan 28 '20 at 06:37
  • @Paulw11 I want to make changes to bakeryData array directly. If I pass it as the function parameter, I can not change its value as pararmeters to a functions are by default constants. So I thought of using `inout` – mutAnT Jan 28 '20 at 06:40
  • Is `BakeryData` a struct? If so then simply make it a class. – Paulw11 Jan 28 '20 at 06:41
  • yes its a struct. I will try that, – mutAnT Jan 28 '20 at 06:42
  • 1
    If it is a struct then your current approach wouldn't have worked anyway - Structs are immutable, so `bakeryData[index].disanceInMiles` should have given you an error. If you make `BakerData` a class then the array contains reference types and you can update the element's properties. – Paulw11 Jan 28 '20 at 06:47
  • @Paulw11 Thanks, I changed it to class and its working now. Bdw, `bakeryData[index].disanceInMiles` this was working too. Maybe because I declared bakeryData as a `var`? – mutAnT Jan 28 '20 at 06:53
  • Please make this comment as an answer so I can accept. Thanks – mutAnT Jan 28 '20 at 06:54
  • well array is a struct ... passing struct with inout ... with escaping ... how can it work ? – Jawad Ali Jan 28 '20 at 07:23
  • @jawadAli this comment is not helpful at all. You are asking my same question back to me. Anyways, I have already got an answer from Paulw11. – mutAnT Jan 28 '20 at 07:36
  • i am trying to understand @Paulw11 answer actually ... – Jawad Ali Jan 28 '20 at 07:39
  • even we use class ... but we are send array as inout parameter ... which is struct ... so i need to understand ... dont want to ask same question again... so through your question i want to develop my understanding related to inout parameter – Jawad Ali Jan 28 '20 at 07:41
  • oh. You should have mentioned his name in the comment so he may get notified. – mutAnT Jan 28 '20 at 07:43
  • 1
    It works because you aren't modifying the array, you are only modifying an element in the array. If you said `someArray[index] = something` you are modifying the array. Saying `someArray[index].someProperty = somethingElse` you aren't modifying the array and as long as the array contains reference objects (class instances) even if you did modify the array, it wouldn't matter - there is only a single instance of the thing in the array at each index – Paulw11 Jan 28 '20 at 07:47
  • Even if you got your answer, your whole function looks weird. You have a for loop inside, but once it hits the first completion, it will jump out of your for-loop, and you will get just one index. Not sure exactly what you were trying to achieve with your index there. You should've collected all the indexes which had ```distances```, and pass and array of ```IndexPath```s, or something similar, and update more cells at once. Otherwise, you will always reload the same cell. – Starsky Jan 28 '20 at 09:46
  • @Starsky I have a collection view loaded already that shows bakers near me. I can't wait for google api to calculate the distance and then show the data to user. Instead, I am showing user the bakers data in collection view and fetching the distance between the user and bakers one by one in background. Once one request is complete, I get the index of it and reload that particular `indexPath` to show the calculated distance. I think this is the best way. – mutAnT Jan 28 '20 at 09:59
  • @Starsky **but once it hits the first completion, it will jump out of your for-loop, and you will get just one index.** This is not right. For loop will continue its execution till the index is`bakersData.count-1` – mutAnT Jan 28 '20 at 10:03
  • 1
    @mutAnT Hmmm, not sure why, but I never tried a for-loop with a completion, and wait for it to complete multiple times. If it works, then it is fine. – Starsky Jan 28 '20 at 10:11

1 Answers1

1

As @Paulw11 suggested in comments,

Is BakeryData a struct? If so then simply make it a class. If you make BakerData a class then the array contains reference types and you can update the element's properties

I changed the struct to class and it did work.

mutAnT
  • 464
  • 3
  • 17