exc_bad_access with GridView, possible memory management fail? - objective-c

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.

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).

Passing value from ViewController to NSObject in Objective-C

I need to pass value from a ViewController to NSObject as soon as the view loaded using Xcode with Objective c.
I am using the code below but the value is null.
-(void)viewDidAppear:(BOOL)animated
{
MyHomeModelNSObject *nsOb;
nsOb = [[MyHomeModelNSObject alloc] init];
nsOb.myString = self.userName.text;
}
The above code is working between Views when using segue, but it does not work with when passing the value to NSObject.
Thanks
The above code is working between Views when using segue, but it does not work with when passing the value to NSObject.
You're not using a real object. You're declaring a pointer to an object, but never allocating the object itself:
MyHomeModelNSObject *nsOb;
nsOb.myString = self.userName.text;
See? You're missing the bit where you do:
nsOb = [[MyHomeModelNSObject alloc] init];
What's more, even if you added that, the object would be deallocated as soon as viewDidAppear exits because it's a local variable. If you want it to hand around, you'll need to 1) create it and then 2) assign it to some property of your view controller or another object.

how to call a method from a class in another class

I'm working to a new app for mac osx where i'm using a drag and drop system to let the user to input some files [this part works well] and i have a tabelView where i would like to display the paths of files inputed.
I have the next method in tabelViewController.m:
-(void)add{
NSLog(#"da");
[list addObject:[[Source alloc] init]];
[tableView reloadData];
}
In the DropView.m i included the tabelViewController.h and i'm trying to call the add method but it does nothing:
#import "TableViewController.h"
.....
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender{
[self setNeedsDisplay:YES];
TableViewController *tvc;
[tvc add];
}
Can someone to figure out why it doesn't do anything ?
Edit1:
Ok after I fallow the answers, my concludeDragOperation method looks like this:
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender{
[self setNeedsDisplay:YES];
TableViewController *tvc = [[TableViewController alloc] init];
[tvc add];
[tvc rD];
}
rD is a method from tableViewController which contain the reloadData method.
But it doesn't want to work it don't reload the table view.
Any ideea ???
tvc needs to point to an actual object. [[tvc alloc] init]
Otherwise you are simply calling add on nil. This doesn't cause your program to crash as you might expect in other languages. Try it out and see what happens.
it seems as if you missed a great chunk regarding how OOP and Objective-C work (seriously, no offense there).
What link is there between DropView.m and tableViewController.h do you have?
By typing TableViewController *tvc; all you are doing is creating a pointer. You are neither creating an object nor pointing to an object, you have just simply created a pointer that can eventually point to an object in memory of type tableViewController.
Solution:
What you will need to do, is to somehow create a link between the two classes. For instance, you could create a custom delegate method for DropView that could communicate with any class who uses that custom DropViewDelegate methods. So, you could create a delegate method that tells objects that follow that delegate protocol that you just concluded a drag operation. A tutorial how to do so can be found at my blog [it's a permalink].
I am happy to post code, or you can read it on my blog. Good Luck.

After app comes from background "Message sent to deallocated instance"

I'm getting an odd error. We are using iOS 5 with ARC. When NSZombiesEnabled is set to true and the app is plugged into the debugger we get this error (it happens normally too but not as consistently)
2012-07-04 11:25:17.161 Trivial[624:707] -[vcCurrentGames gamesLoaded:] [Line 284] Found 62 games that are my turn.
2012-07-04 11:25:17.162 Trivial[624:707] -[vcCurrentGames gamesLoaded:] [Line 285] Found 26 games that are their turn.
2012-07-04 11:25:17.169 Trivial[624:707] -[vcCurrentGames tableView:heightForHeaderInSection:] [Line 409] Height 1: 29
2012-07-04 11:25:17.171 Trivial[624:707] *** -[vcDashboard retain]: message sent to deallocated instance 0xf62c3c0
We are not retaining the dashboard anywhere (ARC doesn't allow retain). This only happens after the app is loaded from the background. vcCurrentGames is actually a UITableView on the dashboard. Which makes it even more odd to me, because if the dashboard is dealloced then why is it's UITableView loading?
I've read a little bit about this. The dashboard is defined in the app delegate as a property:
#property (nonatomic, strong) vcDashboard *vDashboard;
I've attempted making this weak so that it will zero out, but that doesn't work either. Can someone tell me why it's being dealloced or why it's trying to retain vcDashboard after it's been dealloced?
In app delegate I declare it like this:
UIViewController *viewController = [[vcDashboard alloc] initWithNibName:#"vcDashboard" bundle:nil];
self.vDashboard = (vcDashboard *)viewController;
Maybe something goes wrong during initialization. You assign the vcDashboard to a UIViewController and then cast that controller to the appropriate class. While theoretically this should be fine, I have never seen this pattern before. The standard way is:
self.vDashboard = (vcDashboard*) [[vcDashboard alloc] init];
assuming that the nib name is "vcDashboard" (as seems to be the case) and that the class in the nib is also "vcDashboard".
(BTW, the convention is to capitalize class names.)
Also, after the app goes into the background, maybe vcDashboard gets deallocated. In any case, it is not guaranteed that it is still there when the app comes back from background. Did you consider lazy instantiation?
// in app delegate
-(vcDashboard*)vDashboard {
if (_vcDashboard) {
return _vcDashboard;
}
vcDasboard vc = [[vcDashboard alloc] init];
// more initialization code
_vcDashboard = vc;
return vc;
}

ARC weird deallocation behavior in UITableView

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"];
}