Нет, ничего не смущает. Вы молодец, что пытаетесь разобраться. Я лишь пытался сказать, что возможно, дело не в архитектурах, раз они все вам не подходят. Возможно, что дело в том, как вы их понимаете?
Хоть Вы и не просили, но продолжили диалог ^.^, поэтому попробуйте в первую очередь понять, а что же скрывается за понятием масштабируемости, расширяемости и гибкости, которые и являются критериями хорошей архитектуры. Вот быстрая, но очень хорошая ссылка на Хабре.
Поняв что за ними кроется, вы поймете, как нужно организовывать код по ответственностями, и как организовывать эти ответственности во что-то большее. Дальше заметите, что все архитектуры очень похожи и отличаются только нюансами: способами и направлениями взаимодействия компонентов. При этом все они пытаются уменьшить число связей между компонентами своей системы.
А следующим этапом научитесь налету преобразовывать одну архитектуру в другую. Да, это совсем не сложно. При правильном понимании можно за пару часов готовый модуль на MVVM преобразовать в VIP и обратно. Подтверждено практикой. Точно так же этот модуль можно было бы преобразовать и в MVC, и в VIPER (хоть с классическим вайпером было бы и чуточку сложнее – ребятам там слегка нарушили базовые принципы).
В частности в Вашей архитектуре вы просто меняете способ общения компонентов: попытались убрать оркестрирование и заменить его на хореографию. Попытка для иоса смелая. Но не первая. В серверной разработке этот подход давно известен. Насколько успешно вы это сделали, вы можете проверить сами. Тест в конце этого комментария.
В итоге, нужно не архитектуры сочинять, а понять, из чего они строятся, на каких базовых принципах, как это помогает упростить код. Какие еще критерии должны быть у архитектуры (да и у кода, собственно). Ну, а дальше вы можете называть это как угодно, итог у вас будет гибок, изменяем, масштабируем и дальше по критериям необходимым архитектуре.
Кстати, хотите быстрый тест своей архитектуры? Можно ли ее за пару часов преобразовать в MVVM, VIP, VIPER, MVC? а обратно?
Ну, и задача со звездочкой. А сможете реализовать MVC так, чтобы его можно было преобразовать в VIP за пару часов? а MVVM, а MVP, а любую другую архитектуру? А это возможно, как я уже говорил. И если решите эту задачку, она вас поднимет на новый уровень.
Странно, почему не мультивыбор в голосовании? Я вот собирался выбрать несколько вариантов, но с удивлением обнаружил, что дальше первого выбрать мне не удастся. Судя по тому, что за первый выбор проголосовало существенное большинство, то как минимум каждый второй тоже, как и я, считал, что в голосовании мультивыбор :)
Ну, если уж не сделали мультивыбор, то дайте хоть переголосовать. Но нет, это тоже нельзя.
В вашем примере тестовый таргет линкуется с боевым приложением с помощью `@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 вообще не могу предположить, зачем...
Нет, ничего не смущает. Вы молодец, что пытаетесь разобраться. Я лишь пытался сказать, что возможно, дело не в архитектурах, раз они все вам не подходят. Возможно, что дело в том, как вы их понимаете?
Хоть Вы и не просили, но продолжили диалог ^.^, поэтому попробуйте в первую очередь понять, а что же скрывается за понятием масштабируемости, расширяемости и гибкости, которые и являются критериями хорошей архитектуры. Вот быстрая, но очень хорошая ссылка на Хабре.
Поняв что за ними кроется, вы поймете, как нужно организовывать код по ответственностями, и как организовывать эти ответственности во что-то большее. Дальше заметите, что все архитектуры очень похожи и отличаются только нюансами: способами и направлениями взаимодействия компонентов. При этом все они пытаются уменьшить число связей между компонентами своей системы.
А следующим этапом научитесь налету преобразовывать одну архитектуру в другую. Да, это совсем не сложно. При правильном понимании можно за пару часов готовый модуль на MVVM преобразовать в VIP и обратно. Подтверждено практикой. Точно так же этот модуль можно было бы преобразовать и в MVC, и в VIPER (хоть с классическим вайпером было бы и чуточку сложнее – ребятам там слегка нарушили базовые принципы).
В частности в Вашей архитектуре вы просто меняете способ общения компонентов: попытались убрать оркестрирование и заменить его на хореографию. Попытка для иоса смелая. Но не первая. В серверной разработке этот подход давно известен. Насколько успешно вы это сделали, вы можете проверить сами. Тест в конце этого комментария.
В итоге, нужно не архитектуры сочинять, а понять, из чего они строятся, на каких базовых принципах, как это помогает упростить код. Какие еще критерии должны быть у архитектуры (да и у кода, собственно). Ну, а дальше вы можете называть это как угодно, итог у вас будет гибок, изменяем, масштабируем и дальше по критериям необходимым архитектуре.
Кстати, хотите быстрый тест своей архитектуры? Можно ли ее за пару часов преобразовать в MVVM, VIP, VIPER, MVC? а обратно?
Ну, и задача со звездочкой. А сможете реализовать MVC так, чтобы его можно было преобразовать в VIP за пару часов? а MVVM, а MVP, а любую другую архитектуру? А это возможно, как я уже говорил. И если решите эту задачку, она вас поднимет на новый уровень.
SILVER, RoundTable...
А что если дело не в архитектуре?.. ?
Нет, извините, показалось!
МГТС не позволяет оплату с баланса сотового телефона. При этом с баланса МТС все норм.
Если кто смог побороть МГТС, расскажите.
Странно, почему не мультивыбор в голосовании? Я вот собирался выбрать несколько вариантов, но с удивлением обнаружил, что дальше первого выбрать мне не удастся. Судя по тому, что за первый выбор проголосовало существенное большинство, то как минимум каждый второй тоже, как и я, считал, что в голосовании мультивыбор :)
Ну, если уж не сделали мультивыбор, то дайте хоть переголосовать. Но нет, это тоже нельзя.
Надо всегда исправлять не последствия, а причину баги ^.^
В вашем примере тестовый таргет линкуется с боевым приложением с помощью `@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 вообще не могу предположить, зачем...
Есть у Вас ответы на эти вопросы?