В вашем примере тестовый таргет линкуется с боевым приложением с помощью `@testable import`.
Кажется, что этот способ подходит для интеграционных и UI-тестов, т.к.Вы подключаете все приложение целиком в сборке со всеми внутренними настройками и связями. Это приводит к тому, что вызов одного метода может по цепочке пробежать половину связанного приложения, что нарушит кейс изоляции, так необходимый по принципам FIRST для юнит-тестов.
Юнит-тесты же кажется разумным линковать напрямую с конкретным проверяемым боевым файлом без всяких импортов боевого приложения. У этого подхода есть свои плюсы и свои минусы.
В связи с этим вопрос к автору в частности и сообществу в общем: "Можно ли использовать `@testable import` в юнит-тестах (именно в них), и почему?"
Планируете ли в последующих статьях раскрыть этот вопрос?
Спасибо за статью. Ждем более сложные модели, советы по оптимизации и чиселки выигрыша, чтобы сравнить потом со стоимостью таких оптимизаций, и понять, стоят ли они выделки.
Есть еще один вполне себе качественный показатель тестов. Называется Mutation Score Indicator (MSI) и в вике. Правда, замерять его достаточно сложно. Но кажется, что он как раз и покрывает все перечисленные Вами случаи вместе взятые.
В качестве быстрого совета. Обратите внимание, что конфигураторы ячеек у вас имеют абсолютно идентичный код. Их можно привести к дженерику, написать один раз и переиспользовать. По второй ссылке это как раз и реализовано. Только называется оно там фабрикой, если я правильно помню терминологию.
Ну, и объединять 2 ответственности (DataSource и DataDelegate) в 1м классе (SearchTableManager) тоже не лучшее решение, будет сложно переиспользовать код. Аналогично сделано в первой статье. Там же можно поглядеть, к каким сложностям это приводит и как ребята попробовали их решить. В вашем случае все равно придется в каждом экране делать свой менеджер. Если разнести ответственности, то их уже можно переиспользовать отдельно. Например, переиспользовать те же ячейки, но привязать на них уже другие действия.
И последним этапом можно будет еще и развязать экшены действий пользователя по каждому типу ячейки друг от друга. Тогда каждое действие можно будет независимо переиспользовать в любом контроллере. Полученный делегат можно аналогично написать один раз и переиспользовать примерно в 90% случаев. Тут поможет Responder Chain. Статью о том, как responder chain использовать с универсальной реализацией делегата мы готовим. Скоро она появится на Хабре, как продолжение цикла статей из второй ссылки.
В копилку каверзных вопросов для собеседований. Добавьте в ваш граф ретейн цикла еще один объект, и 9 из 10 собеседуемых его уже не найдут.
И еще один. Какую из ссылок в графе ретейн цикла по теории можно сделать слабой для устранения проблемы? А какую нужно, чтобы не создать себе других проблем? Как определять ту единственную связь, которую в графе безопасно ослаблять?
Вот тут из 8 вхождений 7 -- это manual reference counting, и только одно - manual retain release. Правда, в этой же статье есть ссылка и сюда. И вот тут уже дается определение MRR, но тоже всего в нескольких упоминаниях. Имхо, не стоит так категорично. Кажется, что даже сами Apple вполне себе используют аббревиатуру MRC. Да и в русском сообществе она очень популярна. Ну, и так ли важно, как называется технология, или все же важнее понимают ли разработчики, как она работает?
На моменте, когда вы стали дублировать системный код уже стоило задуматься, откатиться назад и разобраться, как же не терять возможность использования системного кода. Ведь таким образом, чем дальше Вы будете развивать проект, тем больше системного поведения Вам придется дублировать. Вы точно этого хотите? Вы точно представляете весь масштаб кода, написанного эплом?
Вот тут мы как раз и показываем вариант, как подружить Вашу идею с типами вьюмоделей с системным кодом. Вводится простая карта соответствий типа вьюмодельки (ваш Item) на идентификатор ячейки для реюза (та самая строка, которую по дефолту делают именем класса и сразу стреляют себе в ногу). Вам больше не надо дублировать системный код. Ваша реализация упростится и потеряет еще несколько счетчиков, от которых Вы так хотите избавиться. А также Вы не будете опускать вью слой аж до уровня парсера и нарушать тем самым принципы построения надежных приложений, типа SOLID или слоистой архитектуры.
Да, в цикле статей мы пока еще не описали, как аналогичный механизм применяется и для обработки действий пользователя. Но статья уже в процессе подготовки и скоро появится. А пока в качестве домашнего задания :) Вы вполне можете подумать, как применить данный подход и к обработке действий пользователя по ячейкам. Ваш код еще сильнее похудеет.
Мне казалось, что под константой с подразумевают все же количество повторений последовательных циклов, а не время выполнения одного прогона цикла.
Но даже если и смотреть на с под таким углом, как говорите Вы, то получим мы следующее. Константы с1 и с2 будут лишь определять точку пересечения графиков друг с другом. Как экспонента и логарифм. Всем понятно, что экспонента на больших значениях более медленный алгоритм, чем логарифм. Но на значениях до 1, у них прямо противоположный эффект.
То же самое и Вы пытаетесь показать, только сдвинув точку пересечения от 1 гораздо дальше вправо и вверх. Но мне кажется, что это должны быть очень большие значения с соответствующие каким-то жутко неэффективны алгоритмам. При этом значениям н будут все еще ничтожно малы.
Для упомянутых вами алгоритмов, кмк, эти значения не такие ужасные. Да и инвертирование картинки получится лишь на малых значениях н до точки пересечения графиков. А дальше мы все равно будем получать "теоретический" неперевернутый результат. А смысл искать оптимальный алгоритм на малых значениях n? Там вообще не важно, оптимален алгоритм или нет. Быстр он или нет. А вот как можно исковеркать алгоритм так, чтобы точка пересечения графиков ушла так далеко вправо, чтобы даже на больших значениях n упомянутая вами картина была верна, я даже думать не хочу. Это что-то из области такого страшного говнокода, что надеюсь, никогда я с этим не повстречаюсь.
И все же ближе к практике. Упомянутые Вами алгоритмы на n=1000 дают инвертированную картину? Вы проводили исследования? Можно где-то почитать?
Согласен, что сравнение двух целых чисел, которыми обычно характеризуют номера сборки, проще, чем парсинг строк в формате «1.2.17» и «1.5.13» и поочередное сравнение номеров мажора, минора и патча (на все это хорошо бы иметь тесты)
Почему-то мне казалось, что в строку iOS уже вшит этот функционал. Нет необходимости дублировать имеющийся функционал и писать на копию тесты.
Как же так? O(cn) - это просто с последовательных циклов, которые можно легко свести к одному циклу, приведя тем самым алгоритм O(cn) к O(n). Именно поэтому константу всегда и упускают в такой нотации. Так что любой самый медленный O(cn) всегда можно сделать быстрее, чем O(n^2). Или есть какие-то случаи, когда нельзя? Можно примеры таких случаев?
Вот и настало то время, когда можно писать статьи на Хабре и выступать на митингах о своем собственном процессном фреймворке, который решает все проблемы, имеющиеся в *Подставь сюда свой самый ненавистный процессный фреймворк*.
Одно время был бум архитектурных реализаций.
А теперь катится волна процессных фреймворков.
К статье вопросов нет. Наоборот, всячески поддерживаю, когда люди пытаются разобраться, а почему же у всех работает, а у них нет. Но я так и не получил ответа из вступления, а что же помешало автору в последующем пользоваться фреймворком, тем более, что успешный опыт у него был? Команда? Люди? И как исправлять-то причину?
Но, это же будет противоречить и опыту и описанию Apple'а. Я четко вижу в своих приложениях, как память уменьшается, когда у меня еще есть unowned ссылки, но уже нет strong ссылок.
HeapObject - это и есть выделенный нами объект, правильно? Вернее обязательная часть нашего объекта, содержащая isa и счетчик ссылок (ну или ссылку на side table). Верно? дальше за этой обязательной частью лежат хранимые переменные нашего объекта. И вот это все вместе - наш объект. Правильно?
Теперь, если у нас счетчик сильных ссылок стал равен 0, то мы можем освободить всю эту конструкцию из памяти, т.к.она нам не нужна. Но получается, что мы ее не можем освободить, т.к. нам нужна side table, по которой мы сможем сказать, что вот такой-то объект больше не существует. Т.е. получается, что объект живет в памяти не до тех пор, пока на него не осталось сильных ссылок, а он живет пока на него есть вообще хоть одна ссылка или хотя бы пока на него не осталось даже unowned ссылок? Ваша цитата: "А unowned счетчик отмеряет время жизни памяти, выделенной под объект." говорит об этом же?
Но погодите, тогда это полностью противоречит тому, что нам дает Apple. Точно ошибки в понимании логики нигде не произошло?
И второй вопрос. Какие прикладные задачи теперь можно решать с этим знанием в финтех-проекте? Вы же не просто ради спортивного интереса реверс-инжинирили компилятор? :)
А зачем же нам счетчики weak и unowned ссылок? Со strong понятно -- это счетчик жизни объекта, по нему мы понимаем, жив объект или уже мертв. А зачем считать weak и особенно unowned. Допустим, что weak нужен просто для валидации, что мы нашли в памяти нужное число ссылок для их обнуления. Хотя зачем может быть нужна такая валидация? А вот про unowned вообще не могу предположить, зачем...
А можно чуть более подробно, почему в UDF не нужны протоколы? В статье об этом дважды, но оба раза просто констатация факта, без пояснений. Причем, во втором случае написано, что есть проблема, которую бы могли решить протоколы (связность и абстракции), но приходится почему-то искать другие способы.
И в продолжение нашего разговора в предыдущей статье, кажется, что после разбиения доменной логики на независимые кусочки, каждый такой кусочек вправе обладать своим собственным состоянием. А состояния разных кусочков агрегируются медиатором. Медиатор самого высокого уровня будет иметь общее единое состояние, если оно кому-то будет нужно.
ну, если вы понимаете это, то вам не составит труда и разделять стейты и разносить их по зонам ответственности. Тогда и не придется столько сопутствующих проблем решать. Но хозяин -- барин, как говорится.
Тут еще такие мысли. Кажется, что при переходе не реактивное слежение за сигналами, читать явно значения переменных не стоит. Т.е. не стоит смешивать активное и реактивное программирование. Если уж у вас везде реакции на изменения, то убрав явное чтение значений в каждый конкретный момент, Вы исключаете саму возможность прочитать неверное состояние. А сигналы рано или поздно дойдут до всех подписчиков в актуальном состоянии.
Кажется, что единый Стейт все же не решает проблемы многопоточности, ну вот никак. Даже с единым стейтом вы все равно не застрахованы, что ваше приложение не окажется в конкретный момент времени в непредусмотренном состоянии. Даже с единым стейтом, система в любой момент времени может перекинуть вас на другой поток. Даже во время вашего перехода из одного стейта в другой, пока этот самый Стейт еще не обновился полностью. И вы получите тот же самый рассинхрон, которого Вы так опасаетесь. Проблему мог бы решить единственный поток, в котором и происходят все смены состояний и их чтения, но мы прекрасно знаем, что в iOS работать в одном единственном потоке практически невозможно. А значит, единый Стейт для Вас - это лишь попытка себя обмануть. Кажется, что единственный возможный выход из данной проблемы - это правильная работа в многопоточной среде с атомарными записями и чтениями состояния. И тут уже не важно, единый у вас Стейт или распределенный, но обеспечивающий атомарность записи и чтения. Тем более, что Вы тоже согласны, что Стейт в Redux не обязан быть единым. Кажется, что единый Стейт обеспечивал бы те плюсы, что Вам нужны, в каком-то другом программерском мире, но не в иосном. Возможно, я ошибаюсь.
Надо всегда исправлять не последствия, а причину баги ^.^
В вашем примере тестовый таргет линкуется с боевым приложением с помощью `@testable import`.
Кажется, что этот способ подходит для интеграционных и UI-тестов, т.к.Вы подключаете все приложение целиком в сборке со всеми внутренними настройками и связями. Это приводит к тому, что вызов одного метода может по цепочке пробежать половину связанного приложения, что нарушит кейс изоляции, так необходимый по принципам FIRST для юнит-тестов.
Юнит-тесты же кажется разумным линковать напрямую с конкретным проверяемым боевым файлом без всяких импортов боевого приложения. У этого подхода есть свои плюсы и свои минусы.
В связи с этим вопрос к автору в частности и сообществу в общем: "Можно ли использовать `@testable import` в юнит-тестах (именно в них), и почему?"
Планируете ли в последующих статьях раскрыть этот вопрос?
Заранее спасибо.
Спасибо за статью. Ждем более сложные модели, советы по оптимизации и чиселки выигрыша, чтобы сравнить потом со стоимостью таких оптимизаций, и понять, стоят ли они выделки.
Есть еще один вполне себе качественный показатель тестов. Называется Mutation Score Indicator (MSI) и в вике. Правда, замерять его достаточно сложно. Но кажется, что он как раз и покрывает все перечисленные Вами случаи вместе взятые.
Вот еще несколько подобных решений, уже описанных на Хабре:
Reactive Data Display Manager
и это
Возможно, сможете что-то почерпнуть оттуда.
В качестве быстрого совета. Обратите внимание, что конфигураторы ячеек у вас имеют абсолютно идентичный код. Их можно привести к дженерику, написать один раз и переиспользовать. По второй ссылке это как раз и реализовано. Только называется оно там фабрикой, если я правильно помню терминологию.
Ну, и объединять 2 ответственности (DataSource и DataDelegate) в 1м классе (SearchTableManager) тоже не лучшее решение, будет сложно переиспользовать код. Аналогично сделано в первой статье. Там же можно поглядеть, к каким сложностям это приводит и как ребята попробовали их решить. В вашем случае все равно придется в каждом экране делать свой менеджер. Если разнести ответственности, то их уже можно переиспользовать отдельно. Например, переиспользовать те же ячейки, но привязать на них уже другие действия.
И последним этапом можно будет еще и развязать экшены действий пользователя по каждому типу ячейки друг от друга. Тогда каждое действие можно будет независимо переиспользовать в любом контроллере. Полученный делегат можно аналогично написать один раз и переиспользовать примерно в 90% случаев. Тут поможет Responder Chain. Статью о том, как responder chain использовать с универсальной реализацией делегата мы готовим. Скоро она появится на Хабре, как продолжение цикла статей из второй ссылки.
В копилку каверзных вопросов для собеседований. Добавьте в ваш граф ретейн цикла еще один объект, и 9 из 10 собеседуемых его уже не найдут.
И еще один. Какую из ссылок в графе ретейн цикла по теории можно сделать слабой для устранения проблемы? А какую нужно, чтобы не создать себе других проблем? Как определять ту единственную связь, которую в графе безопасно ослаблять?
Вот тут из 8 вхождений 7 -- это manual reference counting, и только одно - manual retain release. Правда, в этой же статье есть ссылка и сюда. И вот тут уже дается определение MRR, но тоже всего в нескольких упоминаниях. Имхо, не стоит так категорично. Кажется, что даже сами Apple вполне себе используют аббревиатуру MRC. Да и в русском сообществе она очень популярна. Ну, и так ли важно, как называется технология, или все же важнее понимают ли разработчики, как она работает?
На моменте, когда вы стали дублировать системный код уже стоило задуматься, откатиться назад и разобраться, как же не терять возможность использования системного кода. Ведь таким образом, чем дальше Вы будете развивать проект, тем больше системного поведения Вам придется дублировать. Вы точно этого хотите? Вы точно представляете весь масштаб кода, написанного эплом?
Вот тут мы как раз и показываем вариант, как подружить Вашу идею с типами вьюмоделей с системным кодом. Вводится простая карта соответствий типа вьюмодельки (ваш Item) на идентификатор ячейки для реюза (та самая строка, которую по дефолту делают именем класса и сразу стреляют себе в ногу). Вам больше не надо дублировать системный код. Ваша реализация упростится и потеряет еще несколько счетчиков, от которых Вы так хотите избавиться. А также Вы не будете опускать вью слой аж до уровня парсера и нарушать тем самым принципы построения надежных приложений, типа SOLID или слоистой архитектуры.
Да, в цикле статей мы пока еще не описали, как аналогичный механизм применяется и для обработки действий пользователя. Но статья уже в процессе подготовки и скоро появится. А пока в качестве домашнего задания :) Вы вполне можете подумать, как применить данный подход и к обработке действий пользователя по ячейкам. Ваш код еще сильнее похудеет.
Мне казалось, что под константой с подразумевают все же количество повторений последовательных циклов, а не время выполнения одного прогона цикла.
Но даже если и смотреть на с под таким углом, как говорите Вы, то получим мы следующее. Константы с1 и с2 будут лишь определять точку пересечения графиков друг с другом. Как экспонента и логарифм. Всем понятно, что экспонента на больших значениях более медленный алгоритм, чем логарифм. Но на значениях до 1, у них прямо противоположный эффект.
То же самое и Вы пытаетесь показать, только сдвинув точку пересечения от 1 гораздо дальше вправо и вверх. Но мне кажется, что это должны быть очень большие значения с соответствующие каким-то жутко неэффективны алгоритмам. При этом значениям н будут все еще ничтожно малы.
Для упомянутых вами алгоритмов, кмк, эти значения не такие ужасные. Да и инвертирование картинки получится лишь на малых значениях н до точки пересечения графиков. А дальше мы все равно будем получать "теоретический" неперевернутый результат. А смысл искать оптимальный алгоритм на малых значениях n? Там вообще не важно, оптимален алгоритм или нет. Быстр он или нет. А вот как можно исковеркать алгоритм так, чтобы точка пересечения графиков ушла так далеко вправо, чтобы даже на больших значениях n упомянутая вами картина была верна, я даже думать не хочу. Это что-то из области такого страшного говнокода, что надеюсь, никогда я с этим не повстречаюсь.
И все же ближе к практике. Упомянутые Вами алгоритмы на n=1000 дают инвертированную картину? Вы проводили исследования? Можно где-то почитать?
Почему-то мне казалось, что в строку iOS уже вшит этот функционал. Нет необходимости дублировать имеющийся функционал и писать на копию тесты.
Как же так? O(cn) - это просто с последовательных циклов, которые можно легко свести к одному циклу, приведя тем самым алгоритм O(cn) к O(n). Именно поэтому константу всегда и упускают в такой нотации. Так что любой самый медленный O(cn) всегда можно сделать быстрее, чем O(n^2). Или есть какие-то случаи, когда нельзя? Можно примеры таких случаев?
Вот и настало то время, когда можно писать статьи на Хабре и выступать на митингах о своем собственном процессном фреймворке, который решает все проблемы, имеющиеся в *Подставь сюда свой самый ненавистный процессный фреймворк*.
Одно время был бум архитектурных реализаций.
А теперь катится волна процессных фреймворков.
К статье вопросов нет. Наоборот, всячески поддерживаю, когда люди пытаются разобраться, а почему же у всех работает, а у них нет. Но я так и не получил ответа из вступления, а что же помешало автору в последующем пользоваться фреймворком, тем более, что успешный опыт у него был? Команда? Люди? И как исправлять-то причину?
Но, это же будет противоречить и опыту и описанию Apple'а. Я четко вижу в своих приложениях, как память уменьшается, когда у меня еще есть unowned ссылки, но уже нет strong ссылок.
Это как-то можно объяснить?
Но погодите. У меня что-то не сходится.
HeapObject - это и есть выделенный нами объект, правильно? Вернее обязательная часть нашего объекта, содержащая isa и счетчик ссылок (ну или ссылку на side table). Верно? дальше за этой обязательной частью лежат хранимые переменные нашего объекта. И вот это все вместе - наш объект. Правильно?
Теперь, если у нас счетчик сильных ссылок стал равен 0, то мы можем освободить всю эту конструкцию из памяти, т.к.она нам не нужна. Но получается, что мы ее не можем освободить, т.к. нам нужна side table, по которой мы сможем сказать, что вот такой-то объект больше не существует. Т.е. получается, что объект живет в памяти не до тех пор, пока на него не осталось сильных ссылок, а он живет пока на него есть вообще хоть одна ссылка или хотя бы пока на него не осталось даже unowned ссылок? Ваша цитата: "А unowned счетчик отмеряет время жизни памяти, выделенной под объект." говорит об этом же?
Но погодите, тогда это полностью противоречит тому, что нам дает Apple. Точно ошибки в понимании логики нигде не произошло?
И второй вопрос. Какие прикладные задачи теперь можно решать с этим знанием в финтех-проекте? Вы же не просто ради спортивного интереса реверс-инжинирили компилятор? :)
Статья мощная. Но остались нераскрытые вопросы.
А зачем же нам счетчики weak и unowned ссылок? Со strong понятно -- это счетчик жизни объекта, по нему мы понимаем, жив объект или уже мертв. А зачем считать weak и особенно unowned. Допустим, что weak нужен просто для валидации, что мы нашли в памяти нужное число ссылок для их обнуления. Хотя зачем может быть нужна такая валидация? А вот про unowned вообще не могу предположить, зачем...
Есть у Вас ответы на эти вопросы?
А можно чуть более подробно, почему в UDF не нужны протоколы? В статье об этом дважды, но оба раза просто констатация факта, без пояснений. Причем, во втором случае написано, что есть проблема, которую бы могли решить протоколы (связность и абстракции), но приходится почему-то искать другие способы.
И в продолжение нашего разговора в предыдущей статье, кажется, что после разбиения доменной логики на независимые кусочки, каждый такой кусочек вправе обладать своим собственным состоянием. А состояния разных кусочков агрегируются медиатором. Медиатор самого высокого уровня будет иметь общее единое состояние, если оно кому-то будет нужно.
Спасибо. Статья интересная. Ждем продолжения.
ну, если вы понимаете это, то вам не составит труда и разделять стейты и разносить их по зонам ответственности. Тогда и не придется столько сопутствующих проблем решать. Но хозяин -- барин, как говорится.
Тут еще такие мысли. Кажется, что при переходе не реактивное слежение за сигналами, читать явно значения переменных не стоит. Т.е. не стоит смешивать активное и реактивное программирование. Если уж у вас везде реакции на изменения, то убрав явное чтение значений в каждый конкретный момент, Вы исключаете саму возможность прочитать неверное состояние. А сигналы рано или поздно дойдут до всех подписчиков в актуальном состоянии.
Кажется, что единый Стейт все же не решает проблемы многопоточности, ну вот никак. Даже с единым стейтом вы все равно не застрахованы, что ваше приложение не окажется в конкретный момент времени в непредусмотренном состоянии. Даже с единым стейтом, система в любой момент времени может перекинуть вас на другой поток. Даже во время вашего перехода из одного стейта в другой, пока этот самый Стейт еще не обновился полностью. И вы получите тот же самый рассинхрон, которого Вы так опасаетесь. Проблему мог бы решить единственный поток, в котором и происходят все смены состояний и их чтения, но мы прекрасно знаем, что в iOS работать в одном единственном потоке практически невозможно. А значит, единый Стейт для Вас - это лишь попытка себя обмануть. Кажется, что единственный возможный выход из данной проблемы - это правильная работа в многопоточной среде с атомарными записями и чтениями состояния. И тут уже не важно, единый у вас Стейт или распределенный, но обеспечивающий атомарность записи и чтения. Тем более, что Вы тоже согласны, что Стейт в Redux не обязан быть единым. Кажется, что единый Стейт обеспечивал бы те плюсы, что Вам нужны, в каком-то другом программерском мире, но не в иосном. Возможно, я ошибаюсь.