Простой пример цикла в популярных паттернах наших :) Presenter знает о View, но View должна как миниум держать presenter. В некоторых случая должна иметь delegate. Конечно подобный цикл можно сделать и ручками, без DI.
Предположим есть экран — это объект A. Каждый раз когда заходишь на экран, он должен создаваться, и все его зависимости (B,C,D) тоже должны создаваться каждый раз. Но на объект D ссылаются B и С. Если сделать его lazy var, то D не пересоздастся при создании нового A. Если его сделать всегда создаваемым, то у B и С будут разные экземпляры D.
Я про случай когда вот такие функции должны возвращать один и тот-же объект: resolve() -> A, resolve() -> B, resolve() -> C.
Я про вот эту модульность: "слабо связанные снаружи". Все остальное это "мы просто сделали куча фреймворков, и назвали модулями". Смысл модулей, в том чтобы максимально все скрыть, оставим минимум для взаимодействия (ну если это не куча вспомогательных функций), и не забывать при этом что в отличии от классов между ними связь в обе стороны не возможна на прямую.
Ну а теперь более конкретно, на примере экранных модулей. Есть модуль A и модуль B. С экрана из модуля A можно перейти на экран из модуля B. С экрана из модуля B можно перейти на экран из модуля A. Как делать будем? И как усложнение — реализаций каждого экрана может быть несколько, и только сам модуль знает какую реализацию когда использовать.
P.S. На все эти кейсы решение есть, но назвать эти решения удобными и понятными не всегда можно. Как говорится "плавали знаем" :)
Да вопросом много, и разных. Ответ будет большим, извиняюсь заранее. Для тех кому не хочется читать всё, советую прочитать пункт 5 — там поднята важная проблема описанного решения в статье.
Эту проблему я решал в своей библиотеке самой первой, так как до этого имел дело с Typhoon. Яб даже сказал — это причина возникновения библиотеки :) Вначале были исключения, но потом перешел на логирование и внятные fatalError. То есть, если что-то пошло не так, то есть относительно внятное описание, и с 80% вероятностью в описании будет указано место куда смотреть. Конечно, для этого надо уметь читать мой ломанный английский :D
По другим либам – все грустно, иначе я бы не стал писать свою. В прочем сейчас уже появились CompileTime либы: Weaver, needle. Они частично решают проблему – частично, так как графы зависимостей бывают сложными и с циклами, а это уже надо проверять отдельными функциями.
Почитать… ну "Чистая Архитектура" можно. Я еще люблю "ООАД" от Гради Буч. А так, каждое решение влияющее на большую часть приложения надо принимать командно, и желательно до того как начали его делать :)
В вашем случае не понятно, почему смена страны ломает зависимости. Возможно, у вас есть распространенная проблема, когда в зависимости случайно попадают и данные.
Из возможных проблем вижу лишь — у разных стран доступны разные экраны, и некоторые экраны выглядят настолько по разному, что у них много реализаций. Тут или умение работать с container-ами (кто мешает создать новый? :) ), или не подмешивать техническую бизнес логику в DI, и решать это в прикладном коде используя или аля фабрики, или обычные условия в router/coordinator/navigator.
Я бы склонял к последнему, так как, если подмешивать в DI бизнес логик, четвертая проблема, указанная вами, выльется в катастрофу.
Слегка с конца — в команде 25 человек, и более того я уходил с проекта на 2 года. Так что использование DI в проекте далеко от идеала :) Например, даже когда я был на проекте, и мы переходили на третью версию библиотеки, которая начала искать проблемные циклы, то оказалось, что у нас есть циклы длиннее 15 объектов, и при создании одного простого экрана, приходится создавать более 50 зависимостей. К слову сказать два года назад Swinject просто ломался на подобных графах, и просто крашился, из-за внутренних ошибок. По скорости
Там видно, сколько занимает сам процесс регистраций, и сколько занимает процесс получения 50000 раз объекта, при разном количестве регистраций. Можно увидеть, что сам процесс регистрации зависимостей не линеен, и где-то на 4000 регистраций время станет не приятным. А вот получение объектов линейно зависит от количества регистраций, и даже на 4000 тысячах ещё приемлемое. Но я не представляю себе проекты даже с 2000 регистраций на Swift в ближайшие лет 5, а там и мощности будут быстрее, и сам язык быстрее (по профайлу самое медленное место в библиотеки это ARC), и у меня еще есть идеи как оптимизировать. Ну, и на чистом 'C' всегда можно написать.
"Как не тупить обычным разработчикам?" — Если решили чем-то пользоваться, то научитесь этим пользоваться. Вы же когда пишете на языке понимаете что пишите? Ну, я надеюсь :D Тоже самое и с библиотеками — прежде чем начать на них писать, их надо как и язык изучить, и разобраться. DI контейнер автоматически предполагает, что вы понимаете, зачем вам нужно внедрение зависимостей на проекте, а не "так сказал тим лид".
По практике скажу — это не самая простая тема, я проводил лекции внутри компаний, и видел, как люди не понимают концепцию. И это даже не зависит от опыта.
Все на ваш страх и риск. Предположим, вы писали на UIKit, а apple через 5 лет сказало — выпиливаем теперь только SwiftUI :D Вы можете уменьшить проблемы, если все будет отделено. Если у вас UIKit был только во View, а не оказывался случайно в базе данных (такое бывает), то проблема будет маленькой и решаемой.
Тоже самое и с библиотеками DI. Я выбрал концепцию "декларативный контейнер" и не долюбливаю концепции внедрения через атрибуты, аля Dagger2. В Swinject, DITranquillity при правильном использовании, код знающий о DI либе всегда отдельный, он никак не пересекается с другим. Соответственно подмена этого кода = подмене только этого кода, без необходимости искать по всему проекту. Но это в любом случае подмена, которая потребует времени.
Начнем с главного. DI либы аля моей и Swinject не представляют из себя никакой магии — это просто хорошо обернутый Dictionary. Как работает Dip не помню, я его код 2.5 года назад смотрел, и уже не помню по каким причинам решил больше не возвращаться. Соответственно если отбросить то, что библиотеки пишут люди и допускают разные ошибки, то оверхед не такой уж и большой, по сравнению с ручными вызовами.
"Зачем вообще решать эти проблемы в рантайме?". Я задам встречный вопрос — вы перешли к варианту 450 функций resolve(). Но не описали: каким образом у вас решаются циклические зависимости? Что делать если объекты не надо пересоздавать каждый раз? А если надо создавать каждый раз, но у вас ромбовидные зависимости A->B A->C B->D C->D где объект D должен будет создаться единожды, но на каждое получение объекта A, он разный? Что делать если объект реализует с 10 протоколов, и вы хотите чтобы он отдавался по всем этим протоколам? А обратная ситуация — один протокол, но много реализаций?
А теперь добавим модульность, и последние вопросы становятся еще интересней :)
Собственно говоря, я к чему. На проекте в 10-30к swift строчек кода, вполне можно обойтись концепцией DI без библиотек. Да где-то придется попотеть, вспомнить про Фабрику сервисов, а именно этот паттерн описан у вас, просто он использует возможности автовывода типов. Да это займет условных 1000 строчек, а с библиотекой бы заняло 100. Но это возможно, я так делал и все было хорошо. Кстати у вас точно не модульное приложение ;) Ну или вы не до конца описали GeneralAssembly — при модульности, его придется делать многоуровневым и с использованием протоколов.
Но как только размерность проекта выходит за рамки человеческого понимания — в нашем проекте уже никто не знает, как работает ядро, или как работает самый верхний роутинг, многое написано 4-5 лет назад и людьми которые уже давно уволись, но с тех пор код там не менялся. И такого кода много. C зависимостями у нас также — они очень сложные, они межмодульные. Многие зависимости отключаемые/включаемые. То есть выкинул модуль, собрал проект, и все кто зависел от этого модуля продолжат работу, просто без части функционала (Ну это в идеальном мире правда… все мы люди, и ошибки есть).
И вот тогда DI библиотеки начинают экономить уйму времени, и кода.
P.S. По причинам сложных зависимостей, я и делаю сейчас возможности "нарисовать" граф зависимостей — то есть библиотека будет выдавать граф, который в другой программе можно будет посмотреть визуально.
Не мог пройти мимо. Я создатель библиотеки DITranquillity, может видели статьи. И работаю на проекте где Регистраций больше 1000. И на проекте встала проблема времени запуска на 5c уже более двух лет назад. Из них 3.5 секунды был DI. В силу чего в 3 версии своей Либы я делал упор на скорость, и время снизилось до 0.02 секунд на старте для DI на томже 5c.
Так что выводы не верные — библиотеки с исполнением в реальном времени могут все делать быстро, просто никто их не оптимизирует. Я в своей либе этим обеспокоился и проблем видимых не стало 1000регистраций и 10к получения объекта работают быстрее чем создание вьюшки.
А у swinject кстати график экспонициальный… То есть чем больше регистраций, тем больше время получения объекта, и это время где-то на 400 регистраций становиться уже не совместимыми с жизнью.
Для начала — спасибо за статью, так как на текущий момент информации по swiftUI крайне мало, и обычно сложные кейсы не рассматриваются.
Ну и как участник данного конкурса, я знаю что был критерий к скорости работы, и вывод графиков не был единственной проблемой — приходилось оптимизировать и остальные вьюшки, например с датами и цифрами.
Вопрос- как по ощущениям скорость работы swiftUI? Эквивалентна констрейнам, или же медленней? Возможно даже эквивалентна тому если бы писали все на фреймах?
P.S. Сам к сожалению дальше стандартного туториал от apple не продвинулся, и больше всего меня расстроила в swiftUI навигация между экранами, если использовать обычный navigation bar — слишком жёсткая привязка к экрану на который осуществляется переход.
Все хорошо, вот ток смущает «iOS разработчик. Продвинутый курс».
Не понятно почему простейшая работа с generic-ами стала продвинутым курсов — в swiftbook это есть, и расписано подробней.
При этом в статье нет рассказа о всяких изощрённых использований generic, о том как они интересно с автовывоводом типов сочетаются, ну и о том как внутри работает.
На самом деле соглашусь с утверждением, про двусторонний список. Но тут оно оправдано — removeLast не удалось бы реализовать за O(1) без двухстороннего списка, и при таком способе реализации.
Сразу добавлю к другой части вашего комментария, так-как по теме:
и вообще почему вы представляете в обьектном виде, а не связный список из индексов (int) на обычный массив…
Думаю в силу того, что реализуется "связный список", у которого одно из преимуществ "структурная гибкость" (взял из вики). Что на практике всегда выражалось тем, что можно выделить память под все свои элементы почти всегда, в отличии от массива на который памяти не будет в силу фрагментации.
А вот почему не нужно его всегда копировать, при попытке изменить содержимое, если есть несколько ссылок на список не совсем понятно. Да возможно компилятор языка это делает чуть умнее, и учитывает некоторые другие особенности (не буду утверждать — ни разу не смотрел какие оптимизации там есть на этот счет). Но автор не влазил в работу компилятора, и реализовал вариант, который работает согласно принципу copy-on-write, хоть и было кривенько реализовано.
Возможно некоторые аспекты в статье на самом деле не аргументированы, но думаю автору который говорит про "школу" можно такое простить… Яб даже сказал, я удивлен, что в школе учат swift, в прочем как и любой другой язык...
P.S. Про сложность — если учитывать copy-on-write, и тот факт, что память надо иногда перевыделять, то у apple во всей документации описание сложности не верное. Ведь даже добавление элемента в конец массива, не всегда O(1) — иногда надо сделать realloc, что требует некоторого времени. Иногда realloc требует O(N), если вдруг память идущая дальше уже занята.
Само по себе работать с этим сложнее, в силу необходимости работать в обратном порядке.
Двухсвязный список не реализуем в такой концепции.
(value-type) Замена элемента в середине списка не возможно за O(1). И дело не в копировании даже. Список уже сформирован — когда меняется центральный элемент, список не меняется сам по себе — меняется всего один элемент. Для замены элемента нужно ручками снова пройти все элементы и обновить их.
Простой пример:
var node2 = node.next!
var node3 = node.next!.next!
var node4 = node.next!.next!.next!
node2 = LinkedListItem.linkNode(value: -1, next: node4)
var currentNode = node
// Цикл
В этом примере -1 не будет выведен.
Итог понятен — LinkedList через indirect enum (или struct), не будет являются LinkedList ибо не имеет его преимуществ. С тем же успехом (и даже лучше) можно просто все запихнуть в массив.
К сожалению это решение в данном случае наиболее предпочтительное — не пораждает лишних абстракций.
Но можно сделать протокол который бы имел только getter-ы, и отдавать не экземпляр уже класса, а экземпляр этого протокола во всех методах.
Плюсы первого решения: Не пораждает лишних абстракций, нельзя обойти.
Минусы первого решения: Все в одном файле.
Плюсы второго решения: Можно разделить на файлы.
Минусы второго решения: Лишняя абстракция, легко обходится оператором as.
Таковы реалии swift и иногда не хватает дружественных классов из С++. В данном случае хранить эти объекты в одном файле ничего критично не имеет, но иногда это усложняет чтение кода, но поделить нельзя — так как хочешь закрыть реализацию.
P.S. Есть третье решение — выделить все в отдельный модуль/проект. Тогда у свойств может быть модификатор доступа: public internal(set) и все проблемы уходят.
На самом деле у меня к реализации есть замечание:
Меня смутил WeakReference а ровном месте. То есть протокол принуждаем людей сразуже использовать weak ссылку, а не просто получить первый элемент — что обычно и хочется.
Все это ради Copy-on-write?.. ну а я сделаю вот так: let node = list.first.node то есть решение еще и не назовешь надежным. А потом так: let node = list.first.node.next и поведение резко изменится.
Но давайте учить людей правильно — внешний интерфейс всегда важнее технических проблем. А в данном случае техническая проблема решается легко, да и делает использование надежней:
struct LinkedList<T> {
private class Reference { }
private var ref = Reference()
...
}
И вот этой переменной уже можно пользоваться, для получения количества копий именно структуры. При изменении смотрим количество ссылок, и если больше одной, то делаем тоже самое + пересоздаем переменную (дабы каждый раз не копировать).
P.S. ну и я закрываю глаза, на тот факт, что никто не мешает получить node и поиграться таким образом, что весь лист будет сломан — у Node то нет полей доступа, аля private(set)
P.P.S. не знаю почему, но ваша статья меня привлекла — рассказано в самом деле хорошо. Да и LinkedList я тоже люблю — на нем много какие особенности языка всплывают.
3) Ну… я лично бы не верил. Я даже себе на собеседовании бы не верил :).
И встречал людей с N проектами N лет опыта, но которые не понимаю разницы между этими всеми вещями, даже на базовом уровне.
Хотя меня тоже бы расстроили подобные вопросы на собеседовании, но только если не было бы развития вопроса в глубь. Например вопрос "отличие reference type от value type" — хороший. На него ответит junior и senior по разному, и сломаются они на разных стадиях развития этого вопроса. Ибо к нему потом сверху идут weak/unowned/strong, захват, замыкания, аналоги в obj-c. Короче тема хорошо развивается, и если человек на самом деле крут, то доходит до разговора — а как это внутри устроено.
4) Ну в общем счете да, и я даже не настаиваю как писать. Но люди так пишут до тех пор пока не ошибутся в этом условии по несколько раз, хотя кто-то продолжает так писать. guard let читается легко и это его назначение — разворот области видимости, а guard без let имеет по факту отрицание в себе, и если условие с отрицанием, да еще и не одно условие, то начинаются серьезные проблемы.
В прочем если отстранится от swift то guard без let по стилю мышления похож на assert.
Не знаю как кто, но я еще не видел людей которые бы assert-ы всегда писали с первого раза верно — в них часто условие при написании инвертят, и позже исправляют.
Хотя разговор в этом плане похож на табы vs пробелы :) Так как есть человек с самого первого дня пишет на swift с guard, то ему будет одинаково легко читать как guard так и if.
Начало статьи породило куча вопрос:
1. Почему Avito и Yandex? Я ничего против них не имею, но полно и других.
2. Когда я собеседовался в Yandex на iOS, то меня почему-то спрашивали именно те вопросы которые попали в категорию «так себе перспективы»
3. Почему вдруг эти вопросы плохие? Мне на собеседованиях один кандидат из 20 способен не только сказать заученную фразу, но и на практике доказать, что он понимает её… остальные не способны без компилятора сказать, что напишет код из 5 простых строчек и как поведение меняется в зависимости от типа.
Про Solid промолчу — не люблю этот вопрос, и не обращаю внимание если человек не знает что это, но когда люди говорят, что знаю… см начало пункта.
А ну и замечание по коду — писать ‘guard a != b else’ это кощунство над людьми которые будут читать — два отрицания за место обычного: ‘if a==b’.
P.S. с тем что знание алгоритмов полезно я соглашусь. Правда стоит начать с более важных вещей, например что такое словарь, массив, множество — сложность операций, как реализовано в iOS. Вроде бы этим пользуются каждый день, но как устроено мало кто знает.
Так сложилось что мой опыт где-то 50/50. Причём побывал в нескольких аутсорс и нескольких продуктовых компаниях.
И там и там я видел плохой код, и там и там были проблемы с менеджементом и мамонты технологические и т.п.
Но деление между аутсорс и продукт есть — но оно заключается в личных предпочтениях. Кому-то Интересно постоянно что-то новое, новые подходы/языки/проекты и они хотят драйва. А кому-то интересно укреплять базу, развиваться в глубь. Это как кататься на мотоцикле, прыгать с Тарзанки/парашюта и т.д. Против хождения в одно и тоже места условно каждый день (например смотреть закат).
Но поверьте — специалисты хорошие есть везде, и просиживатели штанов тоже.
Я себя отношу больше к продуктовикам — мне база важнее чем технологии умирающие быстрее чем проект. но это не значит что я буду сидеть на старом языке или не использовать хороший Фреймворк (и точно не значит, что я не в курсе новинок). Просто перед использованием нужно задать вопрос — а что мне это даст? И только если плюсы перевешивают минусы, только тогда использовать.
Но по факту, при смене работы и при приеме на работу я ни когда не смотрю на продукт/аутсорс — все зависит от компании/директора/коллектива.
Если в продуктовую команду на собес приходит человек и с горящими глазами рассказывает как он за год пользовался 50 технологиями, то обязательно задам вопрос — «а ты уверен, что хочешь к нам? У нас не будет кучи технологий, зато будут сложные архитектурные задачи.», а человек в праве сам решать интересно ему это или нет.
Если в аутсорс компанию приходит человек, но ничего не слышал о современных подходах, то также возникает вопрос — «а ты готов постоянно развиваться вширь?», и также человек должен решать сам.
А грамотный лид всегда будет держать хотябы одного разработчика развитого в ширь и хотябы одного в глубь.
Я тоже принимал участие причём в двух этапах.
То что было сделано во втором этапе — публичный поиск багов не улучшил ситуацию, а ухудшил. Сам телеграмм не проверял приложения почти все время, а вся проверка по итогу свелась к 5 минутному запуску приложения. Оценка осталась также субъективной (особенно в плане скорости работы), и никак не учитывала те баги которые нашли другие участники — например приложение с многочисленными крашами попало на 3 место.
Но в прочем — это их конкурс и они вправе делать что захотят.
От автора библиотеки — большое спасибо за совет. Что-то я не подумал об этом. Правда на то есть причины — apple в 3 версии языка избавился от того что несколько параметров и картёж от нескольких параметров это одно и тоже. И в 3 версии это работало, а в 4 что-то снова поправили и сломали, но как-то частично…
Собственно говоря, надо убедиться что в 3 версии со старым Xcode данный код также работает.
И ещё раз спасибо — написать #if в крайнем случае на версию всегда можно.
А в чем полезность заметки/статьи? В википедии написано тоже самое только подробней и точнее. А ещё есть чудесная книжка ООАД от Гради Буч- там не только определение, но и описание зачем оно нужно.
Но да инкапсуляциям как и почти все что связанно с хорошим тоном написания кода появилось давно — в годах этак 60, и соглашусь что это не каждый знает. Но! Скажем так — все придумали пол столетия назад, но пользоваться начали недавно, да и многие умные мысли из того времени не используются.
Да и какая разница когда оно появилось — важнее когда оно дошло до сообщества.
У нас в компании используется fastlane в связке с jenkins. Нажимаем одну кнопку "скомпилировать", всё компилируется и выкладывается на testflight.
До такого уровня как в статье мы конечно не дошли — текст всеже пишется в itunes connect, но сборка автоматизирована полностью.
На самом деле с fastlane интересная история — пользуемся мы им относительно давно, во всем разобраться не смогли, поэтому часть вещей делаем странным образом — например сертификаты мы просто копируем сами в определенные папки, а не пользуемся возможностяами fastlane (возможно зря). У нас он из серии — работает не трож :)
Но один раз настроить (тут самое тяжелое как всегда провижены) и дальше радоваться жизни и не знать горя — оно того стоит.
Да. К сожалению swift на самом деле имеет проблемы. Мы столкнулись у себя и с динамическими библиотеками, и с крашами Xcode (особенно на 2.x версиях), и с долгой компиляцией, и с отваливающимся интелисенс (кстати на obj-c он тоже отваливается), но несмотря на это я считаю это лучше чем писать на obj-c.
Сам язык при должном его понимании позволяет писать код быстрее, безопасней, короче и понятнее что в долгосрочной перспективе экономит уйму времени.
Помимо этого все забывают про то, что код на swift исполняется быстрее в части алгоритмов, нежели чем на obj-c.
На текущий момент не все так плохо:
Xcode падает редко( у меня 6 с obj-c падал чаще)
Интелисенс обычно после переключения файла оживает, но даже без него писать код становиться возможным, из-за более запоминаемого синтаксиса языка.
Непонятные ошибки редкость, и ща частую уже пройденный этап.
Инкрементальная сборка появилась, правда иногда перекомпилирует лишнее.
Осталась 2 проблемы:
Размер приложения, который разбухает на 20-25мб
Долгая загрузка (pre-main), к сожалению у нас в компании это не решили, но статья заставила задуматься о том, что возможно надо было разобраться в этом сильнее.
Кому что важнее то и выбирают. Если команда из 15 человек постоянна сталкивается с кодом любого другого человека, а обьем кода такой, что уже невозможно его весь помнить и приходится его читать, то я лично за swift. С другой стороны если вам важен размер и скорость загрузки, или отсутствуют разработчики способные разобраться в проблеме без google, то всеже лучше obj-c.
Ну я пишу начиная с 3 iOS. Кто-то еще раньше. Там есть mm файлы которые могут содержать код на двух языках objc и c++ — они выступают как обертки. Потом пишут на чистом с++. Причём для игр уже все давно написано: cocos2dx как самое популярное, он далеко не единственный.
При этом swift написан на С++ и может также вызывать с++ код.
Более того многое кроссплатформенные бизнес приложения имеют в ядре также С++. Точно знаю dropbox и 2gis все остальное менее известно.
Простой пример цикла в популярных паттернах наших :) Presenter знает о View, но View должна как миниум держать presenter. В некоторых случая должна иметь delegate. Конечно подобный цикл можно сделать и ручками, без DI.
Предположим есть экран — это объект A. Каждый раз когда заходишь на экран, он должен создаваться, и все его зависимости (B,C,D) тоже должны создаваться каждый раз. Но на объект D ссылаются B и С. Если сделать его lazy var, то D не пересоздастся при создании нового A. Если его сделать всегда создаваемым, то у B и С будут разные экземпляры D.
Я про случай когда вот такие функции должны возвращать один и тот-же объект:
resolve() -> A, resolve() -> B, resolve() -> C
.Я про вот эту модульность: "слабо связанные снаружи". Все остальное это "мы просто сделали куча фреймворков, и назвали модулями". Смысл модулей, в том чтобы максимально все скрыть, оставим минимум для взаимодействия (ну если это не куча вспомогательных функций), и не забывать при этом что в отличии от классов между ними связь в обе стороны не возможна на прямую.
Ну а теперь более конкретно, на примере экранных модулей. Есть модуль A и модуль B. С экрана из модуля A можно перейти на экран из модуля B. С экрана из модуля B можно перейти на экран из модуля A. Как делать будем? И как усложнение — реализаций каждого экрана может быть несколько, и только сам модуль знает какую реализацию когда использовать.
P.S. На все эти кейсы решение есть, но назвать эти решения удобными и понятными не всегда можно. Как говорится "плавали знаем" :)
Да вопросом много, и разных. Ответ будет большим, извиняюсь заранее. Для тех кому не хочется читать всё, советую прочитать пункт 5 — там поднята важная проблема описанного решения в статье.
Эту проблему я решал в своей библиотеке самой первой, так как до этого имел дело с Typhoon. Яб даже сказал — это причина возникновения библиотеки :) Вначале были исключения, но потом перешел на логирование и внятные fatalError. То есть, если что-то пошло не так, то есть относительно внятное описание, и с 80% вероятностью в описании будет указано место куда смотреть. Конечно, для этого надо уметь читать мой ломанный английский :D
По другим либам – все грустно, иначе я бы не стал писать свою. В прочем сейчас уже появились CompileTime либы: Weaver, needle. Они частично решают проблему – частично, так как графы зависимостей бывают сложными и с циклами, а это уже надо проверять отдельными функциями.
Почитать… ну "Чистая Архитектура" можно. Я еще люблю "ООАД" от Гради Буч. А так, каждое решение влияющее на большую часть приложения надо принимать командно, и желательно до того как начали его делать :)
В вашем случае не понятно, почему смена страны ломает зависимости. Возможно, у вас есть распространенная проблема, когда в зависимости случайно попадают и данные.
Из возможных проблем вижу лишь — у разных стран доступны разные экраны, и некоторые экраны выглядят настолько по разному, что у них много реализаций. Тут или умение работать с container-ами (кто мешает создать новый? :) ), или не подмешивать техническую бизнес логику в DI, и решать это в прикладном коде используя или аля фабрики, или обычные условия в router/coordinator/navigator.
Я бы склонял к последнему, так как, если подмешивать в DI бизнес логик, четвертая проблема, указанная вами, выльется в катастрофу.
Слегка с конца — в команде 25 человек, и более того я уходил с проекта на 2 года. Так что использование DI в проекте далеко от идеала :) Например, даже когда я был на проекте, и мы переходили на третью версию библиотеки, которая начала искать проблемные циклы, то оказалось, что у нас есть циклы длиннее 15 объектов, и при создании одного простого экрана, приходится создавать более 50 зависимостей. К слову сказать два года назад Swinject просто ломался на подобных графах, и просто крашился, из-за внутренних ошибок.
По скорости
Там видно, сколько занимает сам процесс регистраций, и сколько занимает процесс получения 50000 раз объекта, при разном количестве регистраций. Можно увидеть, что сам процесс регистрации зависимостей не линеен, и где-то на 4000 регистраций время станет не приятным. А вот получение объектов линейно зависит от количества регистраций, и даже на 4000 тысячах ещё приемлемое. Но я не представляю себе проекты даже с 2000 регистраций на Swift в ближайшие лет 5, а там и мощности будут быстрее, и сам язык быстрее (по профайлу самое медленное место в библиотеки это ARC), и у меня еще есть идеи как оптимизировать. Ну, и на чистом 'C' всегда можно написать.
"Как не тупить обычным разработчикам?" — Если решили чем-то пользоваться, то научитесь этим пользоваться. Вы же когда пишете на языке понимаете что пишите? Ну, я надеюсь :D Тоже самое и с библиотеками — прежде чем начать на них писать, их надо как и язык изучить, и разобраться. DI контейнер автоматически предполагает, что вы понимаете, зачем вам нужно внедрение зависимостей на проекте, а не "так сказал тим лид".
По практике скажу — это не самая простая тема, я проводил лекции внутри компаний, и видел, как люди не понимают концепцию. И это даже не зависит от опыта.
Все на ваш страх и риск. Предположим, вы писали на UIKit, а apple через 5 лет сказало — выпиливаем теперь только SwiftUI :D Вы можете уменьшить проблемы, если все будет отделено. Если у вас UIKit был только во View, а не оказывался случайно в базе данных (такое бывает), то проблема будет маленькой и решаемой.
Тоже самое и с библиотеками DI. Я выбрал концепцию "декларативный контейнер" и не долюбливаю концепции внедрения через атрибуты, аля Dagger2. В Swinject, DITranquillity при правильном использовании, код знающий о DI либе всегда отдельный, он никак не пересекается с другим. Соответственно подмена этого кода = подмене только этого кода, без необходимости искать по всему проекту. Но это в любом случае подмена, которая потребует времени.
Начнем с главного. DI либы аля моей и Swinject не представляют из себя никакой магии — это просто хорошо обернутый Dictionary. Как работает Dip не помню, я его код 2.5 года назад смотрел, и уже не помню по каким причинам решил больше не возвращаться. Соответственно если отбросить то, что библиотеки пишут люди и допускают разные ошибки, то оверхед не такой уж и большой, по сравнению с ручными вызовами.
"Зачем вообще решать эти проблемы в рантайме?". Я задам встречный вопрос — вы перешли к варианту 450 функций resolve(). Но не описали: каким образом у вас решаются циклические зависимости? Что делать если объекты не надо пересоздавать каждый раз? А если надо создавать каждый раз, но у вас ромбовидные зависимости A->B A->C B->D C->D где объект D должен будет создаться единожды, но на каждое получение объекта A, он разный? Что делать если объект реализует с 10 протоколов, и вы хотите чтобы он отдавался по всем этим протоколам? А обратная ситуация — один протокол, но много реализаций?
А теперь добавим модульность, и последние вопросы становятся еще интересней :)
Собственно говоря, я к чему. На проекте в 10-30к swift строчек кода, вполне можно обойтись концепцией DI без библиотек. Да где-то придется попотеть, вспомнить про Фабрику сервисов, а именно этот паттерн описан у вас, просто он использует возможности автовывода типов. Да это займет условных 1000 строчек, а с библиотекой бы заняло 100. Но это возможно, я так делал и все было хорошо. Кстати у вас точно не модульное приложение ;) Ну или вы не до конца описали GeneralAssembly — при модульности, его придется делать многоуровневым и с использованием протоколов.
Но как только размерность проекта выходит за рамки человеческого понимания — в нашем проекте уже никто не знает, как работает ядро, или как работает самый верхний роутинг, многое написано 4-5 лет назад и людьми которые уже давно уволись, но с тех пор код там не менялся. И такого кода много. C зависимостями у нас также — они очень сложные, они межмодульные. Многие зависимости отключаемые/включаемые. То есть выкинул модуль, собрал проект, и все кто зависел от этого модуля продолжат работу, просто без части функционала (Ну это в идеальном мире правда… все мы люди, и ошибки есть).
И вот тогда DI библиотеки начинают экономить уйму времени, и кода.
P.S. По причинам сложных зависимостей, я и делаю сейчас возможности "нарисовать" граф зависимостей — то есть библиотека будет выдавать граф, который в другой программе можно будет посмотреть визуально.
Не мог пройти мимо. Я создатель библиотеки DITranquillity, может видели статьи. И работаю на проекте где Регистраций больше 1000. И на проекте встала проблема времени запуска на 5c уже более двух лет назад. Из них 3.5 секунды был DI. В силу чего в 3 версии своей Либы я делал упор на скорость, и время снизилось до 0.02 секунд на старте для DI на томже 5c.
Так что выводы не верные — библиотеки с исполнением в реальном времени могут все делать быстро, просто никто их не оптимизирует. Я в своей либе этим обеспокоился и проблем видимых не стало 1000регистраций и 10к получения объекта работают быстрее чем создание вьюшки.
А у swinject кстати график экспонициальный… То есть чем больше регистраций, тем больше время получения объекта, и это время где-то на 400 регистраций становиться уже не совместимыми с жизнью.
Ну и ссылочка на либу. Там можно найти performance тесты и проект для сравнения скорости со swinject. https://github.com/ivlevAstef/DITranquillity
Ну и как участник данного конкурса, я знаю что был критерий к скорости работы, и вывод графиков не был единственной проблемой — приходилось оптимизировать и остальные вьюшки, например с датами и цифрами.
Вопрос- как по ощущениям скорость работы swiftUI? Эквивалентна констрейнам, или же медленней? Возможно даже эквивалентна тому если бы писали все на фреймах?
P.S. Сам к сожалению дальше стандартного туториал от apple не продвинулся, и больше всего меня расстроила в swiftUI навигация между экранами, если использовать обычный navigation bar — слишком жёсткая привязка к экрану на который осуществляется переход.
Не понятно почему простейшая работа с generic-ами стала продвинутым курсов — в swiftbook это есть, и расписано подробней.
При этом в статье нет рассказа о всяких изощрённых использований generic, о том как они интересно с автовывоводом типов сочетаются, ну и о том как внутри работает.
На самом деле соглашусь с утверждением, про двусторонний список. Но тут оно оправдано — removeLast не удалось бы реализовать за O(1) без двухстороннего списка, и при таком способе реализации.
Сразу добавлю к другой части вашего комментария, так-как по теме:
Думаю в силу того, что реализуется "связный список", у которого одно из преимуществ "структурная гибкость" (взял из вики). Что на практике всегда выражалось тем, что можно выделить память под все свои элементы почти всегда, в отличии от массива на который памяти не будет в силу фрагментации.
А вот почему не нужно его всегда копировать, при попытке изменить содержимое, если есть несколько ссылок на список не совсем понятно. Да возможно компилятор языка это делает чуть умнее, и учитывает некоторые другие особенности (не буду утверждать — ни разу не смотрел какие оптимизации там есть на этот счет). Но автор не влазил в работу компилятора, и реализовал вариант, который работает согласно принципу copy-on-write, хоть и было кривенько реализовано.
Возможно некоторые аспекты в статье на самом деле не аргументированы, но думаю автору который говорит про "школу" можно такое простить… Яб даже сказал, я удивлен, что в школе учат swift, в прочем как и любой другой язык...
P.S. Про сложность — если учитывать copy-on-write, и тот факт, что память надо иногда перевыделять, то у apple во всей документации описание сложности не верное. Ведь даже добавление элемента в конец массива, не всегда O(1) — иногда надо сделать realloc, что требует некоторого времени. Иногда realloc требует O(N), если вдруг память идущая дальше уже занята.
Начал писать код в комментарии, и вспомнил что да на самом деле использовать Node как протокол не получится.
Про indirect enum: https://www.hackingwithswift.com/example-code/language/what-are-indirect-enums.
Реализовать так можно (отбросим вопросы про удобство использования в сторону), и оно будет работать.
Но:
Простой пример:
В этом примере
-1
не будет выведен.Итог понятен — LinkedList через indirect enum (или struct), не будет являются LinkedList ибо не имеет его преимуществ. С тем же успехом (и даже лучше) можно просто все запихнуть в массив.
К сожалению это решение в данном случае наиболее предпочтительное — не пораждает лишних абстракций.
Но можно сделать протокол который бы имел только getter-ы, и отдавать не экземпляр уже класса, а экземпляр этого протокола во всех методах.
Плюсы первого решения: Не пораждает лишних абстракций, нельзя обойти.
Минусы первого решения: Все в одном файле.
Плюсы второго решения: Можно разделить на файлы.
Минусы второго решения: Лишняя абстракция, легко обходится оператором
as
.Таковы реалии swift и иногда не хватает дружественных классов из С++. В данном случае хранить эти объекты в одном файле ничего критично не имеет, но иногда это усложняет чтение кода, но поделить нельзя — так как хочешь закрыть реализацию.
P.S. Есть третье решение — выделить все в отдельный модуль/проект. Тогда у свойств может быть модификатор доступа:
public internal(set)
и все проблемы уходят.На самом деле у меня к реализации есть замечание:
Меня смутил WeakReference а ровном месте. То есть протокол принуждаем людей сразуже использовать weak ссылку, а не просто получить первый элемент — что обычно и хочется.
Все это ради Copy-on-write?.. ну а я сделаю вот так:
let node = list.first.node
то есть решение еще и не назовешь надежным. А потом так:let node = list.first.node.next
и поведение резко изменится.Но давайте учить людей правильно — внешний интерфейс всегда важнее технических проблем. А в данном случае техническая проблема решается легко, да и делает использование надежней:
И вот этой переменной уже можно пользоваться, для получения количества копий именно структуры. При изменении смотрим количество ссылок, и если больше одной, то делаем тоже самое + пересоздаем переменную (дабы каждый раз не копировать).
P.S. ну и я закрываю глаза, на тот факт, что никто не мешает получить node и поиграться таким образом, что весь лист будет сломан — у Node то нет полей доступа, аля
private(set)
P.P.S. не знаю почему, но ваша статья меня привлекла — рассказано в самом деле хорошо. Да и LinkedList я тоже люблю — на нем много какие особенности языка всплывают.
3) Ну… я лично бы не верил. Я даже себе на собеседовании бы не верил :).
И встречал людей с N проектами N лет опыта, но которые не понимаю разницы между этими всеми вещями, даже на базовом уровне.
Хотя меня тоже бы расстроили подобные вопросы на собеседовании, но только если не было бы развития вопроса в глубь. Например вопрос "отличие reference type от value type" — хороший. На него ответит junior и senior по разному, и сломаются они на разных стадиях развития этого вопроса. Ибо к нему потом сверху идут weak/unowned/strong, захват, замыкания, аналоги в obj-c. Короче тема хорошо развивается, и если человек на самом деле крут, то доходит до разговора — а как это внутри устроено.
4) Ну в общем счете да, и я даже не настаиваю как писать. Но люди так пишут до тех пор пока не ошибутся в этом условии по несколько раз, хотя кто-то продолжает так писать.
guard let
читается легко и это его назначение — разворот области видимости, аguard без let
имеет по факту отрицание в себе, и если условие с отрицанием, да еще и не одно условие, то начинаются серьезные проблемы.В прочем если отстранится от swift то
guard без let
по стилю мышления похож наassert
.Не знаю как кто, но я еще не видел людей которые бы assert-ы всегда писали с первого раза верно — в них часто условие при написании инвертят, и позже исправляют.
Хотя разговор в этом плане похож на табы vs пробелы :) Так как есть человек с самого первого дня пишет на swift с guard, то ему будет одинаково легко читать как guard так и if.
1. Почему Avito и Yandex? Я ничего против них не имею, но полно и других.
2. Когда я собеседовался в Yandex на iOS, то меня почему-то спрашивали именно те вопросы которые попали в категорию «так себе перспективы»
3. Почему вдруг эти вопросы плохие? Мне на собеседованиях один кандидат из 20 способен не только сказать заученную фразу, но и на практике доказать, что он понимает её… остальные не способны без компилятора сказать, что напишет код из 5 простых строчек и как поведение меняется в зависимости от типа.
Про Solid промолчу — не люблю этот вопрос, и не обращаю внимание если человек не знает что это, но когда люди говорят, что знаю… см начало пункта.
А ну и замечание по коду — писать ‘guard a != b else’ это кощунство над людьми которые будут читать — два отрицания за место обычного: ‘if a==b’.
P.S. с тем что знание алгоритмов полезно я соглашусь. Правда стоит начать с более важных вещей, например что такое словарь, массив, множество — сложность операций, как реализовано в iOS. Вроде бы этим пользуются каждый день, но как устроено мало кто знает.
И там и там я видел плохой код, и там и там были проблемы с менеджементом и мамонты технологические и т.п.
Но деление между аутсорс и продукт есть — но оно заключается в личных предпочтениях. Кому-то Интересно постоянно что-то новое, новые подходы/языки/проекты и они хотят драйва. А кому-то интересно укреплять базу, развиваться в глубь. Это как кататься на мотоцикле, прыгать с Тарзанки/парашюта и т.д. Против хождения в одно и тоже места условно каждый день (например смотреть закат).
Но поверьте — специалисты хорошие есть везде, и просиживатели штанов тоже.
Я себя отношу больше к продуктовикам — мне база важнее чем технологии умирающие быстрее чем проект. но это не значит что я буду сидеть на старом языке или не использовать хороший Фреймворк (и точно не значит, что я не в курсе новинок). Просто перед использованием нужно задать вопрос — а что мне это даст? И только если плюсы перевешивают минусы, только тогда использовать.
Но по факту, при смене работы и при приеме на работу я ни когда не смотрю на продукт/аутсорс — все зависит от компании/директора/коллектива.
Если в продуктовую команду на собес приходит человек и с горящими глазами рассказывает как он за год пользовался 50 технологиями, то обязательно задам вопрос — «а ты уверен, что хочешь к нам? У нас не будет кучи технологий, зато будут сложные архитектурные задачи.», а человек в праве сам решать интересно ему это или нет.
Если в аутсорс компанию приходит человек, но ничего не слышал о современных подходах, то также возникает вопрос — «а ты готов постоянно развиваться вширь?», и также человек должен решать сам.
А грамотный лид всегда будет держать хотябы одного разработчика развитого в ширь и хотябы одного в глубь.
Снова много букв…
То что было сделано во втором этапе — публичный поиск багов не улучшил ситуацию, а ухудшил. Сам телеграмм не проверял приложения почти все время, а вся проверка по итогу свелась к 5 минутному запуску приложения. Оценка осталась также субъективной (особенно в плане скорости работы), и никак не учитывала те баги которые нашли другие участники — например приложение с многочисленными крашами попало на 3 место.
Но в прочем — это их конкурс и они вправе делать что захотят.
Собственно говоря, надо убедиться что в 3 версии со старым Xcode данный код также работает.
И ещё раз спасибо — написать #if в крайнем случае на версию всегда можно.
Но да инкапсуляциям как и почти все что связанно с хорошим тоном написания кода появилось давно — в годах этак 60, и соглашусь что это не каждый знает. Но! Скажем так — все придумали пол столетия назад, но пользоваться начали недавно, да и многие умные мысли из того времени не используются.
Да и какая разница когда оно появилось — важнее когда оно дошло до сообщества.
Да я тоже слегка был огорчен. Но swift3.1 не сказать что сильно устаревший на текущий момент :)
С Unix тоже не все так просто — запустить не на ubuntu (я пробывал на CentOS) тоже почти нереально.
Как я давно этого ждал, ну всмысле стратегию…
Осталось swift поддержать быстренько и будет лучший RAIC из всех :)
У нас в компании используется fastlane в связке с jenkins. Нажимаем одну кнопку "скомпилировать", всё компилируется и выкладывается на testflight.
До такого уровня как в статье мы конечно не дошли — текст всеже пишется в itunes connect, но сборка автоматизирована полностью.
На самом деле с fastlane интересная история — пользуемся мы им относительно давно, во всем разобраться не смогли, поэтому часть вещей делаем странным образом — например сертификаты мы просто копируем сами в определенные папки, а не пользуемся возможностяами fastlane (возможно зря). У нас он из серии — работает не трож :)
Но один раз настроить (тут самое тяжелое как всегда провижены) и дальше радоваться жизни и не знать горя — оно того стоит.
Сам язык при должном его понимании позволяет писать код быстрее, безопасней, короче и понятнее что в долгосрочной перспективе экономит уйму времени.
Помимо этого все забывают про то, что код на swift исполняется быстрее в части алгоритмов, нежели чем на obj-c.
На текущий момент не все так плохо:
Xcode падает редко( у меня 6 с obj-c падал чаще)
Интелисенс обычно после переключения файла оживает, но даже без него писать код становиться возможным, из-за более запоминаемого синтаксиса языка.
Непонятные ошибки редкость, и ща частую уже пройденный этап.
Инкрементальная сборка появилась, правда иногда перекомпилирует лишнее.
Осталась 2 проблемы:
Размер приложения, который разбухает на 20-25мб
Долгая загрузка (pre-main), к сожалению у нас в компании это не решили, но статья заставила задуматься о том, что возможно надо было разобраться в этом сильнее.
Кому что важнее то и выбирают. Если команда из 15 человек постоянна сталкивается с кодом любого другого человека, а обьем кода такой, что уже невозможно его весь помнить и приходится его читать, то я лично за swift. С другой стороны если вам важен размер и скорость загрузки, или отсутствуют разработчики способные разобраться в проблеме без google, то всеже лучше obj-c.
При этом swift написан на С++ и может также вызывать с++ код.
Более того многое кроссплатформенные бизнес приложения имеют в ядре также С++. Точно знаю dropbox и 2gis все остальное менее известно.