0

I am new in Swift. I am trying to parse some JSON data from web service and want a singleton class of user.But I got stuck to create the singleton. Here is my code:

import Foundation
class User {
   private var success: String
   private var userId: String
   private var name: String
   private var gender: String
   private var email: String
   private var userObject = [User]()

    class  var sharedInstane:User {
        struct Singleton {
            static var onceToken: dispatch_once_t = 0
            static var instance:User? = nil
        }
        dispatch_once(&Singleton.onceToken){
            Singleton.instance = User()
        }
        return Singleton.instance!
    }

    private init(success: String, userId: String, name: String, gender:  String, email: String)
    {
        self.success = success
        self.userId = userId
        self.name = name
        self.gender = gender
        self.email = email
    }
    convenience init(dictionary: [String:AnyObject]) {
    let success = dictionary["success"] as? String
    let userId = dictionary["userId"] as? String
    let name = dictionary["name"] as? String
    let gender = dictionary["gender"] as? String
    let email = dictionary["email"] as? String
     self.init(success: success!, userId: userId!, name: name!,  gender: gender!, email: email!, )
    }

    func callWebserviceToLoadUserInfo (url:String, param:[String:AnyObject],completeHandler:(Bool?,String) -> ())

    {
     let connection = ServerConnection()
     connection.getJSONDataForWebService(url, params: param) { (response, error) in
     // code goes here
            var responseDict = response as! [String : AnyObject]
            responseDict = responseDict["responseDict"] as! [String : AnyObject]
               if responseDict["success"] as! String == "1" {
                   for dict in responseDict {
                    let user = User(dictionary: (dict as! [String:AnyObject]))
                       self.userObject.append(user)
                }
             print("user : \(self.userObject[0].name)")
            }else{
                // error goes here
            }
        }
  }

}

Can any one please help me how should I do this code?

Kumar KL
  • 15,315
  • 9
  • 38
  • 60
Rupshikha
  • 213
  • 1
  • 3
  • 16
  • Think about making this two classes. Create a `User` model class that handles all your data, like you already did but without the singleton thing, and then create a `UserManager` or something that is a singleton and is able to return the one user. – Pascal May 13 '16 at 09:22
  • can you please give a little example? – Rupshikha May 13 '16 at 09:47
  • @Pascal Why use two classes when you can get it done with one? – catalandres May 13 '16 at 12:35
  • You certainly don't need a singleton. If you want to have a single instance representing the _current_ user (the one that's signed in using this device), you may have a property in your "Application Model" (preferred design), or possibly make it a property of your AppDelegate (poor design). – CouchDeveloper May 13 '16 at 13:13
  • @catalandres For a neat separation between model and functionality. `User` could be something he at one point wants to store to and read from a database, then it would get complicated if you can't init a user. – Pascal May 13 '16 at 13:38
  • @Pascal: Nah. The whole idea of a singleton pattern is that *you cannot create more than one instance*. You create it just once, exactly at the moment you access the static property `sharedInstance`. An overloaded `init` makes no sense, because it's a one-hit thing. Any customization would have to happen after instantiation. – catalandres May 13 '16 at 14:04
  • @catalandres Sure, I agree, that's why I think it makes no sense to use a singleton `User` class in this case. In his method he loops through several `responseDict` and instantiates a user for every dict, adding them to an array. We can only assume what he's trying to do, I'm guessing he's trying to determine who logged in to the app and use this `User` anywhere in the app; I just think it's better to create a (singleton) manager to handle that user. – Pascal May 13 '16 at 14:15
  • @Pascal: So we are both right in that we are trying to answer the OP's question. My approach was to focus on how to create a singleton, while your approach seems to go in a more wholistic direction, trying to understand how the whole blob of code makes sense. The main problem is that this is not a **minimally viable example**, but maximalist code, which requires a lot of work to parse and understand. – catalandres May 13 '16 at 14:20
  • Possible duplicate of [Using a dispatch\_once singleton model in Swift](http://stackoverflow.com/questions/24024549/using-a-dispatch-once-singleton-model-in-swift) – Pascal May 13 '16 at 14:23

4 Answers4

3

The singleton in the single line sample code.

class TheOneAndOnlyKraken {
    static let sharedInstance = TheOneAndOnlyKraken()
    private init() {} //This prevents others from using the default '()' initializer for this class.
}

For more details.

Maheshwar Ligade
  • 6,709
  • 4
  • 42
  • 59
  • Thank you for your answer. Is it possible to send parameters through init()? – Rupshikha May 13 '16 at 06:08
  • 1
    You can initialize any properties inside init, if necessary. Or you could have an initialization method that passes some properties. Here is the thing: the singleton is created only when you call `sharedInstance`, which is called as a property, not a method. Whatever parameters you add to the call to the initializer would be hard coded. – catalandres May 13 '16 at 06:23
2

Using Krakendev's single-line singleton code, cited by Maheshwar, and turning your convenience init into an instance function to be called with User.sharedInstance.initialize(dictionary):

import Foundation
class User {

    // Here you declare all your properties
    // "private var" and all that jazz

    static let sharedInstance = User()

    private init() {
        // If you have something to do at the initialization stage
        // you can add it here, as long as it does not involve
        // arbitrary values that you would pass as parameters.
    }

    func initialize(dictionary: [String:AnyObject]) {
        // Transfer the values of the dictionary to each `self.property`.
        // Be careful while using `as?` as you may have to deal with    
        // optionals. No need to call `self.init` at the end, because 
        // this is now a regular `func`.
    }

    // Add the rest of your stuff here

}

One note about how you were working inside of that convenience initializer: if you do property = SomeClass.someMethod().someProperty as? SomeType, then property will be of type SomeType?, or Optional(SomeType). According to The Swift Programming Language,

The conditional form, as?, returns an optional value of the type you are trying to downcast to.

catalandres
  • 1,149
  • 8
  • 20
1

While User was not instantiated at least one time sharedInstance will return nil. After the first successful instantiation of the User, sharedInstance starts return it and that's became impossible to instantiate another one User as singleton pattern requires it. Consider this:

class User {

    private static var sharedUser: User?

    class var sharedInstance: User? {

        return sharedUser
    }

    private init(success: String, userId: String, name: String, gender:  String, email: String)
    {
        //User initialization code here
        User.sharedUser = self
    }

    convenience init?(dictionary: [String:AnyObject]) {

        guard User.sharedUser == nil else {
            return nil
        }

        //dictionary parsing code is here
        self.init(success: success!, userId: userId!, name: name!,  gender: gender!, email: email!)
    }
}

Client's code:

User.sharedUser
//return nil

let dict: [String:AnyObject] = ["success": "success", "userId":"userId", "name":"name", "gender":"gender","email":"email"]
User(dictionary: dict)
//creates User

User.sharedUser
//returns just created user

User(dictionary: dict)
//return nil
Igor B.
  • 2,219
  • 13
  • 17
0

You should think about making this two classes, so that User is your model class and then create a manager to handle all the users (which seems to be your goal).

So in User remove the sharedInstane part and create a second singleton class, e.g. called UserManager, with the standard way to create a singleton in Swift. Then you can keep the way you're creating your user and in the end just assign it to the singleton:

class UserManager {
    static let sharedInstance = UserManager()

    var users = [User]()
}

// in your code:
...
for dict in responseDict {
    let user = User(dictionary: (dict as! [String:AnyObject]))
    UserManager.sharedInstance.users.append(user)
}
...
Pascal
  • 16,846
  • 4
  • 60
  • 69