iOS: Proper way of initializing an object? - ios

Modeled after Parse's PFQuery class, I'm building my own EMQuery class, for my own project (not a subclass of PFQuery). My question is, if I want to perform a similar call to a class method in the manner that Parse does it (PFQuery *query = [PFQuery queryWith...]) would this be the correct approach?
+ (instancetype)queryWithType:(EMObjectType)objectType {
EMQuery *query = [[self alloc] init];
return [query initWithQueryType:objectType];
}
- (id)initWithQueryType:(EMObjectType)objectType {
self = [super init];
if (self) {
}
return self;
}

No - as you are calling init of the superclass twice.
Your initWithQueryType should replace the call to init
+ (instancetype)queryWithType:(EMObjectType)objectType {
EMQuery *query = [self alloc];
return [query initWithQueryType:objectType];
}
The exception is if the init in your class does something. In that case the two inits init and initWithQueryType: should be set up that one calls the other and the one called is the only one that calls super init This one is the designated initialiser
The main explanation of all initialization is the section on Object Initialization Apple document

Don't call two init methods; call one, once. Like this:
+ (instancetype)queryWithType:(EMObjectType)objectType {
EMQuery *query = [[self alloc] initWithQueryType:objectType];
return query;
}

Related

How properly assign singleton to a variable in iOS?

I have a project written in Objective-C. Inside it I use singleton.
Its declaration is:
+ (id)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
I init it inside each class where I use it by this code:
singApp = [XXXsingApplication sharedInstance];
When I run Xcode Analyzer I get following warning:
Instance variable used while 'self' is not set to the result of
'[(super or self) init...]'
Why I get this warning?
I suppose I have some general misunderstanding of matter, because AFAIK it has only single instance and must not be init again to avoid creating several instances?
EDIT:
My init looks like:
-(id)init
{
self = [super init];
// init read-only values
_appSwVersion = APP_SOFT_VERSION;
_appSwDateTime = APP_SOFT_DATETIME;
_websDns = WEBS_DNS;
_testWebsUrl = TEST_WEBS_DNS_URL;
// init properties dictionary
_propListDict = [[NSMutableDictionary alloc] init];
#try {
if (self) {
// load settings
[self loadSettingsFromFile];
// init custom Bundle
[self setCustomBundle];
}
}
#catch (NSException *exception) {
NSLog(#"EXCEPTION\nName-> %#\nDescription-> %#", [exception name], [exception description]);
}
return self;
}
Exceptions are there to catch programming errors. You don't catch and handle exceptions in Objective-C. When an exception happens, you let it crash. This is Objective-C, not Java or C++. You don't handle exceptions, you fix the code.
You should check whether self == nil immediately after calling [super init]. Think about what you are doing: You are assigning to an instance variable which will lead to a crash if self == nil. Sure, there will be cases where people are 100% sure that self cannot be nil. But five lines later, you check whether self == nil. That combination is a clear indication of a bug. If you check, you must check before you assign to an instance variable. If you assign to an instance variable, any check is too late.
Your code should look like this.
-(id)init {
self = [super init];
#try {
if (self) {
// init read-only values
_appSwVersion = APP_SOFT_VERSION;
_appSwDateTime = APP_SOFT_DATETIME;
_websDns = WEBS_DNS;
_testWebsUrl = TEST_WEBS_DNS_URL;
// init properties dictionary
_propListDict = [[NSMutableDictionary alloc] init];
// load settings
[self loadSettingsFromFile];
// init custom Bundle
[self setCustomBundle];
}
} #catch (NSException *exception) {
NSLog(#"EXCEPTION\nName-> %#\nDescription-> %#", [exception name], [exception description]);
}
return self;
}

Why my code did not get into [super init] function?

There are three class Question,Choiceand Blank,and Question is the super class of Choice and Blank.
Then, I write some methods as follows:
- (instancetype)initWithQuestionType:(NSString *)questionType
{
NSLog(#"**class:%#",[self class]);
if([self isMemberOfClass:[Question class]])
{
self = nil;
if([questionType isEqualToString:#"choice"])
{
NSLog(#"--class:%#",[self class]);
self = [[Choice alloc] initWithQuestionType:questionType];
NSLog(#"++class:%#",[self class]);
}
else
{
self = [[Blank alloc] initWithQuestionType:questionType];
}
return self;
}
return [super init];
}
- (instancetype)init
{
NSLog(#"Init!");
return [self initWithQuestionType:#"unKnow"];
}
and then:
Question *question = [[Question alloc] initWithQuestionType:#"choice"];
the output is:
2015-10-16 20:58:50.278 initSample[3687:161396] **class:Question
2015-10-16 20:58:50.279 initSample[3687:161396] --class:(null)
2015-10-16 20:58:50.279 initSample[3687:161396] **class:Choice
2015-10-16 20:58:50.280 initSample[3687:161396] ++class:Choice
and I can't understand why [super init] did not be executed?
Your [super init] method is being called:
[Question initWithQuestionType] is called.
The first if is true, so it's entered.
The question type is "choice" so [Choice initWithQuestionType:] is called.
As Choice does not override -initWithQuestionType:, this will call [Question initWithQuestionType:] again.
This time the if is false, so it's not entered, and [super init] is being called.
This is being shown in your log messages (add an another log call before the [super init] method, to prove this).
However it's a very confusing and difficult to maintain factory method, and it's much easier to use a class factory method, as below. That way your init methods will be much more straight-forward and easier to maintain.
+ (Question *)questionWithType:(NSString *)type
{
if ([type isEqualToString:#"choice"])
return [[Choice alloc] init];
else
return [[Blank alloc] init];
}
Also consider using an enum to represent the type, rather than a string (quicker/more efficient).

Parse Class Run Through For Loop

My app uses Parse.com for most of its services. The main screen has a PFTableViewController, showing all the objects of a certain class. I would like to add all of these as NSUserActivity, so I know I need to run it through a for loop, but for the life of me, can't think of how to run the class through the loop to add each item from the class into a NSUserActivity.
What I have so far before setting up the table.
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// The className to query on
self.parseClassName = #"Prayers";
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = YES;
// Whether the built-in pagination is enabled
self.paginationEnabled = YES;
// The number of objects to show per page
self.objectsPerPage = 20;
}
return self;
}
- (PFQuery *)queryForTable {
NSLog(#"QUERY");
PFQuery *query = [PFQuery queryWithClassName:#"Prayers"];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
if (self.objects.count == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
[query orderByDescending:#"createdAt"];
return query;
}
There seems to be no real API for this... Please refer to this link for a possible workaround:
https://parse.com/questions/all-keys-or-method-or-query-to-determine-common-class-structure-of-all-objects-in-a-class-collection

Singleton with parameter iOS

I need to implement a singleton class that takes in a parameter. The same object will be passed as a parameter every single time so the resultant singleton object will always be the same.
I am doing something like the code below. Does this look ok? Is there a better way of achieving what I want to achieve?
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
if (!sharedInstance) {
#synchronized(self) {
sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
}
}
return sharedInstance;
}
- (id)initWithAccount:(UserAccount *)userAccount {
self = [super init];
if (self) {
_userAccount = userAccount;
}
return self;
}
- (id)init {
NSAssert(false,
#"You cannot init this class directly. It needs UserAccountDataSource as a paramter");
return nil;
}
+ (id)alloc {
#synchronized(self) {
NSAssert(sharedInstance == nil, #"Attempted to allocated a second instance of the singleton");
sharedInstance = [super alloc];
return sharedInstance;
}
return nil;
}
There are a number of problem in this design:
As recommended by Apple, should dispatch_once instead of #synchronized(self) for singleton:
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[MyClass alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
Refer to this question for more detail: Why does Apple recommend to use dispatch_once for implementing the singleton pattern under ARC?
Bad API design to put singleton in alloc.
As indicated by the name of the method alloc, it means that some memory will be allocated. However, in your case, it is not. This attempt to overwrite the alloc will cause confusion to other programmers in your team.
Bad idea to use NSAssert in your -init.
If you want to disable a method, disable it by putting this in your header file:
- (id)init __attribute__((unavailable));
In this case, you will get a compile error instead of crashing the app at run time.
Refer to this post for more detail: Approach to overriding a Core Data property: isDeleted
Moreover, you can even add unavailable message:
- (id)init __attribute__((unavailable("You cannot init this class directly. It needs UserAccountDataSource as a parameter")));
Sometime input parameters is ignored with no warning.
In your following code, how would the programmer who is calling this function know that the input parameter userAccount is sometimes ignored if an instance of the class is already created by someone else?
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
if (!sharedInstance) {
#synchronized(self) {
sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
}
}
return sharedInstance;
}
In short, don't think it is a good idea to create singleton with parameter. Use conventional singleton design is much cleaner.
objA = [Object sharedInstanceWithAccount:A];
objB = [Object sharedInstanceWithAccount:B];
B is ignored.
userAccount in objB is A.
if userAccount B in objB, you will change sharedInstanceWithAccount.
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
static NSMutableDictionary *instanceByAccount = [[NSMutableDictionary alloc] init];
id instance = instanceByAccount[userAccount];
if (!instance) {
#synchronized(self) {
instance = [[[self class] alloc] initWithAccount:userAccount];
instanceByAccount[userAccount] = instance;
}
}
return instance;
}

Ignore fetching if PFQuery is already running

how can I ignore new fetching if one is already running. Here is one example of my code:
So if i call [self getParticipants] how to make sure to ignore if already running. The only solution I've got is to create BOOL property "inMiddleOfFetching" but I dont want to create anther BOOL property just for this. Is there a better solution?
- (void)getParticipants {
PFQuery *participantsQuery = [self.participantsRelation query];
[participantsQuery includeKey:#"client"];
[participantsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects)
self.participants = objects;
}];
}
This is not simpler but it is better, you can use bolts to create a single task for the query, this way if it is called multiple times it will only run once but all calls will return the value at the same time.
Something like this:
#property BFTask* task;
- (BFTask *)getParticipants {
if (!_task) {
PFQuery *participantsQuery = [self.participantsRelation query];
[participantsQuery includeKey:#"client"];
_task = [participantsQuery findObjectsInBackground];
}
return _task;
}
Then to get the result:
[[self getParticipants] continueWithBlock:^id(BFTask *task) {
if(!task.error){
self.participants = task.result;
}
_task = nil; //if you want to run the query again in the future
return nil;
}];

Resources