All streams
Search
Write a publication
Pull to refresh
18
0
Кирилл @ws233

Руководитель мобильной разработки

Send message

Очень хорошо про это написано в трилогии у Юваля Харари.

Цель, конечно же, всегда благая. Например, собирательство требовало меньше времени на получение того же числа калорий, но зависело от очень многих причин. Была проблема – необходимы запасы, чтобы в неблагоприятное время не умереть с голоду. А делать запасы с собирательством не очень просто.

Поэтому научились выращивать сами – перешли на сельское хозяйство. Это очень быстро привело к избытку запасов и получаемых калорий. Лишние запасы и калории привели к взрывному росту населения. И вот мы получили ситуация, когда просто для того, чтобы прокормить себя, все имеющееся население вынуждено работать по 12/7, т.к. имеющаяся производительность и труда, и выхлоп урожая конечны, а рост населения продолжается.

Пришлось придумывать промышленную революцию, которая привела к аналогичному результату. Сначала производство благ стремительно выросло, что привело к еще более взрывному росту населения, которое сново уперлось в невозможность обеспечить всех благами – снова всем пришлось работать по 12/7...

Впереди очередная революция...

Добрый день. Подскажите, как продвигается работа по следующей статье? Очень хочется ее увидеть...

Если Сбер завел 70 вакансий иосника, чтобы забить им ленту hh, но нанял 1, то в вашей статистике учитывается 70.

Если другой банк завел 1 вакансию, но нанял 10 человек, то в вашей статистике все равно будет 1.

Кажется, по Вашей методике можно лишь считать компании, которые ищут людей и спамят в ленту hh, но никак не число вакансий на рынке...

А есть более точная методика подсчета?

Так ведь:

  1. xib – это файл для разработки и хранения в человекочитаемом виде. В bundle приложения он не складывается. В bundle приложения складывается скомпилированная версия в формате nib. Это по сути сериализованная версия объектов, на парсинг которой особо времени и не нужно. Ну, не больше, чем на сериализацию/десериализацию. Это гораздо быстрее, чем парсить человекочитаемый код. Ну, и это не намного дольше, чем создавать объекты из кода. Есть цифры, подтверждающие, что одно дольше другого? А насколько?

  2. Xcode дает ворнинги при компиляции xib-файлов. По умолчанию это ворнинги и они не останавливают процесс билда. Но, кажется, что этот параметр можно менять и сделать ворнинги в xib-файлах ошибками, останавливающими процесс билда. Так что проверки на этапе компиляции все же есть.
    Что касается вызова событий, то для этого есть Responder Chain и first responder. Этот механизм не вызывает исключений и не роняет приложение, если не находит в цепочке указанный селектор. Добавляем сюда интеграционные тесты на корректность дерева вьюх и контроллеров и проблема уходит. Ошибки в переходах тоже прекрасно закрываются интеграционными тестами.
    С другой стороны, краши при вызове селектора, который не привязали в коде никак от IB не зависит. Это вообще не проблема IB. Это проблема отсутствия связи, которую и в коде можно неверно проставить или вообще не проставить. Полагаю, что и с переходами на экраны аналогично. И обе проблемы лечатся не переходом с IB на код, а интеграционными тестами.

Спасибо, что напомнили еще про 2 мифа, не имеющих отношения к реальности ^.^.

Идеально покрыть тестами сложный код - просто математически невозможно.

Математика очень не любит, когда ей приписывают слова, которых она не говорила. :)

Поглядите, пожалуйста, мутационное тестирование. Оно как раз и отвечает на вопрос, а сколько багов из тех, что можно вообще допустить в вашем коде, Вы вылавливаете вашими тестами. Раздел математики – комбинаторика – дает четко однозначный ответ, как это все посчитать и сколько вообще ошибок может быть.

Да, религию делать не надо, но получить вполне себе однозначную качественную оценку вашей сколь угодно сложной системы вполне возможно. И математика Вам в этом поможет.

Юнит-тесты очень просты и состоят обычно из трех строчек кода:

  1. assign

  2. act

  3. assert

К тому же в 80% случаев Ваши тесты будут копиями друг друга, но имеющими лишь отличия в третьей строчке, т.е.в том условии, которое Вы проверяете.

Согласитесь, что написать 5 функций, состоящие из 3-х строчек в день вполне возможно. Я даже удивляюсь, что написано всего 5 тестов в день, а не существенно больше.

Да, интеграционные тесты будет написать чутка сложнее, но и они должны состоять лишь из 4-х строчек (в первом пункте будет 2 строки, вместо одной, т.к. нужно создать 2 объекта и проверить их интеграцию). Тоже, кажется, что можно писать существенно больше 5 тестов в день.

e2e-тесты еще сложнее, но и они пишутся из заготовок и уже не проверяют то, что было проверено в двух абзацах выше. В итоге, даже тут 5 тестов в день выглядят прям скромным результатом.

Если у Вас так не выходит, задумайтесь, где Вы ошибаетесь.

  1. Тесты – это не просто документация. Это 100% копия технического задания, переведенная с человеческого на язык машинных кодов. Если у вас есть тест, но нет соответствующего описания на человеческом языке в ТЗ, то стоит это описание туда добавить. И наоборот. Любое изменение в ТЗ приводит к гарантированному изменению в тестов. В итоге у вас 1 и тот же документ записан на двух языках: человеческом (ТЗ) и машинном (тесты) – и оба перевода должны постоянно синхронизироваться в обе стороны.

  2. Проблемы с покрытием у Вас из-за отсутствия изоляции. Изоляция – необходимое условие юнит-тестов (принципы FIRST). Если вернете им изоляцию, то проблем с подсчетом покрытия для них не будет. С интеграционными и e2e-тестами это не сработает. Там изоляция быть не может, а значит, что и покрытие для этих тестов как метрика быть использована не может. Другими словами – покрытие можно считать ТОЛЬКО для юнит-тестов (которые являются копией ТЗ, помните?), для всех других тестов эту метрики использовать нельзя.

Есть мнение, что его приоритет чуть ниже, чем других фич. Но когда-то доберутся и до оффлайн режима.

Это не отменяет справедливости остальной части мысли ;-)

Об этом я и написал, да. Что это вряд ли будет быстрее. Скорее медленнее.

  1. в интернетах пишут. раз. два - swiftui code is converted to uikit views behind the scenes. больше искать не стал. поищите, пожалуйста, сами.

  2. возможность транслировать код из SwiftUI в UIKit и обратно тоже как бы намекает.

  3. Ну и я собрал маленький пример. Скрины ниже.

Hidden text

Да, никто не запрещает Эплу полностью переписать внутреннюю реализацию без использования UIKit, но пока это не так. Планов по переписыванию я не встречал.

Да, на других платформах под SwiftUI будут другие фреймворки. Но смысл тот же - это всего лишь обертка.

Так что? Исправите статью под многочисленные комментарии к ней?

А пожалуйста.

Вы "быстродействие" обосновываете "более быстрым и эффективным развитие фреймворка", "лёгкостью, читабельностью и удобством в работе", "опытом программиста" и т.д. Это все, естественно, не связано.

Если оставить только связанное, то остаются только структуры против классов.

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

Сконцентрируемся на том факте, что SwiftUI – это всего лишь обертка поверх UIKit. Т.е. все ваши структуры под капотом все равно создают соответствующие классы UIKit и работают с ними. Т.е. никакого выигрыша в производительности Вы не получите просто по одной причине: вместо того, чтобы работать только лишь с одними классами, вы еще создаете, модифицируете, гоняете по памяти, удаляете эти самые структуры-обертки-описания этих самых классов. Большее количество работы ну никак не может быть быстрее, чем меньшее количество работы. Согласны?

Теперь про "сборку мусора". Ее на iOS нет. Вместо нее используется механизм подсчета ссылок. Вопрос, который проясняют с каждым джуном на собеседовании. Этот механизм считается очень быстрым и имеющим нулевые затраты на управление памятью (на самом деле не нулевые, но близкие к нулю). При этом точно такой же механизм используется и для определения момента удаления структуры из стека. Поэтому этот аргумент в пользу быстродействия SwiftUI и структур тоже сомнителен.

Но да, спасибо за проделанную работу. Осталось ее подкорректировать и получится очень классная статья-сравнение. Можно будет ссылаться на нее и на Вас в моих дискуссиях про 2 технологии :)

Отличная работа проделана. Вы молодец. Но многие вещи все же подтягиваются под необходимый ответ. К комментариям выше добавлю следующие.

Однако, в отличие от Swift, Objective-C не поддерживает жирные структуры. Следовательно, нет способа написать пользовательский интерфейс с помощью SwiftUI и импортировать его в приложение, написанное на Objective-C. Это означает, что разработчики, которые хотят использовать SwiftUI, должны перейти на Swift и создать новое приложение с нуля, если хотят использовать весь потенциал фреймворка.

В корне неверно. SwiftUI полностью совместим с ObjC. У Элла есть документация, какие оберточки Вам нужно сделать, чтобы иметь возможность использовать SwiftUI в ObjC.

  1. Про скорость верстки очень неоднозначный момент. Да, в простых синтетических примерах, где надо только фрейм поменять, тут SwiftUI нет конкурентов. Но как только мы переходим в область комплексного UI и UX, так сразу начинаются проблемки и преимущество в скорости верстки резко превращается в мощный такой минус, перекрывающий полученный до этого выигрыш.

В теории разработки информационных систем считается, что View-компонент должен быть платформенно зависимым. Попытки делать его платформенно независимым делают его тяжелым, сложным и не гибким в настройке и расширении.

Проще написать отдельный UI для TVOS, watchOS и iOS в горизонтальной и вертикальной ориентации, чем пытаться описать все это единым языком с лапшой условий.

По Modern Concurrency. Вам ничего не мешает добавить подобный синтаксис к ObjC и использовать его с ObjC.

Эта проблема давным давно решена. Разделяй и властвуй. Не нужно весь UI приложения пытаться запихнуть в 1 xml-файл. Этих файлов может быть много. Тогда вероятность, что 2 разработчика полезут в один xml-файл резко сократится. А практики установления владения над кодом эту вероятность вообще к нулю сводят. Никаких проблем. Проверено на практике.

Но в новом синтаксисе появляется новая проблема: разработчику надо обязательно отписываться от наблюдения за изменениями свойства, иначе оно так и останется в памяти.

Это не так. Подписка автоматически удалится, когда удалится соответствующий токен. Функция invalidate() вызывается автоматически при удалении токена, что произойдет при удалении удерживающего его объекта в вашем примере. Никаких дополнительных действий по отписке осуществлять в новом синтаксисе не надо. Раз, два.

Что касается остальных примеров, то они похожи на совет не пользоваться ножом для нарезки масла, потому что нож острый и можно порезаться. Да, можно порезаться. Но мазать масло на хлеб без ножа будет довольно сложно и не очень вкусно. Лучше на мой взгляд все же сделать над собой усилие и освоить нож так, чтобы им никогда не резаться. Но дело Ваше.

1.1 Все же это адаптер получится по факту, т.к. вы примените принципы разбиения на слои, как Вы написали. Верхний не будет взаимодействовать с нижним, их интерфейсы не совместимы. При этом между ними и будет некая часть, которая их соединяет. В чистом виде - адаптер. Правда, в этом случае там будет еще куча функционала и будет нарушен принцип единой ответственности. Но функционал адаптера там точно есть.

1.2 Понял, адаптером Вы его назвали только из-за функции адаптации данных.

Но поглядите, что медиатор тоже "адаптирует" сигналы от одного модуля к другому и обратно. Из-за этого он не становится адаптером. Медиатор от адаптера как раз расположением в слоях и зависимостью различаются (кто выше, кто ниже, кто от кого зависит). Блок1 -> Адаптер -> Блок2 против Блок1 <- Медиатор -> Блок2. Это ключевое и, кажется, единственное, отличие между ними.

  1. Ну, кажется, подробности того, как вы это все настроили, как оно работает, какие проблемы, какие выгоды. Почему именно так, а не иначе. Какие вообще варианты рассматривали, какие критерии выбора и т.д. Скрипты, утилиты и т.д. Советы.

  2. Жду следующую статью. Там обсудим предметно. Но пока мы вот наоборот от хореографии обратно в оркестровку предпочитаем все возвращать (на медиаторах, не на адаптерах, это важно), ибо проще. Даже в большом проекте. У хореографии единственный плюс – когда есть шина и общий формат общения. Возможно, это ваш случай. Но если это не ваш случай, то осветите, пожалуйста, этот момент в следующей статье очень детально. Возможно, откроете для себя ошибки.

Спасибо за подробные ответы.

  1. про критический путь.

По сути, это реализация паттерна «Адаптер» на уровне модулей.

"Медиатор" же. "Адаптер" как раз в первом примере.

Последний вариант действительно очень мощный, но и неоправданно сложный. Особенно в больших проектах. При прочих равных его выбирать не стоит. Разница между вторым и третьим вариантом описывается разницей между оркестровкой и хореографией (это как раз из теории распределенных систем, о которых Вы в выводах упоминаете). Там вроде даже правило есть, что без особой необходимости надо предпочитать оркестровку (т.е.второй вариант из Вашей статьи).

  1. Вот конкретно я бы хотел почитать про графы, артефакты сборки (не смотрели в сторону Nexus?) и все перечисленные Вами инструменты. Ссылки Вы дали (и спасибо за них), но хочется опыт и проблемы.

  2. Какой, кстати, способ уменьшения критического пути выбрали? Я бы вот почитал еще и про сравнение второго и третьего пути. Оно пока интуитивное. Хоть я выше и дал некоторые ссылки, но однозначно утверждать пока не решаюсь. Эту статью бы тоже почитал, если у Вас есть информация.

  3. Не функциональные зависимости как-то можно изолировать? Откуда эта градация? можно ссылку? Есть там что-то про то, как их влияние минимизировать? Те же адаптеры или что-то еще?

В заключение.

Статья – просто огонь. Давно не получал такого удовольствия от чтения материалов с Хабра. Настоящий инженерный труд. Спасибо за него. Ответить бы на мои вопросы выше – получится еще и отличный научный труд. ^.^ Ждем следующих статей.

Хороший коммент. Глубокий. Видно, что работу над ошибками Вы провели. Уважаю.

Вы правы про блокировку контекста полностью. Но глядите какая штука. Вы ведь не обязаны дочерний бекграунд поток делать от родительского main. Можно же и наоборот? Родительский у вас в бекграунде и работает напрямую с хранилищем, а вот дочерний от него работает на главном потоке и весь UI завязан на дочерний, а не на родительский контекст. С CoreData работал давно, но почему-то помню именно такую схему. Мне казалось, что как раз в книжке, на которую Вы ссылаетесь эту схему я и видел. Эта схема должна работать быстрее, потому что она отбирает ресурсы главного потока только на мёрдж изменений из родительского контекста в дочерний (без блокировки на запись в стор). Но это не точно :)

Вот как раз ответ из треда, на который Вы ссылаетесь, о том, что я и говорю.

В первом же куске кода у вас ошибка. Вы не имеете права вызывать замыкание в текущей очереди. Только в очереди контекста.

A private queue context (as defined by a NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType) creates its own queue upon initialization and can be used only on that queue. Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the perform(:) and the performAndWait(:) methods.

Отсюда.

Если бы Вы сделали правильно, то проблем бы не возникло:

func enqueueBackgroundWriteBlock(block: (NSManagedObjectContext) -> Void) {
    let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    context.parent = readContext
    context.perform { // Вы пропустили вот это!
      block(context)
      do {
        try context.save()
      } catch {
        context.rollback()
      }
    } 
}

CoreData потоконебезопасна.

Это заблуждение. Методы выше, как раз и сделаны для того, чтобы кордатой пользоваться потокобезопасно.

Вот примерно так нежелание читать документацию приводит к выполнению абсолютно бесполезной работы. Вы проделали все то, что уже есть у Apple из коробки. Только у Apple тот код более надежен и учитывает кучу различных специфических ситуаций.

Удачи в чтении документации и приведении своего кода к рекомендациям Эпла.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Works in
Registered
Activity