-1

I need to initialise the result of JSONDecoder in a var object defined outside the API Call.

apiService.GETAPI(url: apiStr, completion: {(success, result) in
            if(success) {
                let apiResponse = result.value as! NSDictionary
                let data = apiResponse.value(forKey: "data") as! NSDictionary
                    do {
                        let profileData = try JSONSerialization.data(withJSONObject: data.value(forKey: "profile"), options: .prettyPrinted)
                        print(profileData)
                        let profile = try JSONDecoder().decode(Profile.self, from: profileData)

                        print(profile.name)
                    }
                    catch {
                        print("json error: \(error.localizedDescription)")
                    }
            }
            completion(success)
        })

But I am unable to do so. This is my Profile Codable struct

struct Profile : Codable {
    var id : Int
    var name : String
    var member_id : Int
    var category_id : Int
    var membership_id : Int
    var information : String
    var city : String
    var love_count : Int
    var vendor_price : String
    var locality_name : String
    var phone : [String]
    var address : [Address]?
    var status : Int?
    var managed_by_wmg : Int?
}

How to do it. I need it to be var since I need to perform operation and access it later in the other code.

Swati
  • 1,417
  • 1
  • 15
  • 35
  • Why you don't just `var profile = try JSONDecoder().decode(Profile.self, from: profileData)`. Or perhaps you mean something else? – dvp.petrov Apr 18 '18 at 08:06
  • if I define the var inside the completion block, how will I access it outside. – Swati Apr 18 '18 at 10:02
  • I believe you can benefit from reading [this](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html). So when you define your `var` property outside the closure, there is absolutely no problem using it inside of the closure. Also you need to know that if you use any of the `self` properties in the closure, you need to make `[weak self]` reference to it. You can read more about this here: https://blog.haloneuro.com/swift-memory-leak-gotcha-with-weak-self-67293d5bc060 – dvp.petrov Apr 19 '18 at 10:45
  • The problem is that Profile is a Codable struct without any init method. If I have to define it outside, i will have to initialise it like var profile = Profile() and add a initialisation method in codable struct as well. – Swati Apr 20 '18 at 04:13
  • Oh, now I understood your problem. Gonna write answer. – dvp.petrov Apr 20 '18 at 07:22

2 Answers2

0

As we have already discussed in the comments of your question, you need to declare variable of Profile type outside the closure.

So now the problem become "How to change Profile struct, so I can declare variable of it?"

For this you have several options. I will list them and comment on which to choose and why.

Option 1 Add default values to all the variables of Profile struct as so: var id : Int = 0, so you can declare it later with var profile: Profile?. With this solutions you need to have some knowledge about the objects you are expecting and their default values in a way they make sense to your business logic. Example for such default values are "" for string or 0 integer. This might seem good but yet their is better solution.

Option 2 Make ALL the variables of Profile optional as so: var id : Int?. This might sounds strange at first, but is the optimal solution for working with server data. So this method has several benefits:

  1. It will never crash, no matter what the server sends you as a data
  2. All the variables have default values equaling nil
  3. You do not need to think about your business logic and what default value suits your needs

However, with this method there is one drawback: some properties that with your logic could never be nil will have nil initial value. So you need to add validations for unwrapping the nullable properties.

Option 3 Add explicit unwrapping to the variable type as so var id : Int!. Note this also adds nil as initial value to your properties, but tells the compiler that in every time you are gonna use them, they will not be nil. I would personally not recommend this method, because if the variable is not received from the server, or the parsing fails, or something else unexpected happens, your app will crash with found nil while unwrapping optional value. For reference you might have noticed all the IBOutlets are defined this way.

Having said all that, I would recommend you going with Option 2, which best suits server communication, but the choice is yours!

Happy coding.

dvp.petrov
  • 1,110
  • 13
  • 20
0

I have found two solutions to my question. Thanks to @dvp.petrov.

Solution 1 var profile : Profile! comes handy but it becomes difficult if this is not an optional variable and have to be used at many places. One will have to put lot of checks and unwrap it every time. This is easy to use when one has to use it at very less places

Solution 2 : Create an init function giving default values to all the variable and then you can create var profile : Profile(). This way your object will not be nil and will be able to use it at places.

struct Profile : Codable {
    var id : Int
    var name : String
    var member_id : Int

   init() {
    self.id = 0
    self.name = ""
    self.member_id = 0

}
Swati
  • 1,417
  • 1
  • 15
  • 35