Need explanation on block behaviour in for loop [duplicate] - objective-c

This question already has an answer here:
Blocks and stack
(1 answer)
Closed 8 years ago.
Please take a look at this piece of code:
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 10; i++)
{
void (^b)() = ^{printf("%d\n", i);};
[array addObject:b];
}
for (id obj in array)
{
void(^b)() = obj;
b();
}
[array removeAllObjects];
I expected this code to output 0, 1, 2 and so on but it prints 9 always. But why? Doesn't it capture i on every loop iteration? Why the last value is always captured? But what is more confusing for me is that if i change this line:
void (^b)() = ^{printf("%d\n", i);};
to
void (^b)() = [^{printf("%d\n", i);} copy];
then it starts printing 0, 1, 2 and so on. Can anybody please explain why it works this way?

This is not a problem with what the block captures, but rather with what block gets stored in the array. If you print addresses of the blocks after the first loop, you should see identical addresses being printed:
for (id obj in array)
{
printf("%p\n", (void*)obj);
}
This is because all ten blocks are created on the stack in a loop, and are placed at the same address. Once the loop is over, the block created inside it is out of scope. Referencing it is undefined behavior. However, now that you stored the address of the block, you have a way to reference it (illegally).
Since the last block that was created in your loop has captured the last value of i (which is nine) all invocations of your block from your second loop would invoke the same block that prints nine.
This behavior changes if you copy the block, because now it is completely legal to reference it outside the scope where it has been created. Now all your blocks have different addresses, and so each one prints its own different number.

Related

Rules for variable capture by block in objective-C

What are the semantics of capturing a variable by a block in objective-C?
#import <Foundation/Foundation.h>
#include <stdio.h>
int main()
{
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; ++i) {
int j = i;
[arr addObject:^(void) {printf("%d %d\n", i, j); }];
}
for (void (^blk)(void) in arr) {
blk();
}
}
I was expecing this to print something like:
100 0
100 1
...
100 99
Instead, it prints:
99 99
99 99
...
99 99
How is it even possible that it's interpreting j as equal to 99 ? j isn't even alive outside the for loop.
Because you're not using ARC! Without it, your block isn't being copied. You're just getting lucky and running the very last block every single time.
The reason you're seeing 99 99 many times is simply due to undefined behaviour.
Let's take the first for-loop:
for (int i = 0; i < 100; ++i) {
int j = i;
dispatch_block_t block = ^(void) {printf("%d %d\n", i, j); };
[arr addObject:block];
}
[I've pulled out the block for clarity.]
Inside this for-loop, the block is created. It is created on the stack and never moved to the heap because there is no copy of the block to do so.
Each time around the for-loop, it's extremely likely (well, certain really) that the same stack space is used for the block. And then the address of the block (which is on the stack) is added to arr. The same address each time. But it's a new implementation of the block each time.
Once out of the first for-loop, arr contains the same value 100 times. And that value points to the last created block, which is still on the stack. But it's pointing to a block on the stack that can no longer be accessed safely because it's out of scope.
In the case of this example however, the stack space occupied by the block by sheer luck (OK, simple code) hasn't been reused. So when you go and use the block, it "works".
The correct solution is to copy the block when it is added to the array. Either by calling copy or letting ARC do that for you. That way, the block is copied to the heap and you have a reference counted block that will live as long as needed by the array and the scope in which it is created.
If you want to learn more about how blocks work and understand this answer more deeply, then I suggest my explanations here:
http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/
http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/
http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/

Adding a NSDictionary to a NSArray duplicates the first NSDictionary every time

So I pulled some JSON data from a web service which is stored in an NSArray called _infoFromJSON. Each array element in _infoFromJSON essentially has a dictionary of key/value pairs. The goal is to add them to myVehicleObject which is an NSMutableArray
for (NSDictionary* myDictionary in _infoFromJSON) {
myVehicleObject *vehicleInMembersProfile;
vehicleInMembersProfile = [[myVehicleObject alloc] init];
vehicleInMembersProfile.make = [[_infoFromJSON objectAtIndex:carCount] objectForKey:#"make"];
vehicleInMembersProfile.carName = [[_infoFromJSON objectAtIndex:carCount] objectForKey:#"nickname"];
vehicleInMembersProfile.year = [[_infoFromJSON objectAtIndex:carCount] objectForKey:#"year"];
carCount ++;
[self.myVehicleObject addObject:vehicleInMembersProfile] ;
};
With the above code I sort of achieved it, however it keeps adding the same 1st dictionary to myVehicleObject, so it inserts the same NSDictionary 4 times In the past I have used this:
[self.myVehicleObject addObject:[vehicleInMembersProfile copy]] ;
When I do it it throws the following exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[myVehicleObject copyWithZone:]: unrecognized selector sent to instance 0xab4e130'
What am I doing wrong here? Thanks!
Update Requested Sample JSON:
{
color = SILVER;
engine = "";
id = "CF270B81-3821-4585-8C90-7089D7A8654E";
imageUrl = "https://www.someprotectedurl.com/somefile.png";
licensePlate = "ABC-123";
make = Honda;
model = "CR-V";
nickname = "My CR-V";
vin = "XX551234687687687654";
year = 2009;
}
This may or may not be related to the problem, but your forin loop is completely broken. Whether or not your incorrect usage is the actual cause of the problem, this is something you should definitely fix as it can do nothing but cause problems.
If you want to use indexOfObject: to grab an object at an index of the array you're iterating through, you should use a regular for loop:
for (int carCount=0; index < [_infoFromJSON count]; ++carCount) {
// loop body remains identical to what you have...
// except remove carCount++;
}
But for what you're doing, a forin loop is indeed better, and forin loops can be faster since they can be handled in batches. But if you're using a forin loop, use the object you're defining in the loop declaration:
for(NSDictionary* myDictionary in _infoFromJSON) {
// myDictionary is a reference to an object in _infoFromJSON,
//for whatever given index it is currently working on
myVehicleObject *vehicleInMembersProfile = [[myVehicleObject alloc] init];
vehicleInMembersProfile.make = myDictionary[#"make"];
vehicleInMembersProfile.carName = myDictionary[#"nickname"];
vehicleInMembersProfile.year = myDictionary[#"year"];
[self.myVehicleObject addObject:vehicleInMembersProfile];
}
The error is that your class does not implement NSCopying.
However, there isn't any need to copy the object here. You're creating a new object each time through the loop. If you insert a copy, you're just pointlessly throwing away the original each time. The more likely cause if you're seeing odd loop behavior is that your loop is mixing up different kinds of enumeration, as pointed out by nhgrif. Instead of accessing [_infoFromJSON objectAtIndex:carCount], just use myDictionary.
I would probably use a for loop rather than a foreach since you need the array index, I would code it like this
NSMutableArray *vehiclesArray = [[NSMutableArray alloc]init];
for (int i = 0; i < [_infoFromJSON count]; i++)
{
//create vehicle
myVehicleObject *vehicleInMembersProfile = [[myVehicleObject alloc] init];
vehicleInMembersProfile.make = _infoFromJSON[i][#"make"];
vehicleInMembersProfile.carName = _infoFromJSON[i][#"nickname"];
vehicleInMembersProfile.year = _infoFromJSON[i][#"year"];
//finally add the vehicle to the array
[vehiclesArray addObject:vehicleInMembersProfile] ;
}
I wasn't sure what myVehicleObject was , and why is it the same type you are adding to itself, so I changed it to an array. But you get the point.

How can I refer to the current block object within my block function?

I know that a block descriptor is passed on the stack to a block function when it is invoked. Is there a variable name I can use to refer to this in my code (like self or _cmd for methods)
(^{
// how can I access the block descriptor here?
})();
edit
I actually want the block object, not the block descriptor...
In short, you can't. At least not directly (there is nothing akin to self within a block -- we thought long and hard about that, but couldn't come up with something both elegant nor enough need for it in light of the following pattern to justify adding such syntax).
If you want to refer to the block, you need to do something like:
__block void(^strawberryFields)();
strawberryFields = ^{ strawberryFields(); };
strawberryFields();
Note that the above will run forever. Note also that you might want to copy that block upon assignment if you plan on using the block later.
Consider:
NSMutableArray *array = [NSMutableArray array];
int i;
for(i = 0; i<5; i++) {
[array addObject:[^{ return i*i; } copy]];
}
You'll end up with an array with 5 blocks, each capturing a different value of i.
It may help to create a method to initialize each block for you. Here's a quick test that demonstrates each block has its own variable:
-(void (^)(void))intAddingBlock:(NSString *)name {
__block int intForThisBlock = 0;
return ^{
NSLog(#"%# before: %d", name, intForThisBlock);
intForThisBlock += 5;
NSLog(#"%# after: %d", name, intForThisBlock);
};
}
-(void)testTheBlock {
void(^block1)(void) = [self intAddingBlock:#"block 1"];
void(^block2)(void) = [self intAddingBlock:#"block 2"];
block1();
block1();
block2();
block1();
block2();
}
Output:
block 1 before: 0
block 1 after: 5
block 1 before: 5
block 1 after: 10
block 2 before: 0
block 2 after: 5
block 1 before: 10
block 1 after: 15
block 2 before: 5
block 2 after: 10

Weird compiler optimization/behavior regarding a "for" loop without a body in Objective-C

I have the following C array of NSString *:
static NSString *const OrderByValueNames[] = {#"None",#"Added",#"Views",#"Rating",#"ABC",#"Meta"};
Now, I want to check the length of this array at runtime so I wrote the following method:
NSInteger LengthOfArray(NSString *const array[])
{
NSInteger length = 0;
// For Loop Without a body!
for (length = 0; array[length] != nil; length++);
return length;
}
Now, when I run this code at debug configuration, everything is fine and the function returns the right result.
BUT as soon as I switch to release configuration and run it again, the program freezes at the for loop. After 10 seconds of the loop being executed, iOS kills the app for not responding. Weird.
Now, if I add body to the loop, like that:
for (length = 0; array[length] != nil; length++)
{
NSLog(#"%d",length);
}
Then it's working fine even in release mode.
Giving the loop an empty body, like that:
for (length = 0; array[length] != nil; length++){}
Still freezes in release mode.
My guess is that there is a compiler optimization when running in release mode, but what exactly?!
Any ideas?
C-style arrays are not nil-terminated by default. If you want to check the length this way, you need to add the terminator yourself.
static NSString *const OrderByValueNames[] =
{#"None",#"Added",#"Views",#"Rating",#"ABC",#"Meta",nil};
A much better way to find the length is simply this:
length = sizeof(OrderByValueNames) / sizeof(OrderByValueNames[0]);
(Note that this trick doesn't work if you pass the array into a function, since it then degenerates into a pointer.)
Your OrderByValueNames array doesn't contain a nil element. No wonder you can't find one! Walking off the end of the array like that will result in undefined behaviour. If you want to mark the end of the array with nil, you'll have to add it manually.
By the way, if you'd like to know the length of a fixed size C array, you can do this:
length = sizeof(OrderByValueNames)/sizeof(OrderByValueNames[0]);
This doesn't work for function parameters, though, as those are just pointers!

Blocks, loops and local variables

Consider the following code fragment:
for(/* some condition */) {
int x = rand();
[array addObject:^(){
NSLog(#"%d", x);
}]
}
for(void (^block)() in array) {
block();
}
Now I would expect this code snippet to print out all values assigned to x in that for loop; however it seems that all blocks share the same 'x' variable (presumably the last one).
Any idea why this is so and how I could fix the code to have each block contain the variable 'x' as it was at the time the block was defined?
The documentation specifically says not to do this. The reason is that blocks are allocated on the stack, which means they can go out of scope. For the same reason you can't access the variable x outside of the first for loop, you also shouldn't use that block. x has gone out of scope, along with the block itself, and could contain any value.
To get around this, you can take a copy of the block like so:
for(/* some condition */) {
int x = rand();
void(^logBlock)() = ^() { NSLog(#"%d", x); }
[array addObject:[[logBlock copy] autorelease]];
}
This moves the block onto the heap, and should fix your problem.