2
\$\begingroup\$

I was recently tasked with architecting a user/profile object in Objective-C and wanted the ability to access a static instance from the class level, similar in style to the way Parse manages their current user (a static variable on a class, not a singleton). With Parse, I can call a class method to return the current user object if a user is logged in, or nil if there is no current user session with [PFUser currentUser].

I architected my class like so:

User.h

@interface User : NSObject

/// Returns the current user if logged in, or nil if logged out
+ (User *)currentUser;

/// Sets the current user, call this on login
+ (void)setCurrentUser:(NSDictionary *)userInfo;

/// Removes the current user, call this on logout
+ (void)removeCurrentUser;

// Getters

/// Returns the user's name if logged in, nil if logged out
@property (nonatomic, readonly, strong) NSString *name;

@end

User.m

@interface User ()

@property (nonatomic, readwrite, strong) NSString *name;
@property (nonatomic, strong) NSString *address;

@end

@implementation User

#pragma mark - Static Variables

static User *currentUser;

#pragma mark - Static Object Setters and Getters

+ (User *)currentUser
{
    return currentUser;
}

+ (void)setCurrentUser:(NSDictionary *)userInfo
{
    currentUser = [[User alloc] initWithDictionary:userInfo];
}

+ (void)removeCurrentUser
{
    currentUser = nil;
}

#pragma init

- (instancetype)initWithDictionary:(NSDictionary *)userInfo
{
    self = [super init];

    if (self) {
        NSString *name = userInfo[@"name"];
        NSString *address = userInfo[@"address"];

        if (!name || !address ) { return nil; }

        _name = name;
        _address = address;
    }

    return self;
}

@end

This works well. I have a single static User instance which can be non-nil if a user is logged in, and nil if the user is logged out. I can access this object from anywhere with [User currentUser] without a singleton.

Now as an additional challenge I'm trying to translate this type of pattern into Swift. Here is my fist pass:

public class User {

    // Private Properties
    private var name: String?
    private var address: String?

    // Private instance of User
    private static var user: User?

    // Class Functions

    public class func currentUser() -> User? {
        return self.user
    }

    public class func setCurrentUser(userInfo: [String : AnyObject]) {
        self.user = User(dictionary: userInfo)
    }

    public class func removeCurrentUser() {
        self.user = nil
    }

    // Instance methods

    public func getName() -> String? {
        return self.name
    }

    // Private

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

        guard let name = dictionary["name"] as? String else { return nil }
        guard let address = dictionary["address"] as? String else { return nil }

        self.name = name
        self.address = address
    }
}

This works but doesn't feel "Swifty" enough.

Questions:

Is there a way to expose a public getter on a private property (sort of like a property redeclaration through a class extension in Objective-C)? Or do I need to write an additional accessor.

Does declaring my private User object as private static ensure that it can only be accessed on the class level and not on the instance level? Is this variable really static like it would be in C or Objective-C?

Can a User setter and getter be added/modified to remove the need for setCurrentUser and removeCurrentUser?

I am open to optimizations in both my Swift and Objective-C code.

\$\endgroup\$
2
\$\begingroup\$

(I am referring to the Swift code only.)

You can declare a property as

public private(set) var name: String?

to make it public read-only, but internally read-write. That makes the additional accessor getName() obsolete.

If the property is only assigned in the init method and never changed later, you can simply make it constant:

public let name: String?

Since the initializer ensures that both name and address are assigned a value, you don't have to declare them as optionals.

The currentUser(), setCurrentUser(), removeCurrentUser() methods can be replaced by a static property

public static var currentUser: User?

if you make the initializer public. static means class final, i.e. it is a property of the type (and not an instance) and cannot be overridden in subclasses.

The two guard statements can be combined into one.

The class then looks like this:

public class User {

    public static var currentUser: User?

    public let name: String
    public let address: String

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

        guard let name = dictionary["name"] as? String,
            let address = dictionary["address"] as? String
        else { return nil }

        self.name = name
        self.address = address
    }
}

The "current user" can now be set, retrieved, and unset like this:

User.currentUser = User(dictionary: ...)
if let user = User.currentUser {
    // ...
}
User.currentUser = nil

If you change the initializer to

public init?(dictionary: [String : Any])

then you can pass a native Swift dictionary without casting, e.g.

User.currentUser = User(dictionary: ["name": "Joe", "address": "San Francisco"])
\$\endgroup\$
0
\$\begingroup\$

In Objective-C, you could define a class property like this,

@interface User : NSObject

@property (class, nonatomic, strong) User *currentUser;     

@end

Maybe that's what you want.

For the static variable, yes, it could be accessed in both class and instance level in its own namespace.

\$\endgroup\$
  • \$\begingroup\$ This question was written before class properties were announced in Objective-C. Either way, I'm looking for a Swift answer. \$\endgroup\$ – JAL Feb 25 '17 at 16:29
  • 1
    \$\begingroup\$ @JAL, Based on the question, you're looking for either. "I am open to optimizations in both my Swift and Objective-C code." It's a valid suggestion, but to your point, I agree. It's a tangential answer because it's a solution that hadn't existed prior. \$\endgroup\$ – Trojan404 Mar 27 '17 at 20:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.