Objective-C: Recursive method that changes property of self - ios

I have a recursive method that changes a property of self (the object on which the method is defined).
I get the error:
...uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x755f670> was mutated while being enumerated...'
I read about this error, but I'm not sure how to apply a solution to my problem. Recursion is crucial to the solution, as is updating this particular property. I'm very new to Objective-C, so perhaps I'm missing something or designing this solution poorly.
currentPlayer is the property
recursiveMethod is, obviously, the recursive method that's producing the error
This recursive method is called from within a for in loop.
- (void) recursiveMethod:(id <Team>)team atIndex:(int *)i withPlayer:(id <Player>) {
[self.currentPlayer replaceObjectAtIndex:i withObject:nextPlayer];
if // some conditional that's unimportant to this question
{
// grab another team
// grab another index
// grab another player
[self recursiveMethod:nextTeam atIndex:i withPlayer:nextPlayer];
}
}
A lot of the details are unimportant. I stripped it down; really it's just a recursive method that will update a property (in this case an array) of the object on which the method is defined.

Just read the exception message:
NSArrayM [...] was mutated while being enumerated.
You can't do that.

Related

NSArrayWithObject:nil asserts, but NSArrayWithObjects:nil doesn't. Bug or feature?

Whilst debugging a problem with UICollectionView -reloadItemsAtIndexPaths, I traced an assert to a condition where I (normally) need to pass this method a single element array (I have only one cell to reload). Hence, somewhat obviously, I used the equivalent of the following to generate the necessary array:
NSIndexPath *foo = ...
NSArray *bar = [NSArray arrayWithObject:foo];
[mycollectionview reloadItemsAtIndexPaths:bar];
However, in the special case when mycollectionview is still empty then the indexpath foo is nil (ie no cell to reload), it appears arrayWithObject: asserts with the error:
2014-10-11 12:45:08.066 Xulu[26594:90b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
HOWEVER, if I instead use
NSArray *bar = [NSArray arrayWithObjects:foo,nil];
Everything is fine.
So my question is, is the fact that [NSArray arrayWithObject:nil] asserts a bug, or an undocumented feature? The Apple docs saying nothing about the parameter having to be non-nil. I see lots of posts about using nil with arrayWithObjects, but nothing about arrayWithObject and nil. And this seems like a not-that-unusual situation...
By definition, arrayWithObject's parameter must be a pointer which is legal as an element of an NSArray. An NSArray cannot contain nil, so it's a design error to pass nil to this method — hence the exception.
On the other hand, arrayWithObjects takes a nil-terminated list of arguments. Passing nil alone constitutes passing an empty list, creating an empty array. Nothing wrong with that.
However, in modern ObjC it's best to use neither method. Instead, use array literals:
NSArray *bar = #[foo];
This makes your intent clearer to future readers of your code, catches problematic use of nil with a clearer error message, and avoids the kind of silent failures that can arise from use of arrayWithObjects and unintended nils.
You can't put nil into an array - You have to store an NSNull object
NSArray *bar = [NSArray arrayWithObjects:foo,nil];
creates a one element array containing foo - nil is the sentinel indicating the end of the list of objects to put into the array - it isn't put into the array
Now, if foo itself is nil then this is the same as saying
NSArray *bar = [NSArray arrayWithObjects:nil,nil];
The first nil terminates the list and the second nil is ignored - so you end up with an empty array
NSArray *bar = [NSArray arrayWithObject:foo];
Creates a one element array and puts the specified object into it - but your object is nil and you can't store nil so you get the assertion failure.
To followup on my own question, after further digging - and in lieu of the comments above - this is probably working-as-designed; ie an arguably ill-documented 'feature', not a bug... In particular, Apple's documentation for insertObjectAtIndex: does clearly state:
(void)insertObject:(id)anObject atIndex:(NSUInteger)index
Parameters
anObject
The object to add to the array's content. This value must not be nil.
Important: Raises an NSInvalidArgumentException if anObject is nil.
Probably the arrayWithObject: documentation could benefit with something similar. Thnx for those that took the time to respond. This can be closed.

Odd error (UITextSelectionView) while calling a method?

I'm facing a problem while calling a method and I don't know how figure it out.
Basically, during the main menu, I want to call a SKNode showing a tutorial part. The code is the following:
- (void)didMoveToView:(SKView *)view
{
...
if ([[GameData sharedData] openingTutorial]) { // Checks if the menu needs the tutorial
[_tutorialObj performSelector:#selector(runTutorialWithStep:)
withObject:[NSNumber numberWithInt:8]
afterDelay:3.0
];
}
}
When the method didMoveToView: is called (even before waiting the 3 seconds for the runTutorialWithStep:), I got this error:
[UITextSelectionView name]: unrecognized selector sent to instance 0x1576e6c0
2014-10-14 11:01:19.430 [406:130379] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITextSelectionView name]: unrecognized selector sent to instance 0x1576e6c0'
The odd thing is that in the previous class I use the same tutorial's action in the didMoveToView: and it's working right. But...
Thing is getting more strange here!!!
If I use an intermediary method for this calls:
- (void)didMoveToView:(SKView *)view
{
...
[self performSelector:#selector(intermediaryMethod)
withObject:nil
afterDelay:3.0
];
}
- (void)intermediaryMethod
{
[_tutorialObj performSelector:#selector(runTutorialWithStep:)
withObject:[NSNumber numberWithInt:8]
afterDelay:0.0
];
}
Everything works without issues. I don't want to avoid the problem but solve this. Any advices?
The error says it all. You try to send a 'name' message to object that doesn't implement it (UITextSelectionView). Since your second take works the cause is either in
[[GameData sharedData] openingTutorial]
method or before this call. Search for objects calling 'name' and check if it's the UITextSelectionView in question.
That or maybe you have weak reference to general view object and during those three seconds before calling runTutorialWithStep you reassign it to object that implements 'name' message.
Ok, I solved the problem.
I added a breakpoint for every exception and I realized that the issues was due to an other class (the only one with UITextView).
Basically I removed the text field from its parent ([self removeFromParent]) by my own once I did not need it anymore.
I suppose the error was fired during the deallocation 'cause the program can't find the UITextView. I managed to remove this statement and everything works right.
Actually I still have doubts because I don't understand why this exception is throw only if I call the [_tutorialObj runTutorialWithStep:] method.
I found the way to fix it.
UITextView *textView = .....
textView.selectable = NO;

How to lookup code relating to variable memory address from Xcode IOS crash?

I have a weird crash here that I believe is stemming from an external library, but I'm having trouble tracking down the stack trace, and not sure how to use Xcode's memory exploration tools.
This is what I'm getting in the console when it crashes:
*** Terminating app due to uncaught exception 'NSGenericException',
reason: '*** Collection <__NSArrayM: 0x208c5d00> was mutated while being enumerated.'
*** First throw call stack:
(0x37ae32a3 0x35e0d97f 0x37ae2d85 0x7c57d 0x728b1 0x6b865 0x34d9f11f 0x34d9e4b7 0x34da31bd 0x37ab6f3b 0x37a29ebd 0x37a29d49 0x368602eb 0x38ffb2f9 0x643d1 0x64358)
libc++abi.dylib: terminate called throwing an exception
I'm using MKNetworkKits UIImageView setImageFromURL additions to supply images to a UITableViewCell imageview (a custom imageview object, not the default imageview that comes with UITableViewCell). When I remove this setImageFromURL call, I don't get any crashes.
I tried using dwarfdump and atos on the command line but none of the addresses in the crash above link to any specific function addresses.
I have scoured for places where forin enumeration loops are being performed, but can't seem to find any that actually mutate data. Clearly I'm overlooking something..
Any advice/tips/help here would be super appreciated..
EDIT: Thanks so far for the comments. Any tips on how to utilize the memory address to trace down actual lines of codes would be helpful - can Xcode do some of what Visual Studio debugger can do, with respect to code & memory inspection?
Steps :
Run application and check on which ViewController the app is crashing with this exception.
Once you find the ViewController check entire code for that controller for NSArray.
Check related delegate you are calling from that controller, if any.
Check all custom subclasses you are using in that controller.
From the description it seems like you are enumerating an NSArray and checking values of it. Once you find the value you want to change those value. So, you are creating a mutableCopy of NSArray and trying to change inside the current running enumeration.
Solution :
Create NSMutableArray outside enumeration. While enumerating NSArray the objectAtIndex: will remain same for both array when you find the value you want to change. Change it inside mutable copy(created outside). [Bad for memory]
Create NSMutableArray outside enumeration of current NSArray. Then enumerate NSMutableArray. [Enumeration Will be Slower as you are enumerating mutable copy]
Hunt the Bug down!
This maybe useful for you:
replace your main.m file code with this (for ARC):
int main(int argc, char *argv[])
{
#autoreleasepool {
int retVal = -1;
#try {
retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([FRAppDelegate class]));
}
#catch (NSException* exception) {
NSLog(#"Uncaught exception: %#", exception.description);
NSLog(#"Stack trace: %#", [exception callStackSymbols]);
}
return retVal;
}
}
I suggest, starting simple, somewhere, you are altering an array, probably an NSMutableArray inside a loop. This can be easily fixed by using temporary store, them remove the temp objects AFTER the loop completes.
If this does not help, set an exception break point to break on all exceptions. This will likely stop on the line of code causing the error.
This helped:
https://stackoverflow.com/a/12266856/420594
Editing the Debug scheme and switching from LLDB to GDB helped a great deal - the crash points directly to a line of my own code now.

NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault

My iOS app uses core data via multiple threads. I am getting some crash reports with the following message: "'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x1e07a9b0 ''
I understand what is causing this problem - that the object was deleted but another thread is trying to access it. I am working to solve the problem but I want to add a check in the background thread to see if the object will fault in this manner.
My code at the moment relates to myObject.myValue. Is it possible to do some check, such as:
if (!myObject.myValue) {
return;
}
... so that it will get out of the method before doing anything that could cause such a crash? Or will simply calling myObject.myValue, even to see if it's null, cause such an exception to be thrown?
You could try and use existingObjectWithID:error::
Returns the object for the specified ID.
- (NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID error:(NSError **)error
Discussion
If there is a managed object with the given ID already registered in the context, that object is returned directly; otherwise the corresponding object is faulted into the context.
This method might perform I/O if the data is uncached.
Unlike objectWithID:, this method never returns a fault.
You could dO:
if ([myMOC existingObjectWithID:myObject.objectID error:&error])
...
You should verify that the object exists before accessing it's variables if you're having issues where the object may be deleted on another thread.
Two methods:
Refresh the view datasources whenever your data is being deleted. You can do this by registering for the NSManagedObjectContextObjectsDidChangeNotification notification and then parsing the userInfo on that notification to see which object was deleted.
Use code similar to below when you're passing data around to multiple threads.
Example:
// Cache and pass the object's ID off to another thread to do work on
// You can just store it as a property on the class
#try {
NSManagedObject *theObject = [managedObjectContext objectWithID:self.theObjectID];
// do stuff with object
}
#catch (NSException * e) {
// An entity with that object ID could not be found (maybe they were deleted)
NSLog(#"Error finding object: %#: %#", [e name], [e reason]);
}
You can check the NSManagedContext is existed when you use the NSManagedObject.
like this:
if (obj.managedObjectContext)
{
//do things
}
You can check [myObject isFault] where myObject is a NSManagedObject instance
You could give a try to use :
shouldDeleteInaccessibleFaults
property on managed object context. As this article says it should change the behaviour of faulting already deleted object.
https://cocoacasts.com/what-are-core-data-query-generations/
Edit:
Since iOS 9 (when it was added) this property default value is YES.

Serialization array of custom objects iOS

I know there is a lot of resources about that here but yet, I can't find what's going wrong with my code:
I have a class Level and two subclasses of Level: GameLevel and TransitionLevel. Each of this class implements the NSCoding protocol.
Then, I've got an array of Level (so it contains both classes of GameLevel and TransitionLevel). Saving the archive seems to work fine :
NSString *archivePath = [DOCUMENT_DIRECTORY stringByAppendingPathComponent:#"levels.archive"];
[NSKeyedArchiver archiveRootObject:levels toFile:archivePath];
Note that levels is the array I want to save. I can see the file, it's created and seems to contains what it's supposed to contain.
But when I want to retrieve this array :
NSString *archivePath = [DOCUMENT_DIRECTORY stringByAppendingPathComponent:#"levels.archive"];
levels = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
I've got this exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
I'm not sure what I'm missing here.
Either your implementation of initWithCoder: returns nil, either some part of your code tries to insert a nil value into an array.
You may go in the Breakpoint Navigator (⌘6) and add an Exception Breakpoint. Then, when the application raises an exception, the Debug Navigator will display the stack of the functions and methods currently executed. This would allow you to know precisely which method is trying to insert nil into an array.

Resources