Skip to content

Instantly share code, notes, and snippets.

@bjhomer
Created April 27, 2012 16:09
Show Gist options
  • Select an option

  • Save bjhomer/2510467 to your computer and use it in GitHub Desktop.

Select an option

Save bjhomer/2510467 to your computer and use it in GitHub Desktop.

Javascript closure capture for Objective-C developers

In Javascript, you'll often see things like this:

for (var i=0; i<5; ++i) {
  var someString = strings[i];

  button.onclick = (function (str) {
    return function (event) {
      alert(str);
    };
  })(someString);
}

You may be wondering why it couldn't just look like this:

for (var i=0; i<5; ++i) {
  var someString = strings[i];

  button.onclick = function (event) {
      alert(someString);
  };
}

There are two things at play here. The first is that for loops don't create a new lexical scope in Javascript. Only functions create scope. That is, this:

for (var i=0; i<5; ++i) {
  var someString = strings[i];
}

is exactly equivalent to this:

var someString;
for (var i=0; i<5; ++i) {
  someString = strings[i];
}

Note that the same variable is reused in every iteration of the loop. At the end of the loop, someString will have the value assigned in the last iteration.

The second thing going on is that when Javascript captures a variable in a function, it is captured by reference, not by copy. If you're familiar with Objective-C blocks, you could imagine that every variable you capture is __block-qualified. If you create a bunch of anonymous functions (or closures, or blocks) inside a loop, any variable they capture will always have the value as of the last iteration of the loop.

In Objective-C, it would look something like this. (Remember, every variable we capture in a block is going to be __block-qualified, to mimic Javascript's behavior.)

id reader = // some object
__block i = 0;
for (i=0; i<5; ++i) {
   reader.onload = ^(NSData *fileData) {
     NSLog(@"i: %@", i); // Always logs '5'
     NSLog(@"the data: %@", fileData);
   };
   reader.readFile(someFile);
}

To solve this, we need to pass things that we want to remain immutable (such as i) as arguments to a function. We can't just add another parameter to our onload block, because that's called by reader, which we'll assume we do not control. So instead, we introduce another block:

id reader = // some object
__block i = 0;
for (i=0; i<5; ++i) {

   reader.onload = ^(int param_i) {
       __block local_i = param_i; // Imitating JS here by making all captured variables __block

       return ^(NSData *fileData){
           NSLog(@"i: %@", local_i);
           NSLog(@"theData: %@", fileData);
       };
   }(i);
   // Note that we immediately invoke the block. This returns another block, which has captured the value of
   // `i` at the time we invoked our outer block. That returned block is the one passed to 'reader.onload'.

   reader.readFile(someFile);
}

Of course, in Objective-C, you'd never do that. But in Javascript, it's the easiest way to create a non-changing copy of a captured variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment