As others have pointed out, the problem is that Calendar.current.component(_:from:) is, behind the scenes, introducing an autorelease object, an object that is not released until the autorelease pool is drained.
Back in the early days of reference counting Objective-C code, the common way to return a newly allocated object that would be automatically released when the caller was done with it was to return an "autorelease" object. It was an object that would only be deallocated when you yielded back to the run loop, which would drain the autorelease pool. And you could control your high-water mark on large loops that repeatedly created autorelease objects by adding your own manual autorelease pools.
Swift doesn't natively create autorelease objects, so this issue is a bit of an Objective-C anachronism that we don't generally encounter in our own Swift code. But we have to be sensitive to this whenever writing code that loops and calls Cocoa APIs that might be using autorelease objects behind the scenes, such as in this case.
Before I dive into the solution, I'm going to tweak your example to something that is guaranteed to eventually exit. For example, let's write a routine that spins until the minute associated with the current time changes (e.g. when the current minute ends and the next starts). Let's assume that previousValue contains the current minute value.
The trick is that we need to put the autoreleasepool inside the loop. In the following example, we're taking advantage of the fact that autoreleasepool is generic that returns whatever is returned inside its closure:
while autoreleasepool(invoking: { Calendar.current.component(.minute, from: Date()) }) == previousValue {
// do something
}
Note, if you find that pattern hard to read (it takes a while to get used to closures as parameters to methods), you can use a repeat-until loop, to accomplish largely the same thing:
var minute: Int!
repeat {
minute = autoreleasepool {
Calendar.current.component(.minute, from: Date())
}
// do something
} while minute == previousValue
As an aside, this process of having a loop that spins quickly like this is highly inefficient. Certainly, as you mentioned, you would never do this on the main thread (because we never want to block the main thread). But you wouldn't generally do it on any thread because spinning is so computationally intense. Sometimes you have to do it (e.g. doing some complex calculation on background thread anyway and you want it to stop at some particular time), but 9 times out of 10, it's code smell for some deeper problem in the design. Often judicious use of timers or the like can accomplish the desired effect, without the computational overhead.
It's hard to advise you about the best solution in your case, as we don't know what broader problem you're trying to solve. But simply be aware that spinning on a thread is generally inadvisable.
You ask:
Well but if I run a loop with just a date comparison im not running into the same issue. Is this because the optimiser steps in?
No, its simply because Date() simply doesn't introduce any autorelease objects to the mix like Calendar.current.component(_:from:) evidently does. (BTW, Apple's been good about slowly excising autorelease objects throughout their code base, so you're likely to discover at some future date, even this is likely to not require manual autoreleasepool.)