My main goal with this refactor was to change instance methods that should have been class methods in the first place into class methods. My secondary goal was to add singletons to these classes because they are Model classes and I feel that there should only be one instance at any given time.
HALAddressBook.h:
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import <AddressBook/ABAddressBook.h>
#import <AddressBook/ABPerson.h>
@interface HALAddressBook : NSObject
#pragma mark - Properties
@property NSArray *contacts;
#pragma mark - Instance Methods
- (BOOL)isAccessGranted;
#pragma mark - Singleton Method
+ (HALAddressBook *)sharedHALAddressBook;
@end
HALAddressBook.m:
#import "HALAddressBook.h"
@implementation HALAddressBook
#pragma mark - Singleton Method
+ (HALAddressBook *)sharedHALAddressBook
{
static HALAddressBook *sharedHALAddressBook = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedHALAddressBook = [[self alloc]init];
});
return sharedHALAddressBook;
}
- (BOOL)isAccessGranted
{
ABAddressBookRef m_addressbook = ABAddressBookCreateWithOptions(NULL, NULL);
__block BOOL accessGranted = NO;
if (ABAddressBookRequestAccessWithCompletion != NULL) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
// Write your code here...
// Fetch data from SQLite DB
}
});
ABAddressBookRequestAccessWithCompletion(m_addressbook, ^(bool granted, CFErrorRef error)
{
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
if (accessGranted) {
// Access has been granted
self.contacts = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(m_addressbook));
return YES;
} else {
// Access has not been granted
return NO;
}
}
@end
HALContact.h:
#import "HALAddressBook.h"
@interface HALContact : NSObject
#pragma mark - Properties
@property NSArray *phoneNumbers;
@property NSString *mainPhoneNumber;
@property NSString *firstName;
@property ABRecordRef contactRef;
#pragma mark - Instance Methods
- (BOOL)hasMultiplePhoneNumbers;
@end
HALContact.m:
#import "HALContact.h"
@interface HALContact ()
@end
@implementation HALContact
- (BOOL)hasMultiplePhoneNumbers
{
if (self.phoneNumbers.count > 1) {
return YES;
} else {
return NO;
}
}
@end
HALUserDefaults.h:
@interface HALUserDefaults : NSObject
#pragma mark - Class Methods
+ (NSArray *)retrieveHalfImageMessages;
+ (NSArray *)retrieveFullImageMessages;
+ (void)storeHalfImageMessages:(id)halfImageMessages;
+ (void)storeFullImageMessages:(id)fullImageMessages;
+ (void)storeUsername:(NSString *)username;
@end
HALUserDefaults.m:
#import "HALUserDefaults.h"
@interface HALUserDefaults ()
@end
@implementation HALUserDefaults
#pragma mark - Singleton Method
+ (HALUserDefaults *)sharedUserDefaults
{
static HALUserDefaults *sharedUserDefaults = nil;
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedUserDefaults = [[self alloc]init];
});
return sharedUserDefaults;
}
+ (NSArray *)retrieveHalfImageMessages
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [standardDefaults objectForKey:@"halfImageMessages"];
NSArray *retrievedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return retrievedArray;
}
+ (NSArray *)retrieveFullImageMessages
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [standardDefaults objectForKey:@"fullImageMessages"];
NSArray *retreivedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return retreivedArray;
}
+ (void)storeHalfImageMessages:(id)halfImageMessages
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:halfImageMessages] forKey:@"halfImageMessages"];
}
+ (void)storeFullImageMessages:(id)fullImageMessages
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:fullImageMessages] forKey:@"fullImageMessages"];
}
+ (void)storeUsername:(NSString *)username
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setObject:username forKey:@"username"];
}
@end
HALParseConnection.h:
#import <Parse/Parse.h>
@interface HALParseConnection : NSObject
#pragma mark - Class Methods
+ (void)performHalfImageQuery;
+ (void)performFullImageQuery;
+ (void)signupNewUserWithUsername:(NSString *)username password:(NSString *)password email:(NSString *)email;
+ (void)loginUserWithUsername:(NSString *)username password:(NSString *)password;
+ (void)performFriendsRelationForCurrentUserQuery;
+ (void)isUsernameAvailable:(NSString *)lowercaseUsername;
@end
HALParseConnection.m:
#import "HALParseConnection.h"
#import "HALUserDefaults.h"
@interface HALParseConnection ()
@end
@implementation HALParseConnection
#pragma mark - Query Methods
+ (void)performHalfImageQuery
{
// Setup and execute the query
PFQuery *query = [PFQuery queryWithClassName:@"Messages"];
[query whereKey:@"recipientIds" equalTo:[[PFUser currentUser]objectId]];
[query whereKey:@"halfOrFull" equalTo:@"half"];
[query whereKey:@"didRespond" notEqualTo:[[PFUser currentUser]objectId]];
[query orderByDescending:@"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(@"There was an error: %@", error);
} else {
// Store the returned objects and post notification
[HALUserDefaults storeHalfImageMessages:objects];
[[NSNotificationCenter defaultCenter] postNotificationName:@"queryHasFinished"
object:self
userInfo:nil];
}
}];
}
+ (void)performFullImageQuery
{
// Setup and execute the query
PFQuery *query2 = [PFQuery queryWithClassName:@"Messages"];
[query2 whereKey:@"senderId" equalTo:[[PFUser currentUser]objectId]];
[query2 whereKey:@"halfOrFull" equalTo:@"full"];
PFQuery *query3 = [PFQuery queryWithClassName:@"Messages"];
[query3 whereKey:@"recipientIds" equalTo:[[PFUser currentUser]objectId]];
[query3 whereKey:@"halfOrFull" equalTo:@"full"];
PFQuery *query2and3 = [PFQuery orQueryWithSubqueries:[NSArray arrayWithObjects:query2,query3,nil]];
[query2and3 orderByDescending:@"createdAt"];
[query2and3 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(@"There was an error: %@", error);
} else {
// Store the returned objects and post notification
[HALUserDefaults storeFullImageMessages:objects];
[[NSNotificationCenter defaultCenter] postNotificationName:@"query2and3HasFinished"
object:self
userInfo:nil];
}
}];
}
+ (void)performFriendsRelationForCurrentUserQuery
{
PFRelation *friendsRelation = [[PFUser currentUser]objectForKey:@"friendsRelation"];
PFQuery *query = [friendsRelation query];
[query orderByAscending:@"username"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error) {
[[NSNotificationCenter defaultCenter]postNotificationName:@"performFriendsRelationForCurrentUserQueryFailed" object:self userInfo:@{@"error" : error}];
} else {
[[NSNotificationCenter defaultCenter]postNotificationName:@"performFriendsRelationForCurrentUserQuerySucceeded" object:self userInfo:@{@"succeeded" : objects}];
}
}];
}
#pragma mark - Signup Methods
+ (void)signupNewUserWithUsername:(NSString *)username password:(NSString *)password email:(NSString *)email
{
// Create new parse user
PFUser *newUser = [PFUser user];
newUser.username = username;
newUser.password = password;
newUser.email = email;
// Signup new parse user
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
NSLog(@"There was an error when trying to signup the new user: %@", error);
// Post notification for unsuccessful signup
[[NSNotificationCenter defaultCenter] postNotificationName:@"unsuccessfulUserSignup"
object:self
userInfo:nil];
} else {
// Persist user's username
[HALUserDefaults storeUsername:username];
// Post notification for signup completion
[[NSNotificationCenter defaultCenter] postNotificationName:@"successfulUserSignup"
object:self
userInfo:nil];
}
}];
}
+ (void)isUsernameAvailable:(NSString *)lowercaseUsername
{
PFQuery *query = [PFQuery queryWithClassName:@"_User"];
[query whereKey:@"lowercaseUsername" equalTo:lowercaseUsername];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(@"There was an error when querying Parse for the lowercaseUsername key");
} else if (objects.count == 0) {
[[NSNotificationCenter defaultCenter]postNotificationName:@"usernameIsAvailable" object:self];
} else if (objects.count == 1) {
[[NSNotificationCenter defaultCenter]postNotificationName:@"usernameIsNotAvailable" object:self];
}
}];
}
#pragma mark - Login Methods
+ (void)loginUserWithUsername:(NSString *)username password:(NSString *)password
{
[PFUser logInWithUsernameInBackground:username password:password block:^(PFUser *user, NSError *error) {
if (error) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"unsuccessfulUserLogin"
object:self
userInfo:nil];
}
if (user) {
// Persist username
[HALUserDefaults storeUsername:user.username];
// Post notification for signup completion
[[NSNotificationCenter defaultCenter] postNotificationName:@"successfulUserLogin"
object:self
userInfo:nil];
}
}];
}
@end
Some of the key things that I am looking for feedback on:
Do I have too many class methods? Do I have class methods that should/could be instance methods?
One of the things I'm having trouble with is deciding when a method should be a class method or an instance method. I understand that usually a class method is for constructing an instance or a singleton, and that an instance method is for performing operations on a particular instance, but I am having trouble differentiating in my model classes for some reason.
Am I correct in thinking that Model Classes should usually employ the singleton pattern?
You will notice that I am using a singleton in
HALUserDefaults
andHALAddressBook
.Is it correct that I am using a static pointer and setting it to nil at the beginning of the method? Because I don't really understand why that is necessary.
Is it correct that I am returning
HALUserDefaults
andHALAddressBook
in the singleton's method signature? Because I have noticed some developers like to useid
instead.Is it pointless to add a singleton method to a class that only has class methods?
I am conflicted on this because even if a class only has class methods, you can still create an instance of the class. So in the case that a class has all class methods, is it pointless to create a singleton, or is it still smart to create a singleton?
Last but not least, anything in general that you see that could be improved upon.