Есть ли в Objective-C (Objective-C ++) какие-либо условия, когда компилятор может обнаружить, что захват переменной в блоке никогда не используется, и, таким образом, принять решение не захватывать переменную в первую очередь?
Например, предположим, что у вас есть NSArray
, который содержит большое количество предметов, которые могут занять много времени для освобождения. Вам нужно получить доступ к NSArray
в главном потоке, но как только вы закончите с ним, вы захотите освободить его в фоновой очереди. Фоновый блок должен только захватить массив, а затем немедленно освободить его. Это на самом деле не имеет ничего общего с этим. Может ли компилятор обнаружить это и «ошибочно» вообще пропустить захват блока?
Пример:
// On the main thread...
NSArray *outgoingRecords = self.records;
self.records = incomingRecords;
dispatch_async(background_queue, ^{
(void)outgoingRecords;
// After this do-nothing block exits, then outgoingRecords
// should be deallocated on this background_queue.
});
Я гарантирую, что outgoingRecords
всегда будет захвачен в этом блоке и что он всегда будет освобожден на background_queue
?
Редактировать # 1
Я добавлю немного больше контекста, чтобы лучше проиллюстрировать мою проблему:
У меня есть класс Objective-C ++, который содержит очень большой std :: vector неизменяемых записей. Это может быть легко более 1 миллиона записей. Они являются базовыми структурами в векторе и доступны в главном потоке для заполнения табличного представления. В фоновом потоке другой набор записей базы данных может быть прочитан в отдельный вектор, который также может быть довольно большим.
Как только фоновое чтение произошло, я перехожу к основному потоку, чтобы поменять местами объекты Objective-C и снова заполнить таблицу.
В этот момент меня совершенно не волнует содержание более старого вектора или его родительского класса Objective-C. Там нет причудливых деструкторов или объектного графа для разрушения, но освобождение сотен мегабайт, может быть, даже гигабайт памяти не мгновенно. Так что я готов перенести его в background_queue, и там произойдет освобождение памяти. В моих тестах это работает нормально и дает мне немного больше времени в основном потоке, чтобы делать другие вещи до истечения 16 мс.
Я пытаюсь понять, могу ли я сойти с рук просто захватывая объект в «пустом» блоке или я должен выполнить какую-то операцию без операции (например, вызов count
), чтобы компилятор не мог оптимизировать это прочь как-то.
Редактировать # 2
(Первоначально я пытался сделать вопрос как можно более простым, но кажется, что он более детальный, чем этот. Основываясь на ответе Кена ниже, я добавлю другой сценарий.)
Вот еще один сценарий, который не использует dispatch_queues, но все еще использует блоки, и это та часть, которая меня действительно интересует.
id<MTLCommandBuffer> commandBuffer = ...
// A custom class that manages an MTLTexture that is backed by an IOSurface.
__block MyTextureWrapper *wrapper = ...
// Issue some Metal calls that use the texture inside the wrapper.
// Wait for the buffer to complete, then release the wrapper.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
wrapper = nil;
}];
В этом сценарии порядок выполнения гарантируется Металлом. В отличие от приведенного выше примера, в этом сценарии производительность не является проблемой. Скорее IOSurface, который поддерживает MTLTexture
, перерабатывается в CVPixelBufferPool. IOSurface распределяется между процессами, и, насколько я могу судить, MTLTexture
, по-видимому, не увеличивает useCount на поверхности. Мой класс обертки делает. Когда мой класс-обертка освобожден, значение useCount уменьшается, и затем bufferPool освобождается для переработки IOSurface.
Это все работает, как и ожидалось, но я получаю глупый код, как описано выше, просто из-за неуверенности, нужно ли мне «использовать» экземпляр оболочки в блоке, чтобы убедиться, что он захвачен или нет. Если обертка освобождается до запуска обработчика завершения, то IOSurface будет переработан, а текстура будет перезаписана.