Pull to refresh

Comments 19

Спасибо за подробный материал!

«Блоки могут создавать циклы владения.»
Ну может все-таки оставить название retain cycle? Оно в принципе принято среди iOS разработчиков.
Спасибо. Подставил retain cycle, старался дублировать где можно, чтобы и так и так было понятно. Вроде «овладевать» и «ретейнить»
Отлично! Отдельное спасибо за моменты с памятью.
Возник вопрос: Почему прикрутив модификатор __block мы рвем retain cycle? Думал, что данный идентификатор используется для того, чтобы получить возможность изменять внешний для блока объект.
Спасибо. Да, __block используется для «того, чтобы получить возможность изменять внешний для блока объект», но еще и имеет side effect в виде разрыва цикла владения в non ARC коде. в ARC для этого используется __weak или __unsafe_unretained, а __block уже перестает так действовать. Многим не очевидно, что блоки могут создавать циклы владения, особенно если только начинаешь их использовать, поэтому решил подчеркнуть этот момент.
Поэтому приводя примеры Non-ARC ещё больше запутываете новичков, так как ARC сейчас используется по умолчанию.
А как же они поймут ARC код, если они Non-ARC не понимают? Имхо, необходимо знать Non-ARC чтобы понимать ARC, и особенно это важно для новичков, которым фундаментальные концепции учить нужно.
Я с Вашим имхо полностью согласен, что нужно знать. Но Вы уже рассказывайте либо о практически полезном применении блоков либо о разнице ARC и Non-ARC в целом. А кому надо тот и разберётся с нюансами. А кому не надо, те создат на форуме темы «Для чего используется self?» =)
HeisenbergP, спасибо за статью!
И у меня похожий вопрос про память:
В примере использовалось свойство с модификатором копирования
property (nonatomic, copy) PKHTTPRequestCompletionSuccessBlock succesBlock;
И когда присваивали в это свойство присланный блок — он копировался, и посылался лишний ретейн объекту класса PKGetUserNameRequest. Вы это назвали так — «роковая ошибка — обращение к self».
Так вот, а нельзя лы было поставить модификатор свойства «retain», что бы блок не копировался, а просто ретейнился. Тогда бы объект класса PKGetUserNameRequest не должен был рейтейниться в свою очередь.
Почему так нельзя было сделать?
Заранее спасибо.
Нельзя, потому что если блок будет использовать переменные из локального скоупа, то он создастся на стеке. Но к вызову блока, фрейм стека будет уничтожен, что приведет к крешу. Но если сделать его копию, то блок будет скопирован в кучу с сохранением всех адресов. Если же блок уже был выделен в куче, то копирования не будет, только ретейн.
Таким образом для блоков нужно всегда делать property copy.
Следует отметить еще один важный момент из-за которого «Пример разрыва цикла владения» не совсем корректен. Из-за того, что successBlock уже не ретейнит self — к моменту выполнения этого блока self уже может быть удален и по указателю будет висеть зомби, в хорошем случае. В случае ARC это решается __weak для weakSelf, но это тоже плохое решение на самом деле, потому что оно ведет к еще более интересным багам.
Именно по этому я написал:
«Или же, можно было перенести блоки из сигнатуры метода инициализации в метод startRequest,
startRequestwithCompaltion:fail:, и ретейнить блоки только на время выполнения запроса.» Что решало бы эту проблему.
Другой вариант тоже можно использовать, но конечно принимая во внимание то, что написали вы. Хотелось показать такую особенность модификатора __block как отсутствие сильной ссылки. Но наверное нужно акцентировать внимание на опасности Зомби. Спасибо, поправлю.
throw() это deprecated конструкция как и все dynamic exception specification списки. Следует использовать noexcept в реалиях C++11/14, а про throw(...) забыть.
Еще немного позанудствую, но в тексте есть небольшая путаница с понятием «лямбда-выражение» и «замыкание», которые используют как будто взаимозаменяемо.
Лямбда-выражение это часть синтаксиса языка, из лямбда-выражения компилятором генерируется тип-замыкание (closure type), а само лямбда-выражение заменяется на экземпляр этого типа. Так что фраза «не можем присовить лямбде блок» мягко говоря неверна, лямбда-выражению вообще ничего присвоить нельзя.
Про список захвата у лямбда выражения, возможно стоит сказать что спецификаторы неявного захвата ([=], [&]) захватывают не все переменные в текущей области видимости, а лишь те, что используются в теле лямбда-выражения.
Кстати, mutable у лямбда-выражения не имеет непосредственного отношения к захвату.
По-умолчанию замыкание генерируется с operator() const, наличие mutable в лямбда-выражении заставляет компилятор не помечать operator() как const, что позволяет модифицировать захваченные переменные.
Единственным способом переместить лямбду на heap является приведение ее к типу std::function

Способ №2:
auto lamb = []() {return 5;};
auto* p = new decltype(lamb)(lamb);


Способ №3:
template <typename T>
T* heap_alloc(T const& value)
{
    return new T(value);
}

auto* p = heap_alloc([]() {return 5;});


Способ №4:
std::vector<decltype(lamb)> v;
v.push_back(lamb);

И т. д…
Для потенциального уменьшения числа копирований можно и нужно задействовать perfect-forwarding:
template T* closure_alloc(T&& closure) { return new T(std::forward(closure)); }
Полностью согласен. Можно улучшать, можно придумать еще кучу способов. Главное, что способ с std::function не единственный и далеко не самый эффективный.
А все от того, что бытует мнение, что лямбды — объекты неизвестного типа. С decltype тип вполне известен.
Стараюсь не оборачивать лишний раз лямбды в std::function из-за лишнего оверхеда, особенно, когда лямбда не влезает в small buffer optimization.
Поправлю ошибку, благодарствую :)
Sign up to leave a comment.

Articles