ARC assigning instance variable init or awakeFromNib - objective-c

I am trying to initialise a variable in my init method, but it seems to get deallocated when the call chain reaches awakeFromNib. I've read several threads and far too many seem to share the same confusion - something that should be really simple and straightforward... :(.
- (id)init {
self = [super initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
if (self) {
self.customObject = [CustomObject currentInstance]; //OK
}
}
- (void)awakeFromNib {
[self.customObject someMethod]; //here self.customObject is nil?
//self.customObject = [CustomObject currentInstance]; // Why should I do this here?
}
Nothing fancy about the declarations, in my .m file there's. I don't want to assign the property in awakeFromNib without understanding the reason behind.
#property (strong) CustomObject *customObject;
CustomObject instantiation .m file
static CustomObject customObject;
#implementation CustomObject {
+ (instancetype)initMockCustomObject {
customObject = (CustomObject*)[OCMockObject mockForClass:[CustomObject class]];
}
+ (instancetype)currentInstance {
if ( customObject == nil) { [NSException raise...]; }
return customObject;
}
}
initMockCustomObject has already been called in AppDelegate.
Edit: added the actual super call to initWithNibName method in init.
Edit2: added the singleton instance

thank you for your input. I've created a sandbox project with the absolute minimum, and I found that I had an object in one of my parent NIBs, that was of the same type as MyViewController, causing this problem. Closing this question as it's too app specific. The fundamental question of initialising variables in init is OK.

Related

Prevent class from being subclassed in Objective-c

How do I prevent a particular class from being subclassed?
I am not aware of such functionality (say final keyword for example) in the language. However Apple says it has done so for all classes in AddressBookUI.framework (in iOS)
For educational purposes, how can I achieve the same functionality, or how would they have done such thing?
From iOS7 Release Notes(Requires login) :
Here's one way: override allocWithZone: from within your "final" class (substituting MyFinalClassName for your actual class name) like this:
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (self != [MyFinalClassName class]) {
NSAssert(nil, #"Subclassing MyFinalClassName not allowed.");
return nil;
}
return [super allocWithZone:zone];
}
This will prevent a subclass that is not a member of MyFinalClassName from being alloc'ed (and therefore init'ed as well), since NSObject's allocWithZone: must be called eventually, and by refusing to call super from your "final" class, you will prevent this.
There's a simpler way to prevent subclassing in Xcode 6 as a result of Swift interop. To prevent Swift classes from being subclassed in Objective-C the objc_subclassing_restricted is added to all class definitions in the {ProjectName}-Swift.h file.
You can use this in your projects:
#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
# define FOO_FINAL __attribute__((objc_subclassing_restricted))
#else
# define FOO_FINAL
#endif
FOO_FINAL
#interface Foo : NSObject
#end
#interface Bar : Foo
#end
The compiler will halt on the definition of Bar with Cannot subclass a class with objc_subclassing_restricted attribute
Here is possible solution:
#interface FinalClass : NSObject
#end
#implementation FinalClass
- (id)init
{
if (self.class != [FinalClass class]) {
return nil;
}
self = [super init];
if (self) {
// instance initialization
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
#end
#implementation InvalidSubclass
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
#end
I'm not sure this is 100% guaranteed because it's runtime-checking anyway, but it should be enough to block and warn people that they should not subclass this. Subclass might skip superclass's init, but then the instance will not be usable because it's not fully initialised by superclass.
Something like the following will ensure that every time an "impossible subclass" calls +alloc, an object will be allocated that is an instance of FinalClass, and not the subclass. This is essentially what NSObject's +alloc method does, but here we specify an explicit class to create. This is how NSObject allocates instances (in Obj-C 2), but there is no guarantee this will always be the case, so you may want to add an appropriate -dealloc which calls object_dispose. This method also means you don't get a nil object back if you try to instantiate a subclass - you do get an instance of FinalClass.
#interface FinalClass: NSObject
//...
+ (id)alloc; // Optional
#end
// ...
#import <objc/runtime.h>
#implementation FinalClass
+ (id)alloc {
if (![self isMemberOfClass:[FinalClass class]]) {
// Emit warning about invalid subclass being ignored.
}
self = class_createInstance([FinalClass class], 0);
if (self == nil) {
// Error handling
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
// Anything not in FinalClass will not work as +alloc will
// create a FinalClass instance.
#end
Note: I'm not sure I'd use this myself - specifying that a class shouldn't be subclassed is more in the nature of a design-contract with the programmer rather than an enforced rule at compile- or runtime.

Preventing subclasses overriding methods

Assume there is an object that initialises like so
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
and it has a subclass which is inited in a similar way
- (void)doInit
{
NSLog (#"In SubClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
Now if I create an instance of child class then I receive the following output:
In SubClass init
In SubClass init
when really, what I meant to happen is
In BaseClass init
In SubClass init
Is there a way to mark doInit to say that it shouldn't be overridden or do I need to create a unique name for all methods in a subclass?
I'm not entirely sure how I haven't come across this issue before, but there you go.
Edit:
I understand why this is happening, I hadn't expected that the base class would be able to call the overridden function.
I also can't just call [super doInit]; from the Subclass method because the BaseClass still needs to call doInit so that creating an instance of BaseClass will still work. If I called [super doInit], I'd still end up getting SubClass's doInit called twice.
It appears the answer is no and I'll just have to uniquely name each doInit like doBaseClassInit and doSubClassInit.
If you have a method that you don't want to by dynamically bound (i.e. don't want a subclass method to be called if it exists), you need to do it as a C function instead. So, you could do this instead:
In the base class:
static void DoInit(BaseClass *self)
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
in the subclass:
static void DoInit(SubClass *self)
{
NSLog(#"In SubClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
Note that both the DoInit methods are marked as static, so they are only visible each compilation unit (.m file) and don't conflict with each other.
You could, perhaps, try something like this in your base class. It would mean any time the init implementation inside BaseClass executed, the doInit implementation for BaseClass would be called.
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
Class baseClass = [BaseClass class];
SEL selector = #selector(doInit);
IMP baseClassImplementation = class_getInstanceMethod(baseClass, selector);
baseClassImplementation(self, selector);
return self;
}
As I mentioned in my comment, if that's the narrowness of your need this should work as it gets around the dynamic method lookup involved with sending a message. Hope this helps!
EDIT:
Disclaimer - if you're in this situation it's probably not a good sign for the longevity of your design. This technique will get you up and running for now but please document it carefully, and consider ways to refactor your code so this is no longer used. Consider fixes like these to really be used only when extremely urgent.
The reason why you are not getting the "In BaseClass init" console message is because your subclass is not calling the super's implementation of doInit.
If you don't want doInit overridden the 'best' way to avoid doing so is to not publish the existence of this method. Remove it from your header and uniquely name the method so that a collision is unlikely. For example, many of the private methods in Apple's frameworks have a leading underscore. So, for example, you could call your method _doInit and it will be very unlikely that a subclass accidentally create it's own overiding implementation.
Nope, there's no enforceable way to prevent a subclass from overriding a method. The best you can do is to avoid putting it in the public header file for the class so someone is not likely to try to override it. If the method has to be public, you just put a note in the documentation for the method that it shouldn't be overridden or that subclasses should call super's implementation whichever the case may be. You'll find these kind of instructions all over in the documentation for Apple's own classes.
If you want your subclass to use the baseclass version of doInit then in the subclass don't declare a doInit method. Here's what I mean:
ClassA:
#interface ClassA :
-(void) doInit;
-(id) init;
#implementation
-(void) doInit {
NSLog(#"ClassA doInit");
}
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
ClassB
#interface ClassB : ClassA
-(id) init;
#implementation
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
And really you don't need to override the init method as well unless there's some special code that you want that class to do.

Property not set in drawRect method - iOS

I have been seeing some strange behavior when I try to access a class variable or a property in my drawRect method..
In my .h file I have the following
#interface DartBoard : UIView
{
Board * board;
int index;
}
#property (readwrite, assign, nonatomic) NSNumber * selectedIndex;
#end
In my .m file I have the following
#implementation DartBoard
#synthesize selectedIndex;
-(id)init
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"selectedIndex: %d",[self.selectedIndex intValue]);
NSLog(#"index: %d",index);
}
#end
the output is
2012-06-12 19:48:42.579 App [3690:707] selectedIndex: 0
2012-06-12 19:48:42.580 App [3690:707] index: 0
I have been trying to find a solution but have had no luck..
I found a similar question but there was no real answer to the issue
See: UIView drawRect; class variables out of scope
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly but how do I fix it?
Cheers
Damien
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly
No, there is nothing special about -drawRect:.
There are two possibilities:
1. Your -init method is not being called.
You didn't say how this view gets created -- if you are manually calling [[DartBoard alloc] init], or if it is getting unarchived from a nib file.
If it's coming from a nib, UIView's unarchiving doesn't know that your init method should be called. It will call the designated initializer instead, which is -initWithFrame:.
So, you should implement that method instead, and make sure to call super!
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
}
return self;
}
2. There might be two instances of your view: one that you are manually initing, and another one that comes from somewhere else, probably a nib. The second instance is the one that is being drawn. Since its variables and properties are never set, they show up as zero (the default value).
You could add this line to both your -init and -drawRect: methods, to see what the value of self is. (Or, check it using the debugger.)
NSLog(#"self is %p", self);

Singleton's property memory management

I'm trying to write my little app and experiencing some memory management problems.
At first, I have Game singleton object with property:
//Game.h
#interface Game : NSObject
#property (nonatomic, retain) MapBuildingsLayer *mapBuildingsLayer;
+(Game *) game;
-(BOOL) addObject:(NSString *) objName At:(CGPoint) pt;
#end
where MapBuildingsLayer is just cocos2d CCLayer instance
//Game.m
#implementation Game
#synthesize mapBuildingsLayer = _mapBuildingsLayer;
static Game *instance = nil;
+ (Game *)game {
#synchronized(self) {
if (instance == nil) {
instance = [[Game alloc] init];
}
}
return instance;
}
-(BOOL) addObject:(NSString *)objName At:(CGPoint)pt
{
if([objName isEqualToString:OBJ_TYPE_PIT])
{
if([[Game game].mapBuildingsLayer addPitAt:pt]) //app crashes here
{
[self toggleConstructionMode];
return YES;
}
}
return NO;
}
#end
In MapBuildingsLayer.m's init method I use Game's mapBuildingsLayer property to store a reference to this CCLayer instance in my singleton (for future use in other methods):
//MapBuildingsLayer.m
#implementation MapBuildingsLayer
-(id) init
{
if( (self=[super init])) {
[Game game].mapBuildingsLayer = self;
}
return self;
}
When I call Game's addObject:objName At: method, my app crashes with EXC_BAD_ACCESS.
How I must declare property in Game singleton to use it from other places in my app's lifetime?
You typically don't use the singleton in the class itself, try changing
if([[Game game].mapBuildingsLayer addPitAt:pt]) //app crashes here
to
if([self.mapBuildingsLayer addPitAt:pt]) //app crashes here
You should be using [Game game] external to the class to get into the singleton instance of your class and call its methods, but internal to the class you would just refer to it as self like normal.
Typically if you're going to use a game singleton this isn't how you'd use it. Try thinking of it like this with a giant state machine, Create a CCScene subclass which will initialize all of your respective CCLayer subclasses and control them. Then from the statemachine you can load the appropriate initializing its scene and that will create everything under it.
In your applicationDidFinishLaunching method you simply have your singleton object load your first scene. I'd really recommend checking out the Learning Cocos2d Book as it covers this singleton state engine very well and I think would clear up all your questions.
Bottom line is have the state engine load the scene which loads the layers.
No where in your code i am seeing your mapBuildingsLayer initialized. I hope before returning your instance you should also do
instance.mapBuildingsLayer = [CCLayer alloc] init];
I think the way you are assigning the mapBuildingsLayer is wrong. Remove [Game game].mapBuildingsLayer = self from your MapBuildingsLayer init method and instead add the following inside Game init method:
self.mapBuildingsLayer = [[MapBuildingsLayer alloc] init] autorelease];
now it is initialized inside your singleton init method so you can access it simply as [Game game].mapBuildingsLayer anywhere outside the Game class. If this doesnt work try posting what addPitAt: does.
hope this helps

Problem with NSObject init method

I have a problem with the init() method of a standard NSObject. I wrote a class (EFAPersistence) which is a subclass of NSObject. EFAPersistance has a attribute called efaDatabase.
EFAPersistence.h
#interface EFAPersistence : NSObject {
FMDatabase * efaDatabase;
}
#property (assign) FMDatabase * efaDatabase;
Everytime an instance of EFAPersistance is created I want to assign efaDatabase a value from my AppDelegate.
EFAPersistence.m
#implementation EFAPersistence
#synthesize efaDatabase;
- (id)init {
if (self = [super init]) {
efaDatabase = [[NSApp delegate] efaDatabase];
}
return self;
}
#end
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
That's how the important AppDelegate code looks like.
AppDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate> {
FMDatabase * efaDatabase;
}
AppDelegate.m
- (id)init {
if (self = [super init]) {
NSString * databasePath =
[[NSBundle mainBundle] pathForResource:#"efa" ofType:#"sqlite"];
self.efaDatabase = [FMDatabase databaseWithPath:databasePath];
if (![efaDatabase open]) {
NSLog(#"Couldn't open database: %#", databasePath);
// TODO: Create a database here
}
self.db = [[EFAPersistence alloc] init];
}
return self;
}
As you can see I am calling the init method. I also affirmed this by using NSLog(). init() is called. The attribute I am trying to assign in EFAPersistence is also created before init() is called.
To sum everything up:
How can I make this work within the init() method so I do not have to write boiler plate code in all my methods of EFAPersistence?
It looks to me that your AppDelegate is unset when you try to create the EFAPersistance object the first time. This is on below line in AppDelegate.m
self.db = [[EFAPersistence alloc] init];
I imagine the app delegate is set after the init is done (returned).
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
NSApp works fine.
Quoting epatel:
I imagine the app delegate is set after the init is done (returned).
Correct. The nib loader completely instantiates each object (including the app delegate, if it's in a nib), then sets it as the value of any properties it's connected to. These are two separate operations; it will not set a not-yet-initialized object as the application delegate.
Quoting you (Jens) again:
The question is how to assign efaDatabase in EFAPersistences only once . There are other methods like awakeFromNib and viewDidLoad etc. But those are not available in a plain NSObject subclass.
Incorrect. awakeFromNib is sent to every object in a nib after the object has been initialized.
That said, I'm curious as to why you have EFAPersistence in a nib. From its name, it doesn't sound interface-related. Shouldn't the app delegate own the EFAPersistence, and the EFAPersistence own the database directly?