ARC weird deallocation behavior in UITableView - objective-c

I've had this behavior happen to me twice recently and I was wondering what the root cause of the problem is (aka how do I make sure this never happens so I don't have to waste a ton of time fixing it).
When I allocate something inside of a tableview cell that is slated for reuse, once another cell is loaded and the table reloads, sometimes that object is deallocated.
Example:
SubHolder *dataStorage;
- (void) initializeLicenseTable
{
LicenseCell *sampleLicense = [LicenseCell new];
self.licenseData = [[NSMutableArray alloc] initWithObjects:sampleLicense, nil];
nib = [UINib nibWithNibName:#"LicenseCell" bundle:nil];
if (dataStorage == nil)
{
dataStorage = [SubHolder new];
dataStorage.owner = self;
[dataStorage addStorageLocation];
}
} //cellForRowAtIndexPath and stuff
This code doesn't work without the if statement (it causes a dataStorage to become a zombie)
What causes this behavior? It seems like testing if dataStorage is nil and only then allocating it is the opposite of what should fix a zombie problem.
-EDIT-
If this behavior is caused by sharing of the variables, how can I make it so that each time an instance of this object is created it makes its own data storage object? Each table has its own information, which is not shared with other tables.

Since dataStorage is a global variable (visible in the file scope of your class), it will be shared by all the instances of your class.
Now, if a second instance of your class is initialized and you don't check for
if (dataStorage == nil)
then your global object will be overwritten and thus at some point deallocated through ARC. If some other object had stored its value somewhere, it will try to access the old object and you get the zombie access.
EDIT:
if each object needs its own dataStorage, you will simply need to declare
SubHolder *dataStorage;
in your interface declaration, or a property like:
#property (nonatomic, strong) SubHolder *dataStorage;

It looks like you're just creating new cells all the time instead of reusing them.
You should re-use cells like this:
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:aStyle reuseIdentifier:#"myCell"];
}

Related

Cocoa class member variable allocated inside function call nil unless forced to init/load

I come from a C/C++ background and am currently learning a bit about Cocoa and Objective-C.
I have a weird behavior involving lazy initialization (unless I'm mistaken) and feel like I'm missing something very basic.
Setup:
Xcode 10.1 (10B61)
macOS High Sierra 10.13.6
started from a scratch Cocoa project
uses Storyboard
add files TestMainView.m/.h
under the View Controller in main.storyboard, set the NSView custom class as TestMainView
tested under debug and release builds
Basically, I create an NSTextView inside a view controller to be able to write some text.
In TestMainView.m, I create the chain of objects programmatically as decribed here
There are two paths:
first one is enabled by setting USE_FUNCTION_CALL to 0, it makes the entire code run inside awakeFromNib().
second path is enabled by setting USE_FUNCTION_CALL to 1. It makes the text container and text view to be allocated from the function call addNewPage() and returns the text container for further usage.
First code path works just as expected: I can write some text.
However second code path just doesn't work because upon return, textContainer.textView is nil (textContainer value itself is totally fine).
What's more troubling though (and this is where I suspect lazy init to be the culprit) is that if I "force" the textContainer.textView value while inside the function call, then everything works just fine. You can try this by setting FORCE_VALUE_LOAD to 1.
It doesn't have to be an if(), it works with NSLog() as well. It even works if you set a breakpoint at the return line and use the debugger to print the value ("p textContainer.textView")
So my questions are:
is this related to lazy initialization ?
is that a bug ? is there a workaround ?
am I thinking about Cocoa/ObjC programming the wrong way ?
I really hope I am missing something here because I cannot be expected to randomly check variables here and there inside Cocoa classes, hoping that they would not turn nil. It even fails silently (no error message, nothing).
TestMainView.m
#import "TestMainView.h"
#define USE_FUNCTION_CALL 1
#define FORCE_VALUE_LOAD 0
#implementation TestMainView
NSTextStorage* m_mainStorage;
- (void)awakeFromNib
{
[super awakeFromNib];
m_mainStorage = [NSTextStorage new];
NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
#if USE_FUNCTION_CALL == 1
NSTextContainer* textContainer = [self addNewPage:self.bounds];
#else
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:self.bounds textContainer:textContainer];
#endif
[layoutManager addTextContainer:textContainer];
[m_mainStorage addLayoutManager:layoutManager];
// textContainer.textView is nil unless forced inside function call
[self addSubview:textContainer.textView];
}
#if USE_FUNCTION_CALL == 1
- (NSTextContainer*)addNewPage:(NSRect)containerFrame
{
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
[textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
#if FORCE_VALUE_LOAD == 1
// Lazy init ? textContainer.textView is nil unless we force it
if (textContainer.textView)
{
}
#endif
return textContainer;
}
#endif
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
#end
TestMainView.h
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface TestMainView : NSView
#end
NS_ASSUME_NONNULL_END
I am not familiar with cocoa that much but I think the problem is ARC (Automatic reference counting).
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
In the .h file of NSTextContainer you can see NSTextView is a weak reference type.
So after returning from the function it gets deallocated
But if you make the textView an instance variable of TestMainView it works as expected.
Not really sure why it also works if you force it though. ~~(Maybe compiler optimisation?)~~
It seems forcing i.e calling
if (textContainer.textView) {
is triggering retain/autorelease calls so until the next autorelease drain call, textview is still alive.(I am guessing it does not get drained until awakeFromNib function returns). The reason why it works is that you are adding the textView to the view hierarchy(a strong reference) before autorelease pool releases it.
cekisakurek's answer is correct. Objects are deallocated if there is no owning (/"strong") reference to them. Neither the text container nor the text view have owning references to each other. The container has a weak reference to the view, which means that it's set to nil automatically when the view dies. (The view has an non-nilling reference to the container, which means you will have a dangling pointer in textView.textContainer if the container is deallocated while the view is still alive.)
The text container is kept alive because it's returned from the method and assigned to a variable, which creates an owning reference as long as that variable is in scope. The view's only owning reference was inside the addNewPage: method, so it does not outlive that scope.
The "force load" has nothing to do with lazy initialization; as bbum commented, that it "works" is most likely to be accidental. I strongly suspect it wouldn't in an optimized build.
Let me assure you that you do not need to go around poking properties willy-nilly in Cocoa programming. But you do need to consider ownership relations between your objects. In this case, something else needs to own both container and view. That can be your class here, via an ivar/property, or another object that's appropriate given the NSText{Whatever} API (which is not familiar to me).

Objective C - call NSNumber outside FOR loop

It seems that i didn't cover my basics enough, but I hope that You guys mabybe will be able to help here.
I need to use cateringView.status outside this loop and even in another class. This is simple BOOL value, parsed from XML with PUGIXML
- (void)dataPrepared
{
Food* food = (Food*)[[DataManager sharedInstance] dataForItem:kDataManagerItemCatering];
if (food)
{
for (CateringView* cateringView in cateringViews)
[cateringView removeFromSuperview];
[cateringViews removeAllObjects];
for (FoodItem* item in food.catering)
{
CateringView* cateringView = [CateringView new];
[cateringView.imageView loadURL:[NSURL URLWithString:item.image] session:[DataManager sharedInstance].session completion:nil];
cateringView.status = item.status;
[self addSubview: cateringView];
[cateringViews addObject: cateringView];
}
[self layoutSubviews];
}
[super dataPrepared];
}
Could You explain me how can I do that?
My header file:
(...)
#interface CateringView : UIView
#property (strong) NSNumber* status;
#end
#interface CateringPreviewCell : PreviewCell
{
NSMutableArray* cateringViews;
(...)
}
#end
Is this somebody else's code you are trying to understand? It is unclear what you are asking, but you appear to be confusing the lifetime of local variables and objects. Maybe the following will help:
The second for loop starts:
for (FoodItem* item in food.catering)
{
CateringView* cateringView = [CateringView new];
This last statements does two things:
The right hand side (RHS) creates a new object of type CateringView. The result of the RHS is a reference to the created object. The lifetime of the created object extends as long as there is a reference to it[A].
The left hand side (LHS) creates a new local variable called cateringView. The reference returned by the RHS is stored in this variable. The lifetime of the created variable is a single iteration of the for loop.
At the end of the loop the code is:
[self addSubview: cateringView];
[cateringViews addObject: cateringView];
}
These two statements take the reference, to the created CateringView object, which is stored in the local variable cateringView and add it to this object's (which is an instance of the CateringPreviewCell class) subviews and cateringViews instance variable.
After these two statements have executed you have stored the reference to the created CateringView object three times: in the local variable cateringView, in the owning object's subviews, and in the owning object's cateringViews instance variable.
Also after these statements the loop iteration ends, so the lifetime of the local variable cateringView ends and you can no longer use that variable. However the reference to the object that was stored in that local variable still exists in two locations and that object is still alive.
You are stating you need to access cateringView.status outside of the loop. That does not make sense, the variable does not exist. However the object the variable referenced when it did exist is still alive, so the status value you seek is still around - you are just looking in the wrong place.
After the loop, and after the call to dataPrepared has returned, all the CateringView objects created can be accessed either:
as subviews of the object instance of CateringPreviewCell that dataPrepared was called on, or
as members of the instance variable cateringViews of that object instance.
The first of these is accessible "outside the class", the second can be provided you have instance methods defined on CateringPreviewCell which provided access to the instance variable.
HTH
[A]: This is not exactly true, but sufficient for the purpose here. Later you may learn about things such as weak references which do not govern how long an object lives.
You wouldn't use cateringView, because you actually have many of them. They're all stored in cateringViews so that's what you'd actually use. You'd either iterate all the views in that array or you'd choose one at a specific index to interact with.
Without knowing whether your intention is to try to add .status to
existing objects inside the cateringViews array
or
new objects that you add to cateringViews array
since your question has missing information and isn't very clear, here is the solution for #2
for (CateringView* cateringView in self.cateringViews) { //needed curly braces and self. to access property var
[cateringView removeFromSuperview];
}
[self.cateringViews removeAllObjects]; //again needed self.
for (int i = 0; i < food.catering.count; i++) { //make sure food.catering is array and not nil
CateringView* cateringView = [[CateringView alloc] init];
[cateringView.imageView loadURL:[NSURL URLWithString:item.image]
session:[DataManager sharedInstance].session
completion:nil];
FoodItem *item = food.catering[i]
cateringView.status = item.status;
[self addSubview: cateringView];
[self.cateringViews addObject: cateringView];
}
If I'm wrong and you're trying to accomplish #1, (consider improving your question wording if this is the case) then you'll need to loop through self.cateringViews as well.

In objective c can a class essentially delete itself?

In objective c, suppose I have an object Obj stored in a NSMutableArray, and the array's pointer to it is the only strong pointer to Obj in the entire program. Now suppose I call a method on Obj and I run this method in another thread. In this method, if Obj sets the pointer for itself equal to nil will it essentially delete itself? (Because there will be no more strong pointers left) I suspect the answer is no, but why? If this does work, is it bad coding practice (I assume its not good coding, but is it actually bad?)
It is highly unlikely that an object would be in a position to cause its own release/deallocation if your code is designed properly. So yes, the situation you describe is indicative of bad coding practice, and can in fact cause the program to crash. Here is an example:
#interface Widget : NSObject
#property (retain) NSMutableArray *array;
#end
#implementation Widget
#synthesize array;
- (id)init
{
self = [super init];
if(self) {
array = [[NSMutableArray alloc] init];
[array addObject:self];
}
return self;
}
- (void)dealloc
{
NSLog(#"Deallocating!");
[array release];
[super dealloc];
}
- (void)removeSelf
{
NSLog(#"%d", [array count]);
[array removeObject:self];
NSLog(#"%d", [array count]);
}
#end
and then this code is in another class:
Widget *myWidget = [[Widget alloc] init];
[myWidget release]; // WHOOPS!
[myWidget removeSelf];
The second call to NSLog in removeSelf will cause an EXC_BAD_ACCESS due to the fact that array has been deallocated at that point and can't have methods called on it.
There are at least a couple mistakes here. The one that ultimately causes the crash is the fact that whatever class is creating and using the myWidget object releases it before it is finished using it (to call removeSelf). Without this mistake, the code would run fine. However, MyWidget shouldn't have an instance variable that creates a strong reference to itself in the first place, as this creates a retain cycle. If someone tried to release myWidget without first calling removeSelf, nothing would be deallocated and you'd probably have a memory leak.
If your back-pointer is weak (which it should be since a class should never try to own it's owner, you will end up with a retain-cycle) and you remove the strong pointer from the array the object will be removed from the heap. No strong pointers = removed from memory.
You can always test this.
If you need a class to bring to a situation where its deleted, the best practice is to first retain/autorelease it and then make the situation happen. In this case the class won't be deleted in a middle of its method, but only afterwards.
I think we can say it might be bad coding practice, depending on how you do it. There are ways you could arrange to do it safely, or probably safely.
So let's assume we have a global:
NSMutableArray *GlobalStore;
One approach is to remove yourself as your final action:
- (void) someMethod
{
...
[GlobalStore removeObject:self];
}
As this is the final action there should be no future uses of self and all should be well, probably...
Other options include scheduling the removal with a time delay of 0 - which means it will fire next time around the run loop (only works of course if you have a run loop, which in a thread you may not). This should always be safe.
You can also have an object keep a reference to itself, which produces a cycle and so will keep it alive. When its ready to die it can nil out its own reference, if there are no other references and that is a final action (or a scheduled action by another object) then the object is dead.

exc_bad_access with GridView, possible memory management fail?

I have the following code that tries to set up a GridView and I have a GridviewController subclass that manages the datasource. This is the code that is used to set it up.
AQGridView* gridView = [[AQGridView alloc] initWithFrame:frame];
NUBMyCpGridviewController* controller = [[NUBMyCpGridviewController alloc] init];
gridView.dataSource = controller;
gridView.delegate = controller;
[gridView reloadData];
However, the app crashes when it tries to access the datasource. This is the line (in the Gridview class) that gives the tries to call the method and crashes it:
AQGridViewCell * cell = [_dataSource gridView: self cellForItemAtIndex: index];
The error is exc_bad_access. What could be the problem? Is it because the object is being released too early? How can I rectify it?
You're right; the problem is most likely that your NUBMyCpGridviewController is being deallocated. Based on your code snippet it looks like no one is retaining it.
My suggestion would be to make it a strong #property of whichever class your snippet code is being executed in.

Singleton Design

I'm creating a game that uses cards.
I have an AppController class with one instance in the nib.
The AppController instance has an NSArray instance variable called wordList.
On init, the nib's instance of AppController generates a new GameCard.
Every gamecard has an array of words containing 5 words selected at random from the the list in AppController.
Because the list is large, I'd like to read it into memory only once. Therefore, I want only one instance of AppController, as a singleton class. Every time a new GameCard is created from within AppController, it should access that same singleton instance to retrieve the wordlist.
So basically, I need a singleton AppController that creates GameCards, where each GameCard has a reference to the original AppController.
I'm not sure how to implement this. Sorry if the explanation was confusing.
A code example I found online follows (http://numbergrinder.com/node/29)
+ (AppController *)instance
{
static AppController *instance;
#synchronized(self) {
if(!instance) {
instance = [[AppController alloc] init];
}
}
return instance;
}
But when I tried to do something with it in a GameCard instance through the code below, my application took forever to launch and Xcode told me it was loading 99797 stack frames.
AppController *controller = [AppController instance];
It sounds like an infinite loop. Make sure that -[AppController init] isn't calling +[AppController instance].
Why does every card need a reference to the app controller?
If it's just to access its words, it's simpler to let each card own its words directly. Make a new method named initWithWords: the designated initializer for the GameCard class. Initialize each card with the array of its five words, and have the card own that array for its lifetime.
Removing the cards' references to the app controller would resolve the infinite loop that Tom astutely detected.
Also, if no word should appear on two cards at once, remember to take that into account when drawing from the app controller's Great Big Array Of Words, and when destroying cards (you may or may not want the words to go back into the pile for future cards).
It sounds like you're on the right track. I've never tried to put a reference to a singleton in a nib file, though. You may want to create a separate singleton class that maintains a copy of the data (DataManager, maybe?), and then call it from within your instance of AppController to fetch the words.
You may find that putting a singleton within a nib (using the code for a singleton in Stu's post) works just fine, though. Good luck!
It looks like you may be calling your class instance method from within your init method. Try something like this:
static AppController* _instance = nil;
- (id)init
{
// depending on your requirements, this may need locking
if( _instance ) {
[self release];
return _instance;
}
if( (self = [super init]) ) {
_instance = [self retain];
// do your initialization
}
return self;
}
+ (AppController*)instance
{
if( _instance ) return _instance;
else return [[AppController alloc] init];
}
This makes sure that only one instance of AppController is ever available and also that it's safe to allocate it as well as getting a copy through the instance class method. It's not thread safe, so if it's going to be accessed by multiple threads, you should add some locking around the checks to _instance.
The normal way to create an AppController/AppDelegate is to add a custom NSObject to your MainMenu/MainWindow.xib file. Set the class to be AppController. Link your UIApplication/NSApplication delegate reference to your AppController object. Then you can get your single AppController with either
(AppController*)[NSApp delegate];
or
(AppController*)[[UIApplication sharedApplication] delegate];
You never have to create it with alloc/init because it will be created when your application is launched. You don't have to worry about making it a singleton because no one will ever try to create another one. And you don't have to worry about how to access it because it will be the delegate of the UIApplication/NSApplication object.
All that said, if you need a global variable holding an array of words, then forget about the AppController and make a new singleton object which holds/reads the array. In which case you just need:
+ (NSArray *)sharedWordListArray
{
static NSArray *wordList;
if( !wordList ) {
wordList = [[NSMutableArray alloc] init];
// read array
}
return wordList;
}
If you really need thread safety, then simply call [WordList sharedWordListArray] from your app delegate's applicationDidFinishLaunching: method before starting any threads, or add an NSLock if you really want to defer the loading to later, but often its better to take the load time hit at the start of the program rather than unexpectedly when the user takes some later action.