There are lots of questions about this error (Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0), but they are all about code written in Swift, and this is pure Objective-C. The answers mostly seem to be about "unwrapping nil" which apparently is something that cannot be done in Swift, but doesn't seem to have a direct counterpart in Objective-C.
I never had this error before "upgrading" to Catalina and Xcode 12 (from 10). It seems to happen randomly, after a few minutes, at different places in the code, but usually somewhere in the encodeWithCoder method when encoding one of the NSMutableArrays:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeInt:serial_version forKey:#"version"];
[encoder encodeObject:program_name forKey:#"program_name"];
[encoder encodeObject:program_info forKey:#"program_info"];
[encoder encodeObject:story_name forKey:#"story_name"];
[encoder encodeObject:story_title forKey:#"story_title"];
[encoder encodeObject:_windows forKey:#"windows"];
[encoder encodeObject:_streams forKey:#"streams"];
...
Original code here.
I guess makes it similar to Thread 1:EXC_BAD_INSTRUCTION error when saving NSMutableArray In custom object saved in NSUserDefaults in swift . Again, it works for a while, encoding everything just fine, and then suddenly crashes.
EDIT: Stack trace here.
As suggested by Ol Sen in a comment, rewriting the code to conform to NSSecureCoding (basically replacing decodeObjectForKey: with decodeObjectOfClass:forKey) seems to fix the problem.
Related
Environment: Mac OS 10.8.5, XCode 5.1.1
Problem: Crash in obj_msgsend on addObject message to a NSMutableArray
Disclaimer: I'm new to Objective-C, so this could an obvious mistake. But it's mysterious.
Details:
I've been able to prune the problem down to a small test case (thankfully), though the exact manifestation of the problem is different from the full application.
Here's the #interface:
#interface ObjCQueue : NSObject
+ (void) push: (NSString *)calEvent;
+ (NSString *) pop;
#end
Here's the Objective-C class implementation.
#import <Foundation/Foundation.h>
#include "ObjcQueue.h"
NSMutableArray *qArray;
#implementation ObjCQueue
{
}
+ (void) init
{
qArray = [[NSMutableArray alloc] init];
// NSLog(#"(init)qArray class is: %#\n", NSStringFromClass([qArray class]));
}
+ (void) push:(NSString *)calEvent
{
[qArray addObject:calEvent];
}
+ (NSString *) pop
{
// This will return nil if there's no first object
NSString *retEvent = [qArray objectAtIndex:0];
// Don't delete the front of the queue if nothing is there
if (retEvent != nil)
[qArray removeObjectAtIndex:0];
return retEvent;
}
#end
and main.m does this:
int main(int argc, const char * argv[])
{
#autoreleasepool {
[ObjCQueue init];
[ObjCQueue push:#"Pushed thing"];
NSLog(#"Popped: %#\n", [ObjCQueue pop]);
}
return 0;
}
For the moment, let's ignore the possibility that how I'm doing this is totally wrong (we'll get back to that).
If I run this code as-is, I get a crash in objc_msgSend called by the addObject message sent from [ObjCQueue push:]
The mystery part is, if I uncomment the NSLog call in [ObjCQueue init] everything runs just fine.
In the larger application, I see a different issue. The failure also occurred in the push method, except the run-time error I got said that addObject was an invalid selector. When I check the type of qArray in that case, it has a type of NSDictionary (that's from memory, it wasn't spelled exactly that way) instead of NSMutableArray. Also, in the larger application, adding the NSLog call in the init method makes everything run smoothly.
In this smaller example, the type of qArray always appears to be NSMutableArray.
In other answers to similar questions, the implication is that the object corresponding to qArray is getting overwritten, and/or released prematurely. I don't see how that could happen here, since it's global, and ObjCQueue only has class methods and no instance of it is created. [ObjCQueue init] is only called once.
One other bit of data: In this smaller case, qArray gets displayed differently depending where (in the debugger) it's displayed.
In init, in the case where it crashes, immediately after qArray gets its value, the debugger shows:
Printing description of qArray:
<__NSArrayM 0x10010a680>(
)
But in push, just before the addObject method is called, the debugger shows:
Printing description of qArray:
(NSMutableArray *) qArray = 0x000000010010a680
The value is the same, but the type is kinda sorta different (maybe). In the case with no crash, the display is identical in both cases (they're both the same as the first display)
This may not be the best way (or it may be a blatantly wrong way) to initialize qArray, and I can accept that. But why would the behavior change with the addition of the NSLog call?
Any help/insights will be appreciated.
-Eric
P.S. Here's the XCode project: Bug Test
The problem is because ARC is releasing qArray before you called push so you're calling on an object that is already released. A good solution to this problem would be to either change your class to an actual instance, or create a singleton so that ARC knows to retain the array rather than just releasing it right after you init.
I ran into this problem while trying to fix a memory leak with the facebook-ios-sdk. How do i handle this situation when passing objects from no arc compiled classe to arc enabled classe?
This is the code inside the non arc compiled Facebook library: (i removed the unnecessary stuff which is not related to the problem) as you can see, result object is not autoreleased or released.
- (void)handleResponseData:(NSData *)data {
NSError* error = nil;
id result = [self parseJsonResponse:data error:&error];
self.error = error;
// Call the defined delegate wich is my AppDelegate didLoad method wich is arc enabled
[_delegate request:self didLoad:result];
}
- (id)parseJsonResponse:(NSData *)data error:(NSError **)error {
SBJSON *jsonParser = [[SBJSON alloc] init];
//gets the object wich leaks or gets overreleased
id result = [jsonParser objectWithString:responseString];
[jsonParser release];
return result;
}
Now if i try to add autorelease to the result object, i am facing a NSZombie when my arc code in my AppDelegate try's to release the object. However if i leave the code like this i'm facing memory leaks whit the result object which gets not released.
am i missing something basic? i can't get my head around this?
Thanx for any advice! Chris
The result returned from -parseJsonResponse:... is autoreleased already (see note at bottom).
Since the name of the -parseJson method doesn't begin with new, alloc, copy or mutableCopy, the compiler assumes that it returns an object with a +0 reference count, meaning it needs to be retained by the calling code if it is to be kept around, and doesn't need to be released if it's not being kept around. That's a long winded way of saying that it should neither leak nor cause a crash in your ARC code as written in your question.
Passing objects between ARC code and manual reference counting code doesn't require any special handling. You just need to make sure that methods' names match their memory management semantics in the non-ARC code. It certainly seems like you've done that in this case, although as you say, you didn't post your complete code.
Note: Presumably, objectWithString: returns an autoreleased object. If it doesn't it, it should (because it doesn't start with alloc, new, copy, mutableCopy).
I recently posted a question here about some memory issues I was having. I've got that fixed now thanks to this wonderful community but I'm facing another problem. I'm using Cocos2d to develop a game and I'm trying to remove a Sprite from and array. The problem arises when I try and release the temporary array I'm using to keep track of the sprites to remove.
NSMutableArray *spritesToRemove = [[NSMutableArray alloc] init];
// Loop through all sprites
for(CSSprite *sprite in _sprites){
if(sprite.toRemove){
[spritesToRemove addObject: sprite];
}
}
// loop through sprites to be removed
for(CSSprite *removeableSprite in spritesToRemove){
[_sprites removeObject: removeableSprite];
// Cocos2d code to remove a sprite
[self removeChild: removeableSprite cleanup: YES];
}
[spritesToRemove release]; // EXC_BAD_ACCESS error
I get a feeling the reason I'm getting the error is because I'm releasing the sprite object in [self removeChild: removeableSprite cleanup: YES]; before actually releasing the array. It all works fine if I remove the line [spritesToRemove release] but I obviously then have a memory leak on my hands.
I've tried moving the removal of the sprites around and I can get the memory thing sorted by completely omitting the line [self removeChild: removeableSprite cleanup: YES]; but then Cocos2d throws the same EXC_BAD_ACCESS error from within CCNode at [child visit]; of -(void) visit
Thanks again for your help :-)
EDIT: I enabled NSZombie and I got this message:
*** -[Sprite release]: message sent to deallocated instance 0xfa94cf0
Which to me kind of suggests my initial thought, somewhere an entry in the array is being released to soon. Would that be correct? If so is there anyway for me to find out where?
I managed to find the issue (and to be honest I feel a little silly :-P ). I was releasing the sprite manually once I added it to the array, which wasn't in the code provided so you guys couldn't have found it. The sprite was already set up to be autoreleased and thus was being cleared twice - causing my error.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Is calling [self release] allowed to control object lifetime?
What will happen if I use this code snippet?
[self release]
self descrements its own retain count by one, just like release would do with any other object.
The only situation however that I have so far come across, where such a call was appropriate, was the case where an init… method would fail and be expected to return nil and deallocate the just allocated instance.
In 9 out of 10 situations you shouldn't use [self release], I'd estimate.
Something like this (which basically forbids the calling of - (id)init;, for cases where you want to force the use of a particular - (id)initWith… method.):
- (id)init {
[self release];
NSString *reason = [NSString stringWithFormat:#"%# is not a valid initializer for the class %#", NSStringFromSelector(_cmd), NSStringFromClass([self class])];
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:nil];
return nil;
}
or this (which basically enforces the proving of an object on initialization)
- (id)initWithFoo:(Foo *)foo {
if (!foo) {//foo is required to be non-nil!
[self release];
return nil;
}
//proceed with initialization
return self;
}
These however are not the only ever occasions where [self release] would be appropriate. But merely the ones I've come across so far. (as Oliver correctly pointed out)
Edit: Self-invalidating NSTimers would probably be another (quite common, yet somewhat special) situation.
You will release yourself, what means that the object declares itself that it does not have to still live. If no one else retains tha object, it dies and its memory is freed
You have little reasons to do that, except if you want to create object that are not controlled by anyone else and are self controlled.
I mean, for example, there is the case where you may want to create an self living object that makes some treatments, and suicide when it has finished without having to tell anyone else it has finished.
Let's imagine for example a class whose purpose is just to send a message to a server :
You instanciate the class, as soon as it is istanciated it send a message to a server, it waits for the server answer, and if the answer is ok, it suicide. That way, you don't have to manage any server answer from within another class controller whose purpose is not to manage such events.
It just decrements the receiver’s reference count.
I created an NSScanner category method that shows a leak in instruments.
- (BOOL)scanBetweenPrefix:(NSString *)prefix
andSuffix:(NSString *)suffix
intoString:(NSString **)value
{
NSCharacterSet *charactersToBeSkipped = [self charactersToBeSkipped];
[self setCharactersToBeSkipped:nil];
BOOL result = NO;
// find the prefix; the scanString method below fails if you don't do this
if (![self scanUpToString:prefix intoString:nil])
{
MY_LOG(#"Prefix %# is missing.", prefix);
return result;
}
//scan the prefix and discard
[self scanString:prefix intoString:nil];
// scan the important part and save it
if ([self scanUpToString:suffix intoString:value]) // this line leaks
{
result = YES;
}
[self setCharactersToBeSkipped:charactersToBeSkipped];
return result;
}
I figure it's the way I'm passing the value to/from the method, but I'm not sure. It's a small leak (32 bytes), but I'd like to do this right if I can. Thanks in advance.
I found the answer. I had a model class that used the result of the code above, but forgot to release the property in the dealloc method. I should have caught it with Instruments, but I didn't know where to look. I got caught up in the call stack in the Extended Details, which had only part of the info I needed.
For morons like me, here's what I did:
Run your app with Instruments ... Leaks.
In Instruments, watch the leaked blocks view (the grid icon at the bottom) and turn on the extended detail.
If you have multiple leaked objects, click the disclosure triangle so you can look at a discrete address.
Next to an address, there will be a detail arrow. Click on it.
Now you'll be looking at history. It starts with Malloc and shows you each retain and release.
You should have a release for the malloc and a release for every retain in the history. Match up your retains and releases and look for the oddball. When in doubt, look toward the bottom of the page and carefully review any properties you may have forgotten to release in your dealloc method(s).
OK, I'm stupid. This method does not leak. The leak is farther down my call stack, but I can't figure out where. Sorry for the premature post. I'll follow up when I figure out where the real leak is, if I can't figure out the fix myself.
In answer to Yuji's comment, my method call is:
NSString *title;
[fileScanner scanBetweenPrefix:kTitlePrefix
andSuffix:kTitleSuffix
intoString:&title];
Is it the &title that's the problem? I patterned it after the existing NSScanner methods and calls.