Хранение блоков в массиве

В Objective-C я знаю, что блоки считаются объектами, поэтому мне было интересно, можно ли хранить их в массиве. Возникает вопрос, являются ли блоки объектами первого класса или они просто рассматриваются как объекты для передачи их между объектами? Если они являются объектами первого класса, то разве они не должны храниться в массивах?


person noizetoys    schedule 03.11.2011    source источник


Ответы (2)


EDIT: не вдаваясь в подробности, в ARC теперь вы можете добавлять блоки в коллекции, как и любой другой объект (см. обсуждение).

Ниже я оставил исходный ответ нетронутым, так как он содержит некоторые интересные технические детали.


Возникает вопрос, являются ли блоки объектами первого класса или они просто рассматриваются как объекты для передачи их между объектами? Если они являются объектами первого класса, то разве они не должны храниться в массивах?

Блоки — это объекты Objective-C, которые во многом ведут себя так же, как и любой другой объект NSObject, с несколькими ключевыми отличиями:

  • Блоки всегда генерируются компилятором. Они эффективно «распределяются/инициируются» во время выполнения, когда выполнение проходит через объявление блоков.

  • Блоки изначально создаются в стеке. Block_copy() или метод copy должен использоваться для перемещения блока в кучу, если блок должен пережить текущую область (см. пункт ARC ниже).

  • Блоки на самом деле не имеют вызываемого API, кроме управления памятью.

  • Чтобы поместить блок в коллекцию, его сначала нужно скопировать. Всегда. В том числе в рамках ARC. (См. комментарии.) Если вы этого не сделаете, существует риск того, что блок, выделенный в стеке, будет autoreleased, и ваше приложение впоследствии выйдет из строя.

  • Копирование блока на основе стека также скопирует все захваченное состояние. Если вы делаете несколько копий блока, более эффективно скопировать его один раз, а затем копировать копию (поскольку копирование копии просто увеличивает количество сохранений, поскольку блоки неизменяемы).

  • В ARC возврат блока из метода или функции «просто работает»; он будет автоматически скопирован в кучу, а возврат будет фактически блоком с автоматическим освобождением (компилятор может оптимизировать автоматическое освобождение при определенных обстоятельствах). Даже с ARC вам все равно нужно скопировать блок, прежде чем вставлять его в коллекцию.

Я написал несколько сообщений в блоге, в которых содержится введение в блоки и некоторые советы и рекомендации. Вы можете найти их интересными.

И да, добавлять их в словари весьма полезно. Я написал пару фрагментов кода, в которых я помещал блоки в словари в качестве обработчиков команд, где ключом было имя команды. Очень кстати.

person bbum    schedule 03.11.2011
comment
При использовании ARC блок больше не нужно копировать при добавлении в коллекцию! Apple сообщила об этом в своем Руководстве по переходу на ARC, но это руководство довольно устарело. Пожалуйста, проверьте blog.parse.com/2013/02/ 05/objective-c-blocks-quiz — особенно ответы на примеры B и C, а также Заключение в конце. Необходимость копирования блоков даже в ARC возникла из-за ошибки компилятора в clang (которая была давно исправлена) и была просто обходным путем, поскольку Apple не могла дождаться, пока эта ошибка будет исправлена, прежде чем выпустить следующую версию Xcode. - person Mecki; 23.08.2013
comment
Спасибо @Mecki. Мне нужно вернуться и отредактировать все мои ответы о блоках. :) - person bbum; 08.09.2013
comment
Этот ответ @bbum (человека, который действительно знает) всех людей действительно указывает на отсутствие окончательной и актуальной информации о блоках, ARC и циклах сохранения. - person zaph; 14.09.2013
comment
@Zaph Полная информация содержится в документации llvm. Я подозреваю, но еще не проверил, что документация разработчиков iOS7 тоже будет обновлена ​​(по крайней мере, я надеюсь, что они есть). - person bbum; 14.09.2013
comment
Я только что проверил то, что сказал Меки, в последней версии llvm, поставляемой с Xcode 5 (Apple LLVM 5.0). Бага больше нет, поэтому блоки можно спокойно хранить в контейнерах без явного копирования под ARC. - person Gabriele Petronella; 07.10.2013
comment
Вот ответ, связанный с этой конкретной проблемой: stackoverflow.com/a/17385402/846273 - person Gabriele Petronella; 08.10.2013
comment
@GabrielePetronella: никакие тесты не могут показать, что код C правильный, потому что это может быть неопределенное поведение. - person newacct; 13.11.2013
comment
@newacct конечно, но это конкретное поведение было признано ошибкой и исправлено. Тест может быть бессмысленным, но в этом случае они подтверждают то, что указано в спецификации clang. - person Gabriele Petronella; 13.11.2013
comment
@GabrielePetronella: Но нигде в спецификации ARC не говорится, что литерал блока, переданный непосредственно в качестве аргумента addObject: (например), копируется. - person newacct; 13.11.2013
comment
@newacct, ты смотришь на это с неправильной точки зрения. NSArray содержит строгую ссылку на свои элементы, когда они добавляются в коллекцию. Это означает, что ARC вставит retain, чтобы соответствовать семантике строгой ссылки на элементы. В случае блочных указателей он вставит Block_Copy в соответствии со спецификацией clang. - person Gabriele Petronella; 13.11.2013
comment
@GabrielePetronella: То, что NSArray делает с элементом, который вы ему даете, находится в его внутренней реализации. Foundation компилируется Apple, и вы не компилируете этот код самостоятельно; поэтому, используете ли вы ARC или MRC в своей программе, не имеет значения. Даже если мы рассмотрим, как Foundation компилируется Apple, независимо от того, использовали ли они ARC или MRC для компиляции кода NSArray, он не будет выполнять копирование в соответствии со спецификацией ARC, потому что элемент будет иметь тип id, т. е. универсальный указатель объекта. тип, и спецификация ARC не преобразует сохранение в копию для значения этого типа. - person newacct; 13.11.2013
comment
Те же соображения применимы к любому объекту. В MRC, когда вы сохраняете объект в массиве, вы можете освободить его и ожидать, что объект по-прежнему будет доступен из массива, не так ли? Опять же, это была ошибка компилятора, исправленная несколько месяцев назад, о чем прямо заявил один из сопровождающих LLVM: lists.apple.com/archives/objc-language/2012/Feb/msg00071.html - person Gabriele Petronella; 13.11.2013
comment
@GabrielePetronella: Когда вы отвечаете на комментарий, вам нужно поставить @ и имя человека, которому вы отвечаете. В противном случае ваш ответ никто не увидит. Этот вопрос не о LLVM; этот вопрос касается АРК. Они не сказали, что они подразумевают под ошибкой. Они, вероятно, имеют в виду, что это не соответствует тому, что они хотят в LLVM. Если бы они сказали, что это ошибка, потому что она несовместима со спецификацией ARC (чего они не сказали), то они были бы неправы, потому что она согласуется со спецификацией ARC. - person newacct; 18.11.2013
comment
@GabrielePetronella: Чего вы, кажется, не понимаете, так это того, что ARC — это функция времени компиляции. Он работает, переписывая ваш код, чтобы написать вызовы для сохранения и освобождения, и в случае, когда тип выражения времени компиляции имеет тип блочного указателя, он записывает вызов для копирования вместо сохранения. Вот и все. Он не может волшебным образом изменить вызов сохранения на вызов копирования во время выполнения. Это не то, как работает ARC. Это не то, что говорит спецификация ARC. - person newacct; 18.11.2013
comment
@GabrielePetronella: NSArray не может отправить сообщение copy элементу во время выполнения, потому что код NSArray работает с id и делает то же самое (отправляет одни и те же сообщения) для всех объектов. Сообщение, которое он отправляет добавленным объектам, — retain. Это сообщение, которое вызывается во время выполнения. Период. Сообщение retain, вызванное в блоке стека, не возвращает его копию. ARC не имеет значения, потому что ARC выполняется во время компиляции. - person newacct; 18.11.2013
comment
@bbum Вы упомянули, что копирование блока на основе стека также скопирует все захваченное состояние. Если вы делаете несколько копий блока, более эффективно скопировать его один раз, а затем копировать копию (поскольку копирование копии просто увеличивает количество сохранений, поскольку блоки неизменяемы). Это потому, что если вы делаете несколько копий с оригинала, который находится в стеке, новую копию в куче необходимо создавать каждый раз, но если вы копируете копию, она уже находится в куче и, следовательно, может сработать счетчик. - person SayeedHussain; 07.03.2016

Да, блоки действительно являются объектами, и вы можете поместить их в массивы:

NSMutableArray *arr = [NSMutableArray new];
[arr addObject:^(){NSLog(@"my block");}];
void (^ myblock)() = [arr objectAtIndex:0];
myblock();

это поместит «мой блок» в консоль.

person Eduardo Scoz    schedule 03.11.2011
comment
Спасибо за быстрый ответ. Я предполагаю, что то же самое справедливо и для добавления блоков в любой тип коллекции, например, в словарь. Это может иметь некоторые интересные последствия. - person noizetoys; 03.11.2011
comment
Правильный. Я использую это все время в C # (блоки внутри массивов). Если у вас есть сотни элементов для выбора, я думаю, что это намного чище, чем операторы switch, плюс его динамика. - person Eduardo Scoz; 03.11.2011
comment
Блоки должны быть скопированы перед добавлением в коллекцию. Всегда. Даже под АРК. - person bbum; 03.11.2011
comment
Этот ответ фантастический. Спасибо. - person Duck; 01.11.2012
comment
хороший код и определение: +(void)reset:(void(^)())block;[arr addObject:[block copy]]; - person zszen; 13.11.2012
comment
@bbum: блоки больше не нужно копировать в ARC, даже при добавлении в коллекцию. Apple сказала об этом в своем руководстве по переходу, но это было сделано только для того, чтобы обойти ошибку компилятора clang, которая была исправлена ​​много лет назад. См. blog.parse.com/2013/02/05/ Objective-c-blocks-quiz — особенно ответы на Пример B, Пример C и Заключение в конце (которое также ссылки на пост сопровождающего LLVM, объясняющий ситуацию). - person Mecki; 23.08.2013
comment
@Mecki приятно это слышать. Может быть, у вас есть какая-нибудь информация, исправлена ​​ли уже эта ошибка в LLVM от Apple, или хотя бы отчет об ошибке? Я не могу найти много информации. - person Eduardo Scoz; 23.08.2013
comment
@EduardoScoz Я не знаю точный номер выпуска, но проблема была исправлена ​​в LLVM более года назад, и всякий раз, когда Apple выпускает новую версию Xcode, их LLVM довольно близок к переднему краю (в Xcode 4.6 наверняка есть исправление, даже Xcode 4.5 может иметь это). Я могу подтвердить, что добавление блока стека в массив отлично работает с Xcode 4.6 даже без копии (clang-425.0.28, на основе LLVM 3.2svn) - person Mecki; 26.08.2013
comment
Спасибо @Mecki, я удалил копию из примера на случай, если люди сочтут это полезным. Первоначально, когда я опубликовал ответ, у меня не было вызова для копирования, поэтому я думаю, что опередил время! :) - person Eduardo Scoz; 27.08.2013
comment
@Mecki: это совершенно неправильно. В C просто потому, что что-то не падает, это не значит, что это правильно — это может быть неопределенное поведение. Ничто в спецификации ARC не гарантирует, что в этом случае блок будет скопирован. Тот факт, что эта версия этого компилятора ARC вставляет копию, не означает, что вы можете на нее положиться. - person newacct; 27.08.2013
comment
@newacct Если разработчик LLVM говорит, что тот факт, что массив должен был быть скопирован, был ошибкой и что правильное поведение для ARC состоит в том, чтобы скопировать массив для вас в этом случае, то спорить не о чем. LLVM имеет собственную спецификацию ARC, и никто, кроме разработчиков LLVM, не решает, что является правильным поведением ARC в clang, а что нет. Apple может утверждать обратное, но дело в том, что у Apple нет собственного компилятора, они используют clang, и clang работает так, как хочет LLVM.org, а не так, как хочет Apple (у Apple нет собственной ветки clang). - person Mecki; 24.09.2013
comment
@Mecki: Спецификация ARC (от LLVM) находится прямо здесь: clang.llvm.org/ docs/AutomaticReferenceCounting.html Любой может прочитать его и решить, что является правильным, а что нет. - person newacct; 26.09.2013
comment
@newacct Да, это так. И эта спецификация говорит, что я прав. NSArrays делают retain свои объекты внутри (они всегда так делали и делают до сих пор), и если вы сейчас прочитаете последний абзац главы 7.5, там говорится, что каждое сохранение типа блочного указателя должно иметь тот же эффект, что и Block_copy(). Поэтому, когда NSArray сохраняет объект, который на самом деле является указателем блока, это сохранение должно вести себя как Block_copy(). Таким образом, нет необходимости явно копировать блоки перед их передачей в коллекцию, подобную NSArray, и если это требовалось в прошлом, это была ошибка в компиляторе. - person Mecki; 09.11.2013
comment
@Mecki: Нет. Ты ошибаешься. NSArray сохраняет элементы, добавленные к нему во внутреннем коде, которые вы не видите. В этом внутреннем коде, даже если предположить, что он написан с использованием ARC, переменная не будет иметь тип блочного указателя, потому что все методы NSArray принимают тип id. На самом деле, ARC/MRC — это вещь времени компиляции, и она не имеет отношения к уже скомпилированному коду, такому как классы Foundation. - person newacct; 09.11.2013
comment
@newacct Ладно, что мне сделать, чтобы заставить тебя замолчать? Соберите сюда группу разработчиков clang, которые будут кричать вам в лицо? Я так и сделаю, если вы действительно настаиваете на этом. Но, честно говоря, я считаю, что даже если все разработчики clang подпишут бумажку о том, что я прав, вы все равно не поверите, потому что по какой-то совсем уж больной причине вы просто не хотите в это верить. Ладно, делайте как хотите, через 20 лет никто не вспомнит ни ваши высказывания здесь, ни это обсуждение, и все будут делать так, как я говорю, потому что это правильный способ, и сегодня он отлично работает. - person Mecki; 11.11.2013
comment
@newsact И не имеет значения, был ли NSArray построен с использованием ARC или нет. Метод retain блока стека, созданного в рамках ARC, указывает на Block_copy внутри, поэтому вызов функции сохранения для такого блока (из кода ARC или из кода, отличного от ARC) будет всегда копировать блок (после этого указатель изменится на Block_retain). ). Вы можете легко убедиться в этом с помощью отладчика. См. здесь: pastebin.com/rcCncCy4 Вызов retain для блока приводит к копированию блока, но только потому, что сам блок был создан в коде ARC. - person Mecki; 11.11.2013
comment
@Mecki: Что за больная у тебя проблема, что ты нападаешь на меня лично, когда не можешь рассуждать о языке, как нормальный человек? Нигде в официальной спецификации ARC это не поддерживает то, что вы утверждаете. Спецификация — вот что важно. Все остальное — неопределенное поведение. Как я уже объяснил вам, спецификация ARC говорит, что, когда в противном случае требуется сохранение значения типа блочного указателя, вместо этого выполняется копирование. Нигде не сказано, что сохранение указателя объекта, который указывает на блок, копирует его. - person newacct; 12.11.2013
comment
@Mecki: Ваш код даже не проверяет то, что вы утверждаете, потому что, назначая блок someBlock, переменной __strong типа указателя блока, вы вызываете копию там, в коде ARC. - person newacct; 12.11.2013
comment
@newacct Какие проблемы со здоровьем есть у ВАС? Я связался с цитатой непосредственно от одного из разработчиков, который сказал, что явный вызов копирования блоков никогда не должен требоваться в коде ARC, это предполагаемое поведение, и оно всегда было, и вы все еще спорите. По сути, вы говорите, что люди, которые пишут компилятор, а также написали все спецификации, на которые вы постоянно ссылаетесь, понятия не имеют, что они делают или о чем говорят. Покажите мне хотя бы один фрагмент кода, в котором отсутствие вызова Block_copy приводит к сбою или неожиданному поведению. Бьюсь об заклад, вы не можете, потому что его больше не существует. - person Mecki; 12.11.2013
comment
@newacct Просто для протокола (все заархивировано) и для всех, кто столкнется с этим в будущем: Джон МакКолл сказал, что вам не нужен Block_copy в ARC. См. bit.ly/1br5J7Z. Если вы не знаете, кто такой Джон МакКолл, см. bit.ly/19gnnGD и найдите его имя. Я бы поверил Джону МакКоллу примерно в миллион раз больше, чем вам скажет любой пользователь SO. С включенным ARC компилятор всегда должен знать, когда ему нужно скопировать блоки в кучу (любой отказ сделать это, когда это необходимо, будет ошибкой), нет необходимости когда-либо вызывать Block_copy; это может даже нарушить оптимизацию компилятора. - person Mecki; 12.11.2013
comment
Ребята отличная ветка. На данный момент я соглашусь с точкой зрения @Mecki, что вам не нужно копировать блоки. Я делал это некоторое время, и это работает правильно, и документация (clang. llvm.org/docs/AutomaticReferenceCounting.html#blocks) дает понять, что это предполагаемое поведение. Может он недавно обновлялся? - person Eduardo Scoz; 12.11.2013
comment
@EduardoScoz: спецификация ARC, на которую вы ссылаетесь, ясно говорит, что в этом случае блок нельзя копировать. Оптимизатор может удалить такие копии, когда увидит, что результат используется только как аргумент вызова. Я не понимаю, как может быть какая-то неопределенность по этому поводу. - person newacct; 12.11.2013
comment
@Mecki: это языки программирования. У нас есть технические характеристики. Требования некоторых лиц не могут иметь приоритет над тем, что написано в спецификации. Снова и снова я представил то, что написано в спецификации и как это не гарантирует, что блок скопирован здесь. И вы не указали ни на одно место в спецификации в поддержку своих утверждений; и вместо этого продолжайте приносить другие не относящиеся к делу вопросы. - person newacct; 13.11.2013
comment
@newacct Деталь, которую вы, кажется, упускаете, заключается в том, что во время компиляции ARC действительно знает тип объекта, добавляемого в массив, и может правильно выдать Block_copy вместо сохранения, если он является блоком. Тот факт, что метод объявлен как принимающий id, этого не меняет. Если вы этого не понимаете, я не уверен, что смогу вам помочь. - person jbg; 09.07.2014
comment
@newacct А что касается спецификации. Часть спецификации, которую вы цитируете снова и снова, просто говорит, что блок не будет скопирован во время передачи в качестве параметра [NSMutableArray addObject:] или аналогичному. Однако позже сам NSArray будет хранить этот блок со строгой семантикой (как и со всеми хранящимися в нем объектами), а спецификация ARC требует, чтобы это было эквивалентно Block_copy. Раньше LLVM не следовал этой части спецификации, которая была ошибкой, а теперь следует. - person jbg; 09.07.2014
comment
@JasperBryant-Greene: и может правильно выдать Block_copy вместо сохранения, если это блок. Вызывающий объект не сохраняет сохранение, участвующее в передаче аргумента функции. - person newacct; 09.07.2014
comment
@JasperBryant-Greene: спецификация ARC требует, чтобы это было эквивалентно Block_copy ARC время компиляции. испускание выполняется компилятором. Спецификация ARC требует, чтобы код, скомпилированный под ARC, где хранится значение типа указатель блока во время компиляции, был изменен компилятором на Block_copy. NSArray не скомпилирован вами. Вы не знаете, компилируется ли он в ARC, и в любом случае это не имеет значения, потому что в любом случае это будет то же самое - внутри реализации метода NSArray нет переменной типа времени компиляции указателя блока. - person newacct; 09.07.2014
comment
@JasperBryant-Greene: ARC не изменяет вызов во время выполнения метода retain, чтобы иметь семантику копирования. Это соответствует спецификации ARC (в которой говорится об изменении сохранения для копирования семантики во время компиляции) и может быть легко проверено на любой версии компилятора. Трудно представить, как это могло бы работать в любом случае, потому что проект может смешивать код ARC и не-ARC, так как же внутренний метод NSArray (скомпилированный Apple) должен знать, используется ли он из ARC или нет? - person newacct; 09.07.2014
comment
Я не знаю, чем еще вам помочь. Вы утверждаете, что вещи не работают так, как они совершенно ясно и проверяемо работают. Я предлагаю вам написать базовый код и протестировать его самостоятельно, как это уже сделал я, и убедиться, что все работает так, как описано в документации. - person jbg; 10.07.2014
comment
@JasperBryant-Greene: Пожалуйста, отвечайте на комментарии с помощью @. Я ничего не спорю, и мне не нужна помощь. Я тот, кто исправляет ошибку в ответе. Я говорю то, что ясно говорит спецификация. Никакая проверка не может показать, что что-то правильно, потому что это может быть неопределенное поведение. Вы не указали и не обосновали ничего, с чем вы не согласны в том, что я сказал. - person newacct; 11.08.2014
comment
Спецификация явно говорит противоположное тому, на чем вы продолжаете настаивать, и компилятор ведет себя соответственно. Разработчики неоднократно заявляли, что текущее поведение является правильным. Все эти вещи понятны всем остальным, независимо от того, считаете ли вы самонадеянно, что исправляете ошибку в ответе, или нет. - person jbg; 26.08.2014