Как уже отмечали другие, проблема заключается в том, что Calendar.current.component(_:from:)
скрытно представляет объект автоматического выпуска, объект, который не освобождается до тех пор, пока пул автоматического выпуска не будет удален.
Еще в первые дни подсчета ссылок в коде Objective-C распространенным способом возврата вновь выделенного объекта, который будет автоматически освобожден, когда вызывающий объект покончит с ним, было возвращение объекта "autorelease". Это был объект, который будет освобожден только тогда, когда вы вернетесь к циклу выполнения, который истощит пул автоматического выпуска *1005*. Кроме того, вы можете контролировать свою верхнюю отметку на больших циклах, которые неоднократно создавали объекты автоматического выпуска, добавляя свои собственные пулы автоматического выпуска.
Swift изначально не создает объекты автоматического выпуска, так что эта проблема представляет собой анахронизм Objective-C, с которым мы обычно не сталкиваемся в нашем собственном коде Swift. Но мы должны быть чувствительны к этому всякий раз, когда пишем код, который зацикливается и вызывает API-интерфейсы Cocoa, которые могут использовать объекты автоматического выпуска за кулисами, как в этом случае.
Прежде чем погрузиться в решение, я собираюсь настроить ваш пример на что-то, что гарантированно в конечном итоге завершится. Например, давайте напишем подпрограмму, которая вращается до тех пор, пока minute
, связанный с текущим временем, не изменится (например, когда текущая минута заканчивается и начинается следующая). Предположим, что previousValue
содержит текущее значение minute
.
Хитрость в том, что нам нужно поместить autoreleasepool
внутри цикла. В следующем примере мы используем тот факт, что autoreleasepool
является универсальным, который возвращает все, что возвращается внутри его замыкания:
while autoreleasepool(invoking: { Calendar.current.component(.minute, from: Date()) }) == previousValue {
// do something
}
Обратите внимание: если вам кажется, что этот шаблон труден для чтения (требуется некоторое время, чтобы привыкнуть к замыканиям в качестве параметров методов), вы можете использовать цикл repeat
- until
, чтобы выполнить в основном то же самое:
var minute: Int!
repeat {
minute = autoreleasepool {
Calendar.current.component(.minute, from: Date())
}
// do something
} while minute == previousValue
Кроме того, этот процесс с циклом, который вращается так быстро, очень неэффективен. Конечно, как вы упомянули, вы никогда не сделаете этого в главном потоке (потому что мы никогда не хотим блокировать основной поток). Но вы, как правило, не будете делать это на любом потоке, потому что вращение требует больших вычислительных ресурсов. Иногда вам приходится делать это (например, делать сложные вычисления в фоновом потоке, и вы хотите, чтобы он останавливался в определенное время), но 9 раз из 10 это запах кода для более глубокой проблемы в дизайне. Часто разумное использование таймеров или тому подобного может достичь желаемого эффекта без вычислительных затрат.
Трудно посоветовать вам лучшее решение в вашем случае, так как мы не знаем, какую более широкую проблему вы пытаетесь решить. Но просто имейте в виду, что вращение на нити обычно нежелательно.
Вы спрашиваете:
Хорошо, но если я запускаю цикл с простым сравнением дат, я не сталкиваюсь с той же проблемой. Это потому, что в работу входит оптимизатор?
Нет, просто потому, что Date () просто не вводит в микс объекты авто-релиза, как, очевидно, делает Calendar.current.component(_:from:)
. (Кстати, Apple хорошо справлялась с медленным удалением объектов автоматического высвобождения по всей их базе кода, поэтому вы, вероятно, обнаружите это в будущем, даже если это не потребует автоматического повторного запуска вручную.)