4

I have:

NSDictionary* server;

for (server in self.servers)
{
    if (<some criterium>)
    {
        break;
    }
}

// If the criterium was never true, I want to use the last item in the
// the array. But turns out that `server` is `nil`.

The loop block never changes server.

servers is an NSMutableArray with dictionaries, a property that's not changed during the loop.

Why does server have the value nil after the loop has ended?

Was the first time I used such a variable after the loop. Without thinking too much, I assumed it would work like (in the old C days):

int i;
for (i = 0; i < n; i++)
{
    ...
}
meaning-matters
  • 21,929
  • 10
  • 82
  • 142
  • 5
    the loop has to exit somewhow, so when it runs out of things in self.servers to assign to server for the loop, nil's the logical end-value. – Marc B Jul 18 '16 at 20:17
  • 1
    Is the 2nd "server" variable a separate declaration - that is, is it scoped to the for block only (meaning the original was never assigned to)? – Ty Connell Jul 18 '16 at 20:23
  • 2
    That feature is useful if you are searching for a particular object in a collection, and `break` the loop when you find it. If the loop variable ends with the value `nil`, then the loop didn't find the desired object. – user3386109 Jul 18 '16 at 20:52
  • Possible duplicate of [Value of loop variable after "for in" loop in Objective C](http://stackoverflow.com/questions/18979464/value-of-loop-variable-after-for-in-loop-in-objective-c) – Wyetro Jul 18 '16 at 22:07
  • 1
    @MarcB Your comment does make much sense. The loop exists when it is done iterating all of the values. There is no need to assign `server` to any specific value just to end the loop. – rmaddy Jul 18 '16 at 22:59
  • @TyConnell No, the 2nd "server" is not a separate declaration. It would be if it were `for (NSDictionary *server in self.servers) {`. – rmaddy Jul 18 '16 at 23:00

3 Answers3

7

The language defines that the loop variable will be set to nil when the loop exits. The language doesn't state that the loop variable will have the last value, quite the opposite.

Behind the scenes, there is a very good reason for that. Fast iteration makes assumptions about the underlying data. For example, when you iterate over a mutable array, the array may not be modified while you iterate. Fast iteration is fast, among other things, because it doesn't retain and release the loop variable. Instead it relies on the underlying object (for example an array) to hold a reference.

But once the loop exits, that underlying object gives no guarantees anymore. The array could go away, or the array element that was last used might get deleted. So the compiler either must retain the variable, or set it to nil. Setting to nil is quicker.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • 5
    This is only true if the fast enumeration completes iterating through the entire list. If the loop breaks early, then the loop variable (declared prior to the loop) will have the last value. It is only set to `nil` if the loop runs completely. – rmaddy Jul 18 '16 at 23:05
5

The variable used in a for-loop will always be nil when loop ends (except if the loop is broken with break statement, as pointed by @rmaddy).

Another way for your loop which will avoid misunderstanding is :

for (NSDictionary* server in self.servers)
{
    //... server is not nil
}
//here server doesn't exist (out of scope)

If you want to save a value of self.servers in a var outside of the loop you'll have to do this :

NSDictionary* serverSave;

for (NSDictionary* server in self.servers)
{
    serverSave = server;
}
//here you can use serverSave, which will contain the last value of self.servers

Hope this helps.

AnthoPak
  • 4,191
  • 3
  • 23
  • 41
  • 2
    No, it will not always be `nil` after the loop. That is true only if the loop is allowed to iterate the entire list. If the loop is ended early (via `break`, for example), then the variable will reference whatever value it had when the loop exited. – rmaddy Jul 18 '16 at 23:07
-1

It's nil after the loop because Objective-C with ARC nills it (see Value of loop variable after "for in" loop in Objective C).

Thus when you get to the end of:

NSDictionary* server; // server is nil here

for (server in self.servers)
{
    ... // Does not set server to nil.
}

server will be nil.

If you are looking for a certain value you can do this:

NSDictionary *dict;

for (NSDictionary* server in self.servers)
{
    if (server == whatever you want){
        dict = server;
        break;
    }
}

Or to just get the last value:

NSDictionary *dict;

for (NSDictionary* server in self.servers)
{
    dict = server;
}
Community
  • 1
  • 1
Wyetro
  • 8,439
  • 9
  • 46
  • 64
  • 1
    In your last two examples, there is no reason to declare `server` before the loop. It's actually more confusing to do so. – rmaddy Jul 18 '16 at 23:06
  • @rmaddy, I was just keeping it the same as how it was in the question. – Wyetro Jul 18 '16 at 23:16