Objective-C: Mutating parameters - ios

Is it safe to have a method edit a parameter and not be inside a category.
For example, just having a method like this in a ViewController subclass:
-(void)editArray:(NSMutableArray*)array
{
[array removeLastObject];
}
And calling [self editArray:(someArray)]; inside the view controller where someArray is a strong atomic property. It seems to work when I test it, I just don't know if this is discouraged. I know I could easily do this in a category, I just want to know if something like this is safe.

This is safe, as long you're not modifying the reference, just the contents of the parameter object. E.g. assigning to array inside the method would not work, you'd need to pass it by reference instead. As regards style, consider how the errors are returned from many Cocoa Touch framework methods:
NSError *error = nil;
[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
Here, too, the parameter object, in this case &error, is modified inside the method. So I think you're good from the style perspective as well :)

If you change the array while it is being mutated Cocoa will throw an exception *** Collection <__NSArrayM: 0x7f96432004c0> was mutated while being enumerated.
Consider the code:
NSMutableArray* a = [[NSMutableArray alloc] initWithArray:#[#"a", #"b", #"c"]];
for (id x in a) {
[a removeLastObject];
}
In this case it would be unsafe and cause your application to crash. Abstracting the removeLastObject message into another message doesn't really change anything.
It will also not be safe if done on a background thread.

Related

Safely perform changes to NSMutableArray

What can cause an assignment or change to an NSMutableArray to crash?
I have a mutable array containing custom objects, and I consistently keep no more than the 3 latest objects in it, the rest are removed. I started calling
[myArray insertObject:newObject atIndex:0];
if (myArray.count > 3)
[myArray removeLastObject]; // Crash
But whenever I do this too fast, the last line causes an exception-less crash.
I know that you are not allowed to add or remove objects of an array while it is being enumerated, but I do not enumerate myArray anywhere unless calling count on it performs an implicit enumeration. I also tried doing this:
NSMutableArray *tmp = [myArray mutableCopy];
[tmp removeLastObject];
myArray = tmp; // Crash
But the same thing happens, it crashes on the last line. Again, this works perfectly fine when doing it slowly. The action itself is being called when a user taps a button, and when tapping it too fast, it crashes every time.
EDIT:
I should add that all of this is being run inside the cellForItemAtIndexPath method of a UICollectionView.
First, could you please post the crash message. It would be nice to know what error you are actually seeing.
I wonder what would happen if you switched to immutable arrays. Switch myArray to being an NSArray * and use the following.
myArray = [self updatedArray:myArray withObject:newObject];
Where -updatedArray:withObject: is
- (NSArray *)updatedArray:(NSArray *)array withObject:(id)object {
switch (array.count) {
case 0: return #[object];
case 1: return #[object, array[0]];
default: return #[object, array[0], array[1]];
}
}
Or better for testing
NSArray *temp = [self updatedArray:myArray withObject:newObject];
myArray = temp; // I assume the crash will be here!
If the code crashed at my comment, then deallocating myArray is causing the crash. My guess is that one of the items in the array is pointing to bad memory (a zombie or some such thing).
I can think of two possible reasons for the crash:
1) The array is being accessed/mutated from multiple threads
2) An object inside the array was over-released somewhere, causing it to be deallocated while still living inside the array.
Short answer: try profiling your app in Instruments, and select the "Zombies" instrument. When the crash happens, you just might get a zombie alert, in which case you can get a back-log of everything that led up to your memory getting stomped over.
I think the root cause is this variable is accessed from multiple threads at the same time.
You can use this when access the array
#synchronized(self) {
//Your accessed code here
}

How to initialize an NSArray of NSString?

I'm using a property of NSArray type. Then I'm trying to initialize or setting values for the NSArray. When I use shorthand assignment, I'm getting the output. But when I'm trying with long initialization style, I'm not getting the result. What should be the right way for the latter??
Here is the code snippet:
#property NSArray * moods;
//shorthand assignment
self.moods=#[#"Happy",#"Sad"];
NSLog(#"Hello %#",[self moods]);
This is working. But when I tried:
//long initialization style
[[self moods]initWithObjects:#"Happy",#"Sad", nil];
NSLog(#"Hello %#",[self moods]);
This isn't doing the same way. Suggest me something please.
The second example should be:
self.moods = [[NSArray alloc] initWithObjects:#"Happy",#"Sad", nil];
alloc must always be called before init to actually allocate the memory for the object. [self moods] is going to return nil until you assign something to self.moods.
Edit:
If you really want to avoid the assignment by property dot notation syntax for whatever reason, you can use the setter method instead:
[self setMoods: [[NSArray alloc] initWithObjects:#"Happy",#"Sad", nil]];
The answer above is completely correct. I would love just to add a comment for the sake of completeness but I can't so I'll add an extra answer to give all the options.
You can use the convenience initializers if you always get confused with the order of the alloc and init. Or if you want to have cleaner code.
self.moods = [NSArray arrayWithObjects:#"Happy",#"Sad", nil];
But the answer above it's perfect and I personally prefer the more explicit alloc init pattern.
just some alternative approach without dots... ;)
[self setMoods:#[#"Happy", #"Sad"];

How I can send a parameter by reference in a block in obj-c

Do you know how I can send a parameter by reference in a block?
My function is similar to this:
I tried with this code:
//The function
-(void)downloadObjects:(NSMutableSet**)set handler:(void(^block)(void))handler{
...
code
...
}
and this call
-(void)myFunction{
__block NSMutablesSet *objects = [NSMutableSet new];
[self downloadObjects:&objects handler:^(void(^block)(void)){
[self show:objects];
}];
}
And I receive a EXC_BAD_ACCESS error because my "objects" variable was deallocated before the use
I know that I can receive the objects by block response, but in my real case I want to receive it in this mode.
There's no need to pass your NSMutableSet to a block like this, you are only going to have memory management issues for no reason. You only need it this way if you want to return some value like an NSError.

KVO on removeAllObjects Triggers NSKeyValueChangeRemoval for each Item Separately

I'm watching an NSArray property with KVO. I've implemented KVC like in this post and I also implemented most of the KVC array accessors. To mutate it, I use mutableArrayValueForKey. It works fine, except to 2 issues:
When I call removeAllObjects, I get a NSKeyValueChangeRemoval change for each single removed item. I'd like to receive only one NSKeyValueChangeRemoval notification with all removed indexes in it.
Similarly when I call addObjectsFromArray:, I get NSKeyValueChangeInsertion for each single added item. I'd like to receive only one NSKeyValueChangeInsertion notification with all added indexes in it.
Notice that I do have implemented the KVC methods remove<Key>ItemsAtIndexes: and insert<Key>Items:atIndexes:. They are not called though.
I use the following workarounds:
- (void)removeAllObjectsWorkaroundFromArray:(NSMutableArray *)modelArray {
NSRange indexRange;
indexRange.length = modelArray.count;
indexRange.location = 0;
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:indexRange];
[modelArray removeObjectsAtIndexes:indexSet];
}
- (void)addObjectsFromArrayWorkaroundWithArray:(NSMutableArray *)modelArray arrayToAdd:(NSArray *)arrayToAdd {
NSRange indexRange;
indexRange.length = arrayToAdd.count;
indexRange.location = modelArray.count;
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:indexRange];
[modelArray insertObjects:arrayToAdd atIndexes:indexSet];
}
Is there a way to directly use removeAllObjects and addObjectsFromArray: without the need for the above workarounds?
As I am sure you are aware one cannot observe the array itself, just attributes of it. So I think your workaround is unavoidable.
That being said - I really like the way you solved this!

EXC_BAD_ACCESS error when changing views with PresentModalViewController

I'm trying to switch views in my app using this chunk of code:
self->variable1 = [[NSNumber alloc] initWithInt:0];
self->variable2 = [[NSMutableArray arrayWithCapacity:1];
self->variable3 = [[NSMutableArray arrayWithCapacity:1];
[self presentModalViewController:titleScreen animated:YES];
If I comment out all of the allocated variable lines, the code works fine. If it leave just 1 line in the code crashes with the "EXC_BAD_ACCESS" error. Why is this happening? The variables aren't being used at all, just declared for later use. I'm not getting any compile errors on the lines either. What am I doing wrong?
UPDATE:
Thank you everyone for the help. I change the way I declare my variables to #property/#synth to clean up my code, but it didn't fix the problem. After a long time of fiddling I fixed it. I changed the code from this:
self.variable1 = [[NSNumber alloc] initWithInt:0];
to this:
self.variable1 = [NSNumber alloc];
[self.variable1 initWithInt:0];
and it worked! Can someone explain why this worked and the first line didn't?
Update:
Thank you Peter Hosey for showing me my evil ways. This time I'm pretty sure it's fixed. I was storing my variable Releases in
-(void)release
I didn't realize xCode will release when it needs to. I moved all the variable releases to
-(void)Destroy
so I can release everything on MY command. Now the code works. Thanks again!
I suggest that you declare variable1, variable2, and variable3 as properties, not instance variables. Then, use self.variable1, self.variable2, and self.variable3 to access them.
The dot syntax (self.variable1, etc.) uses the memory management policy you declared on each property; the arrow syntax (self->variable1, etc.) will access the variables directly. The crash is because you created two arrays in away that doesn't leave you owning them, and then did not assign the arrays to a property that would retain them.
You may also want to upgrade your project to use ARC. Then there is no memory-management difference; assigning to the instance variables rather than the properties will not cause the object to be prematurely released, because ARC considers instance variables to be ownerships by default. You may still want to switch to using properties after you switch to ARC, but not to prevent a crash.
In response to your edit:
I change the way I declare my variables to #property/#synth to clean up my code, but it didn't fix the problem.
Then something else was wrong.
You never did say much about the problem itself. You said you got an EXC_BAD_ACCESS, but not what statement triggered the crash or on what grounds you blamed it on the code you showed.
I changed the code from this:
self.variable1 = [[NSNumber alloc] initWithInt:0];
That's the correct code, though. That's what you should be using.
to this:
self.variable1 = [NSNumber alloc];
[self.variable1 initWithInt:0];
Noooo! That code is wrong, wrong, wrong, on multiple levels.
init methods (including initWithWhatever: methods) are not guaranteed to return the same object you sent the message to. NSNumber's initWithInt: very probably doesn't.
That object creates an uninitialized NSNumber object and assigns that to the property. Then it sends initWithInt: to that object, which will return an initialized object, which can be and very probably will be a different object. Now you are holding an uninitialized object (which you will try to use later) and have dropped the initialized object on the floor.
Never, ever, ever send alloc and init(With…) in separate expressions. Always send them in the same expression. No exceptions. Otherwise, you risk holding the uninitialized object rather than the initialized object. In your case (with NSNumbers), that is almost certainly what will happen.
What you should be doing is declaring and synthesizing a strong property that owns the NSNumber object, and creating the NSNumber object in a single statement: either [[NSNumber alloc] initWithInt:] or [NSNumber numberWithInt:]. If you're not using ARC, you'll want the latter, since the property will retain the object. If you are using ARC, they're effectively equivalent.
And if you get a crash with that code, then something else is wrong, so please tell us—either in this question or in a new question—about the crash so we can help you find the true cause of it.
variable2 and variable3 are being autoreleased before you actually access them (presumably) later after presenting the modal view.
At the very least change the lines to:
self->variable2 = [[NSMutableArray arrayWithCapacity:1] retain];
self->variable3 = [[NSMutableArray arrayWithCapacity:1] retain];
or
self->variable2 = [[NSMutableArray alloc] initWithCapacity:1];
self->variable3 = [[NSMutableArray alloc] initWithCapacity:1];
variable1 should be fine.
Best would be to use #property and #synthesize so you can use dot notation:
.h
#interface MyClass : SuperClass
#property (nonatomic,retain) NSMutableArray *variable2;
#property (nonatomic,retain) NSMutableArray *variable3;
#end
.m
#implementation MyClass
#synthesize variable2,varible3;
- (void)foo {
self.variable2 = [NSMutableArray arrayWithCapacity:1];
self.variable3 = [NSMutableArray arrayWithCapacity:1];
}
#end
By default, all instance variables in objective-c have protected scope. So unless you have explicitly declared them public in your interface file as:
#interface MYClass {
#public
NSNumber *variable1;
NSMutableArray *variable2;
NSMutableArray *variable3;
}
//...
#end
then they will not be accessible using the struct dereferencing operator. This is likely the cause of those EXC_BAD_ACCESS errors.

Resources