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.
2 Answers 2
(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"])
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.
-
\$\begingroup\$ This question was written before class properties were announced in Objective-C. Either way, I'm looking for a Swift answer. \$\endgroup\$JAL– JAL2017年02月25日 16:29:09 +00:00Commented Feb 25, 2017 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– Trojan4042017年03月27日 20:11:20 +00:00Commented Mar 27, 2017 at 20:11
Explore related questions
See similar questions with these tags.