#interface ViewController
{
NSMutableArray * GetPrices;
}
-(void)viewWillAppear:(BOOL)animated
{
GetPrices=[[NSMutableArray alloc]init];
// here I’m adding objects to the array..
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
#try
{
if([Getprices count]>0)
{
// dealing with array values
}
}
#catch (NSException *exception) {
// here Im using some mail service to get crash description
}
}
So I got following info to my mail
Stack Trace: -[__NSCFString count]: unrecognized selector sent to instance 0x157153180
stackoverflow.com/questions/5152651/… from this accepted answer Im thinking that array was released at some point.
Now my doubt is, Is there any chance of my array become released… (Let us say my app is in background for a long time, will my array gets released).
What are possible reasons for that crash ?
Thank You..
ARC will retain this array, so there is no way it becomes released until you do it programatically.
Related
I don't understand whats causing an EXC_BAD_ACCESS - KERN_INVALID_ADDRESS error in my app.
When my app starts up, my AppDelegate.m I make a call to a SKProductRequest to get all the available IAPs for an app. It works sometime but not others and I'm at a loss to figure out why. My code in the AppDelegate to do this looks like the following:
[[MyIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
for (SKProduct *prod in products) {
// set up pricing in the db
[Price setPriceOfProduct:prod.productIdentifier WithLocale:prod.priceLocale AndAmount:prod.price];
}
}
}];
The requestProducts... method is simply:
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
_completionHandler = [completionHandler copy];
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
Any help on this is much appreciated - if I've left out any key information then just let me know.
For dealing with BAD_ACCESS, you should have a look at my answer in this post :
iOS error Terminating app due to uncaught exception 'NSRangeException Range or index out of bounds'
See how to add the "malloc_error_break" breakpoint. This might help to find out which line is causing the bug.
In your case, I suspect that your MyIAPHelper instance, or _productsRequest has been deallocated.
To check this, you should override the "dealloc" method of MyIAPHelper like this :
-(void)dealloc{
NSLog(#"MyIAPHelper is deallocated");
}
And add a breakpoint on the NSLog to see if dealloc is called.
You should also check that your _productsRequest property is retained (i.e. declared like this : #property(nonatomic,retain) SKProductsRequest* productsRequest;
Try Block_copy() instead of copy on the handler.
_completionHandler = Block_copy(completionHandler);
This answer should help.
(and don't forget to call Block_release(...) when you're done with it.
In my app I'm accessing and changing a mutable array from multiple threads. At the beginning it was crashing when I was trying to access an object with objectAtIndex, because index was out of bounds (object at that index has already been removed from array in another thread). I searched on the internet how to solve this problem, and I decided to try this solution .I made a class with NSMutableArray property, see the following code:
#interface SynchronizedArray()
#property (retain, atomic) NSMutableArray *array;
#end
#implementation SynchronizedArray
- (id)init
{
self = [super init];
if (self)
{
_array = [[NSMutableArray alloc] init];
}
return self;
}
-(id)objectAtIndex:(NSUInteger)index
{
#synchronized(_array)
{
return [_array objectAtIndex:index];
}
}
-(void)removeObject:(id)object
{
#synchronized(_array)
{
[_array removeObject:object];
}
}
-(void)removeObjectAtIndex:(NSUInteger)index
{
#synchronized(_array)
{
[_array removeObjectAtIndex:index];
}
}
-(void)addObject:(id)object
{
#synchronized(_array)
{
[_array addObject:object];
}
}
- (NSUInteger)count
{
#synchronized(_array)
{
return [_array count];
}
}
-(void)removeAllObjects
{
#synchronized(_array)
{
[_array removeAllObjects];
}
}
-(id)copy
{
#synchronized(_array)
{
return [_array copy];
}
}
and I use this class instead of old mutable array, but the app is still crashing at this line: return [_array objectAtIndex:index]; I tried also this approach with NSLock, but without a luck. What I'm doing wrong and how to fix this?
I believe this solution is poor. Consider this:
thread #1 calls count and is told there are 4 objects in the array.
array is unsynchronized.
thread #2 calls removeObjectAtIndex:2 on the array.
array is unsynchronized.
thread #1 calls objectAtIndex:3 and the error occurs.
Instead you need a locking mechanism at a higher level where the lock is around the array at both steps 1 and 5 and thread #2 cannot remove an object in between these steps.
You need to protect (with #synchronized) basically all usage of the array. Currently you only prevent multiple threads from concurrently getting objects out of the array. But you have no protection for your described scenario of concurrent modification and mutation.
Ask yourself why you're modifying the array on multiple threads - should you do it that way or just use a single thread? It may be easier to use a different array implementation or to use a wrapper class that always switches to the main thread to make the requested modification.
I enabled zombies with my Xcode to find if my process crashes to a memory leak. Here is a code snippet:
- (NSString *)facVersion
{
return facVersion;
}
- (void) setFacVersion:(NSString*)_facVersion
{
if(facVersion != nil) [facVersion release];
facVersion = [_facVersion retain];
}
Now when I call
NSLog(#"%#", facVersion);
[self setFacVersion:facVersion];
the code crashes with the message
[CFString retain]: message sent to deallocated
Do you know what the problem is?
This is a typical problem with badly written setters. When the object itself is the last owner of the backing ivar of the property, assigning the property to itself causes a release effectively deallocating the object, then a retain on the same deallocated object. You can fix this in two ways. Either check for the to-be-assigned object not being the same as the current value of the property, or retain first and release only after. All in all, solution one:
- (void) setFacVersion:(NSString*)_facVersion
{
if (facVersion == _facVersion) return;
[facVersion release];
facVersion = [_facVersion retain];
}
Soltion two:
- (void) setFacVersion:(NSString*)_facVersion
{
[_facVersion retain];
[facVersion release];
facVersion = _facVersion;
}
By the way, checking for an object not being nil before releasing it is superfluous. Objective-C is not Java.
It's supposed to be if (facVersion != _facVersion) in the setter. Otherwise if you set the same object again, your setter will release it (resulting in the object being deallocated) and you can't use it (retain) after that:
- (void) setFacVersion:(NSString*)_facVersion
{
if(facVersion != _facVersion) {
[facVersion release];
facVersion = [_facVersion retain];
}
}
Also, did you know about Automatic Reference Counting (ARC)?
Automatic Reference Counting (ARC) is a compiler feature that provides
automatic memory management of Objective-C objects. Rather than having
to think about about retain and release operations, ARC allows you to
concentrate on the interesting code, the object graphs, and the
relationships between objects in your application.
If you pass the same object to setFacVersion as already set, it will first release it and then try to retain released (and deallocated) object.
You're releasing it before you call retain.
Unless you're comparing the pointers before releasing and retaining, you should use something like:
- (void) setFacVersion:(NSString*)_facVersion
[_facVersion retain];
[facVersion release];
facVersion = _facVersion;
}
That way, if the pointers are the same, as you demonstrated, you won't get a crash.
Or just let the compiler synthesize the setter for you.
Some information about the way I am saving my data: I have an array of View Controllers that are added and deleted by the user (this is basically a note taking app and the View Controllers are folders). The View Controllers have several dynamic properties that the app needs to save as well as the notes array within them, and then the Note objects themselves have a few properties that need to be saved. The View Controllers and the Notes both of course have the proper NSCoding stuff, this is the one on the View Controller for example:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.folderName forKey:#"lvcTitle"];
[encoder encodeObject:[NSNumber numberWithInt:self.myPosition] forKey:#"myPosition"];
[encoder encodeObject:self.notes forKey:#"notes"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.folderName = [decoder decodeObjectForKey:#"lvcTitle"];
NSNumber *gottenPosition = [decoder decodeObjectForKey:#"myPosition"];
int gottenPositionInt = [gottenPosition intValue];
self.myPosition = gottenPositionInt;
self.notes = [decoder decodeObjectForKey:#"notes"];
return self; }
The array of Controllers belongs to a Singleton class. NSCoding is pretty confusing to me even though it's considered to be simple stuff, but so far I've had success with only telling the Singleton to save the Controllers array - which then (successfully) saves all of the contained properties of the View Controllers, their properties and all of the Notes' properties as well. Here is the code in the Singleton:
- (void) saveDataToDisk:(id)object key:(NSString *)key {
NSString *path = [self pathForDataFile];
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:object forKey:key];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path]; }
- (void) loadDataFromDisk {
NSString *path = [self pathForDataFile];
NSDictionary *rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if ([rootObject valueForKey:#"controllers"] != nil) {
self.controllers = [NSMutableArray arrayWithArray:[rootObject valueForKey:#"controllers"]];
firstRun = false;
LabeledViewController *lastOneThere = [self.controllers objectAtIndex:self.controllers.count-1];
lastOneThere.isFolderAddView = TRUE;
}else{
firstRun = true;
}
}
I then call the save method several times in the Folder View Controllers:
[singleton saveDataToDisk];
And this will work well several times, until I randomly get a crash right when the app is loading up. The culprit is heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Note *currentNote = [self.notes objectAtIndex:indexPath.row];
if (currentNote.associatedCellIsSelected) {
return currentNote.myHeight + NOTE_BUTTON_VIEW_HEIGHT;
}
return NORMAL_CELL_FINISHING_HEIGHT; }
I get the following error:
2012-06-07 08:28:33.694 ViewTry[1415:207] -[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710
2012-06-07 08:28:33.696 ViewTry[1415:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710'
*** First throw call stack:
I understand that "__NSCFString" and "unrecognized selector sent to instance" means that there is a string somewhere there shouldn't be, as associatedCellIsSelected is a bool. However, if I only return "currentNote.myHeight" in heightForRow, I also get the same __NSCF error with myHeight, which is a float. If I take out heightForRow all together, everything works except for the appropriate height definitions.
BTW, the table view that heightForRowAtIndexPath is referencing is made in loadView AFTER the notes array is made and populated. I just don't understand why this error would only pop up every once in a while (like 5-10 opens, savings, closings and reopenings of app), seemingly random - I cannot find the pattern that causes this behavior. Any pointers?
Sorry for the mess, I'm new to iOS programming and I'm sure I'm doing a lot of things wrong here.
Edit - Also, once the app has crashed, it stays crashed every time I reopen it (unless I disable heightForRow) until I uninstall and reinstall it.
When you see an "unrecognized selector" error and the receiver type is not the kind of object that you coded (in this case __NSCFString instead of Note), the odds are that you have a problem where the object you intended to use has been prematurely released and its address space is being reused to allocate the new object.
The fix depends on tracking down where the extra release is happening (or retain is not happening). If you can show the #property declaration for notes it might shed more light on the situation.
One quick thing to do is choose Product->Analyze from the menu and fix anything it flags. It won't catch everything but it's a good sanity check to start.
for my table view I have the following going on (paraphrased)
.h
#interface OptionsView : UIView <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, retain) NSArray *dataSource;
.m
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
...
self.dataSource = [NSArray arrayWithObjects:options, sections, sponsor, nil];
}
return self;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.dataSource) {
NSArray *ds = [self.dataSource objectAtIndex:indexPath.section];
NSDictionary *d = [ds objectAtIndex:indexPath.row];
ActionBlock a = [d objectForKey:ACTION]; // <-- Thread 1: EXC_BAD_ACCESS (code=2, address=0x802)
if(a != nil) a();
}
}
You can see that in my didSelectRowAtIndexPath I'm getting an EXC_BAD_ACCESS but I'm not sure why. Because I'm using arc it isn't a zombie problem (already checked).
Using breakpoints I see that the self.dataSource exists after it's initialized, but not later when it's needed. It doesn't show up as <nil> either, it's just white space. Also, this works in debug but not in release so what does that mean?
** EDIT adding a screenshot **
You will notice that neither ds or d show up.. odd?
So there's two places I can see that might cause the problem. First, what is ACTION? Is it some sort of NSString? Just want to make sure that you're using a valid key object. Second, (and more likely the problem), it looks like ActionBlock is some kind of code block you're storing in a collection array. Are you copying that block before you store it in the array/dictionary? You must copy any block you intend on keeping around (storing) longer than the scope it was created in. This is easy to do. For example:
void (^NewBlock)(void) = [^{
....code....
} copy];
Alternately:
void (^NewBlock)(void) = ^{
....code....
};
[dictionary setObject:[NewBlock copy] forKey:ACTION]; //Assuming #define ACTION #"action"
This copies it onto the heap so that it can be stored. If you don't copy it, you'll get BAD_EXC_ACCESS anytime you try to reference the block outside the scope it was created in.
It seems you´re using a UIViewController instead of a UITableViewController. If I remember correctly, you have to go to the inspector and drag the delegate and datasource from the tableView to the UIViewController. I don´t remember exactly and I´m not on my computer but I´m sure I did it several times before I began to use a UITableViewController for every tableView I have.
Oh, and i wouldn´t use dataSource as a name for your array. Just to prevent naming conflicts.