Результати пошуку
ITVDN: курси програмування
Відеокурси з
програмування
Підписка

300+ курсів за популярними IT-напрямками

Вибери свою IT спеціальність

Підписка
Підписка

300+ курсів за популярними IT-напрямками

Результати пошуку за запитом: обучение c*
Значні зміни у збирачі сміття в .NET 4.6.2

Автор: Maoni Stephens

В этой записи нашего блога мы хотели бы обсудить некоторые значительные изменения, которые были сделаны в сборщике мусора (GC от англ. garbage collector) .NET 4.6.х. Мы рекомендуем Вам использовать самую последнюю версию, 4.6.2. Наш главный разработчик, Маони Стивенс описала улучшения, которые были реализованы в 4.6.2. фреймворке. Эти изменения были сделаны для того, чтобы улучшить производительность фреймворка и позволить сборщику мусора работать более эффективно. Как сделать закрепление объектов более эффективным В предыдущих версиях .NET Framework, когда объект определялся как прошедший проверку закреплённый объект, мы не могли перемещать этот объект и смежные ему существующие объекты. Поскольку мы оставляем закрепленные объекты в поколениях 0 и 1 (таким образом вы сможете вскоре использовать свободное пространство между ними), это значит, что нам необходимо просматривать их во время выполнения сборки мусора. Если бы многие другие объекты вокруг закрепленных объектов пережили бы процесс сборки мусора, тогда время сбора мусора в 0 и 1 поколениях могло бы быть больше желаемого. Начиная с версии 4.6.2, это ограничение было отменено, так что мы можем собирать смежные существующие объекты вокруг закрепленных объектов. Во время тестирования мы заметили впечатляющие улучшения времени сбора в сценариях, где сборщик мусора искусственным образом закреплял многие объекты. На рисунке Before два не закрепленных объекта собираются, но не закрепленный объект рядом с закрепленным остается в gen0. В 4.6.х, это ограничение было отменено, так что мы можем переносить смежные существующие объекты вокруг закрепленных. Во время тестирования мы заметили впечатляющие улучшения времени сбора в сценариях, где сборщик мусора искусственным образом закреплял многие объекты. На рисунке After мы можем увидеть, что не закрепленный объект, следующий по отношению к закрепленному, переходит в следующее поколение: Если вы закрепляете много объектов, например, при длительных операциях ввода-ввывода, при этом наблюдаете длинные паузы сборщика мусора, вы, скорее всего, извлечете выгоду из этого изменения. Более эффективное использование свободного пространства во втором поколении В предшествующих версиях фреймворка при переносе объектов из поколения 1 в поколение 2 использовалась техника first fit, а это означало, что GC не использовал память, которая не подходила для хранения объекта. Эта техника приводила к потере памяти. На рисунке Before, мы видим, что F0 область освобождена, потому что была слишком мала для того , чтобы сохранить выживший объект S. Мы смогли поместить S в F1 при этом часть F1 остается (для дальнейшего уплотнения памяти при переносе из gen1). Чтобы улучшить этот подход и более эффективно использовать память, мы ввели список, в котором мы размещаем объекты слева направо в свободном пространстве в сегменте, который подходит по размеру. Мы должны очень аккуратно относиться к использованию маленьких сегментов, так как их может быть очень много. Также, мы работали над политиками определения сегмента, который следует попробовать использовать для хранения объекта, так как мі не хотим продлевать сборку мусора в первом поколении поиском свободного подходящего места в памяти. Мы хотим продолжать делать сборку мусора в фоновом режиме где это возможно, чтобы более эффективно использовать свободное пространство. На картинке After F0 остается в свободном списке и может быть использован для уплотнения памяти объектами из gen1. И мы сразу пробуем поместить S в F1. Если вы наблюдаете увеличение памяти gen2, то одной из причин может быть то, что у вас закончились элементы в свободно списке. Следовательно, продвижение оставшихся объектов из gen1 потребует увеличение размера gen2. Для сценариев, которым помогает это изменение, вы увидите, что размер gen2 увеличивается намного медленнее, и мы сможем разместить больше коллекций gen1 для каждой коллекции gen2. Это также означает, что вы будете видеть блокировку gen2 реже, потому что, когда куча становится слишком большой, это приводит к чрезмерной загрузке памяти, мы будем блокировать gen2 и выполнять уплотнение. В наших тестах, мы наблюдали сценарии, в которых соотношение количества сборов в поколениях gen1 и gen2 возросло в 20 к 1 -  более чем 200 сборам для поколения gen1 для каждого сбора в поколении gen2. Резюме Если вы столкнулись с ситуациями, описанными выше, вам обязательно нужно попробовать .NET 4.6.2, чтобы получить преимущества улучшений в сборщике мусора. Источник
4 найкращі блоги з front-end розробки

Автор: Anita Soczka

Интернет, а соответственно и сфера веб разработки, быстро и постоянно изменяется. Если ты front-end разработчик, тогда скорее всего, ты знаешь, что нужно быть в курсе новостей (уж поверь!), уметь работать с новыми инструментами, тенденциями и бизнес-процессами. Все самое важное ты сможешь найти в Интернете, но будь осторожен, поскольку в сети много ненужного мусора. Какие же блоги по front-end технологиям наилучшие? Ежедневно публикуются сотни постов и статей. Все они беспорядочны и можно сойти с ума, пытаясь поспеть за всем. Прочитать их все просто невозможно. Более того, не нужно читать все в подряд.  Именно поэтому я попросил людей из компании Merixstudio выделить лучшие технические блоги по front-end разработке, которые стоит читать, и они собрали лучшие ресурсы новостей и тенденций в мире веб разработки. Ранее я уже публиковал статью о самых влиятельных блогах, ориентированных на технологии и веб разработку в целом. На этот раз буду подробно рассказывать о лучших front-end ресурсах (порядок сайтов случайный). Speckyboy Сайт позиционирует себя как журнал веб дизайна, но Paul Andrew – основатель Speckyboy – концентрируется не только на дизайн-ресурсах, но также предоставляет полезную информацию о новейших веб технологиях. Блог, безусловно, является отличным источником информации для ежедневного использования front-end разработчиком – блог предлагает отличные посты, справочники, разные источники информации и мотивирующий контент со всего мира. • #11,771 по рейтингу Alexa Rank* • 54,143 подписчиков на Facebook • 75,150 подписчиков на Twitter CSS-Tricks Если ты хочешь улучшить свои навыки в области веб дизайна и веб разработки, тебе стоит подписаться на CSS-Tricks, который ведет интернет-гуру Chris Coyier. Это кладезь знаний по веб разработке, который непременно повысит уровень представляемого тобой веб контента. CSS-Tricks в основном фокусируется на темах, связанных с CSS. Блог предоставляет фрагменты кода, «прорывные» статьи, видео, учебные курсы, подкасты и многое другое. • #1,253 по рейтингу Alexa Rank* • 67,776 подписчиков на Facebook • 298,126 подписчиков на Twitter Codrops Codrops можно назвать одним из самых новых и быстро растущих сайтов с документацией в сфере информационных технологий. Каждый front-end разработчик и веб дизайнер может найти много полезных материалов и фрагментов кода на Сodrops. Он также охватывает  общие темы веб-разработки и веб-дизайна. Блог ведется всего лишь двумя дизайнерами – фанатами своего дела – Manoela Ilic и Pedro Botelho. Ко всем преимуществам блога можно добавить красивое оформление сайта. Этот блог стоит посещать, чтобы обучаться новым хитростям и тенденциям в индустрии. • #3,197 по рейтингу Alexa Rank* • 89,619 подписчиков на Facebook • 164,845 подписчиков на Twitter Todd Motto’s blog Todd работает на позиции Developer Advocate в компании Telerik. Todd – основатель Ultimate Angular, а также Developer Expert в Google, спикер на конференциях и сторонник проектов с открытым кодом. В целом, он пишет на Angular и JavaScript. Его самоуверенный гид по командному стилю работы с AngularJS приобрел очень большую популярность на Reddit и Hacker News. Его блог отличный ресурс для подпитки собственных знаний, особенно, если ты заинтересован именно в таких технологиях, как Angular и JavaScript. • #44,788 по рейтингу Alexa Rank* • 1140 подписчиков на Facebook • 30,396 подписчиков на Twitter Очень важно, чтобы ты нашел свой собственный, уникальный способ отслеживать нужную тебе информацию. Так что, читай книги, журналы, отслеживай в Twitter людей, которые создают новые тенденции, смотри видеоматериалы, посещай конференции, общайся с единомышленниками и создавай новое! *Alexa Rank – глобальный рейтинг трафика, учитывающий, как число посетителей, так и число просмотров страниц. Предоставляется компанией Alexa Internet. Оригинал: www.merixstudio.com.
Як правильно скласти ТЗ англійською

Автор: Віктор Осадчий

Составляем ТЗ на английском О важности правильно составленного и правильно понятого технического задания в работе любой IT-команды говорить не стоит. Вопрос в том, как правильно написать его на английском. А если вы работаете в международной команде, это потребуется обязательно. Без паники! EnglishDom расскажет, как написать ТЗ на английском и не наделать ошибок. Итак, обо всех важных моментах - по порядку. SRS structure. Структура ТЗ В техзадании обязательно присутствуют следующие разделы: introduction - вступление, overall description - общее описание, specific requirements - специфические требования, а также возможны и appendices - приложения. Поговорим о каждом разделе отдельно. В introduction, помимо purpose - цели, может быть включен еще и scope - объем работ, definitions - термины, специфичные и принятые для этого ТЗ, и раздел overview, который дает общее представление о наполнении и структуре будущего проекта - identify the product, describe the content and the structure. Overall description - это раздел, который включает многое: product perspective - функционал будущего продукта, который описывает все внешние интерфейсы - describes all external interfaces, functions - главные функции продукта (summary of major functions), constraints, or anything that limits our options - возможные ограничения, use cases - сценарии использования. specific requirements - все, что можно назвать требованиями, идет в этот раздел. Собственно, это и есть его основной частью - body of the document. Итак, структура ТЗ в общем виде должна выглядеть так: Introduction: purpose / scope / definitions Overall description: product perspective / functions  / constraints / use cases  Specific requirements: all the possible requirements = body of the document Useful phrases: identify / limit options, content, structure, product, external interfaces Overview is a section that identifies the product and describes the content and the structure. - Общее представление - это раздел, который определяет проект и описывает наполнение и структуру. Anything that limits our options is described in the constraints. - Все, что как-либо лимитирует варианты действий, указано в разделе “ограничения”. All the possible requirements usually identify the body of the document. - Все возможные требования определяют главную часть документа. Характеристики  ТЗ Есть несколько ключевых характеристик ТЗ. Первое - оно должно быть correct - правильным, то есть up to the required standards for the quality, database integrity and so on - соответствовать требуемым стандартам по качеству, целостности базы данных и т.д. Второе - unambiguous and complete  - недвусмысленным и полным, чтобы четко описать проект и его функционал - describe what is the future project created for. Третье - consistent and detailed - последовательным и детальным, подробно описывающее, каким будет взаимодействие с людьми, оборудованием и другими внешними интерфейсами - how it interacts with people, hardware and other external interfaces. Четвертое - modifiable and traceable - поддающееся изменениям и их отслеживанию, то есть the one you can edit and control. Ряд фраз, которые вам пригодятся: imposed on an implementation - обязательный к внедрению, main considerations - ключевые факторы, operation environment - рабочая среда. Итак, ТЗ должно быть: Correct: up to the required standards for the quality / database integrity Unambiguous and complete: describe what is the future project created for Consistent and detailed: how it interacts with people, hardware and other external interfaces Modifiable and traceable: the one you can edit and control Полезные фразы: imposed on an implementation / main considerations / operation environment. SRS typical mistakes. Как не испортить ТЗ Столько деталей и нюансов, подпунктов, что сложно не ошибиться. Мы расскажем, чего стоит избегать. Первое: ambiguity - неясность. don’t give too much or too little information that may not be verified - не стоит давать чересчур много или мало информации, которую, к тому же, еще и проверить нельзя. Второе: requirements on users - требования к пользователям. Запомните, you can only assume what user will do - нельзя заставить пользователя, можно только предположить, что он будет делать. Третье: unnecessary or invented terms - термины, которые не нужны или выдуманы. Avoid any extra terms if you can say it simple - избегайте лишней терминологии, если все можно сказать проще. Бесполезные и ненужные в ТЗ вещи: forward reference - ссылка наперед, contradiction - противоречие, noise - лишняя информация, silence - недостаточное описание. В общем, придерживайтесь структуры, избегайте лишней информации, используйте ключевые фразы - и ваше ТЗ будет идеальным! Если же вы сомневаетесь в своем знании английского - в EnglishDom есть специальный курс “Английский для IT”, где вы сможете подтянуть все важные для it-шника темы. Научитесь писать резюме и CV, проходимть собеседования, обсуждать проекты и проводить переговоры, переписываться в чате, писать ТЗ и деловые письма, понимать носителей языка и читать зарубежные блоги.
10 секретів ідеального коду

Автор: Nicolas Baptiste

Взято с настоящего поля битвы: работы Спустя практически 10 лет работы на Javascript мне захотелось поделиться своими ежедневными секретами написания кода. Многие советы были позаимствованы у моих коллег, из книг или видео. Такие приёмы не должны оставаться в тайне, поэтому я ими и хочу поделиться!   1. Меньше кода = меньше багов Это может прозвучать тривиально, но всегда держите это в уме. Любую часть можно убрать, даже не сомневайтесь! Просто удалите. Неработающий код, неиспользуемые переменные, лишние скобки: каждый малейший знак учитывается. 2. Развивайте читабельность кода Это дополнение к пункту 1. Если хочется сократить код, всегда нужно помнить о том, что рано или поздно код будет прочитан другими людьми. Может, кто-то другой, может, через несколько месяцев, но будет очень неприятно, если придется потратить больше времени на разбор кода, чем было потрачено на его написание. Запомните: код всегда важнее, чем комментарии или документация. Следите за переменными и именами функций, они должны быть достаточно наглядными, чтобы потом не объяснять их в комментариях. 3. Не пугайтесь функций, возвращающих функции Когда я начал кодить на javascript, мне не нравился метод написания кода с использованием функций, возвращающих функции, потому что, казалось, этому сложно следовать в процессе выполнения. Но этот шаблон, довольно мощный и часто используемый, называется замыканием. Это действительно полезная функция и, как только вы используете её, она значительно облегчает структуру кода. И это первый шаг для погружения в функциональное программирование. 4. Извлекание частей Функция становится слишком большой? Сделайте из нескольких частей отдельные функции! Не совсем понятно? Выделите их в переменную и дайте ей понятное имя. Такой метод, конечно, схож со вторым пунктом, но это сделает ваш код более читабельным. А знаете ли Вы, что Ваша среда разработки может помочь? Ctrl + alt + v выражение извлекается в Webstorm и потом выделяется в переменную. Ctrl + alt + m сделает из него функцию. 5. Переверните мышление Это первый шаг перед TDD. Скорее всего, вы всегда знаете, чего хотите достичь. Так начните с конца, напишите ожидаемый результат и пишите код в обратном порядке, чтобы получить то, что хотите. Если будете писать в прямом порядке, вероятней всего, Вы начнете писать и проверять, работает ли оно. Поменяйте, проверьте, напишите что-то другое, проверьте и…. Возможно, это даже не особо полезно для достижения Вашей цели. Но такое мышление поможет сфокусироваться и потратить меньше времени. 6. Исключите условные операторы Если для функции у вас на уме принцип единственной ответственности, условным операторам тут не место. Они создают ветвления в потоке выполнения. И один маленький if позволит другим разработчикам добавить всё больше логики в него. Я считаю, что условные операторы if вообще не должны использоваться без else, а их размещение внутри другого условного оператора нужно полностью запретить! Опять же, такой подход делает код более сложным, и часто оказывается, что функция выполняет больше, чем нужно. Как же от этого избавиться? Смотрите следующий пункт. 7. Используйте lodash_.get вместо проверки null/undefined Очень часто, анализируя код, я вижу использование ifs для проверки null или undefined значений. Но без использования else! То есть идёт работа только с одним путём, а другой путь остаётся местом загадок (и багов!). Тут на помощь приходит Lodash (лучшее, написанное когда-либо на javascript) с его замечательной функцией get. Её первым аргументом является объект, над которым вы работаете, вторым – путь к дочерним атрибутам, которые вы хотите получить, и третьим является значение по умолчанию в случае с undefined. Два преимущества использования всего одной функции! 8. Используйте тернарные операторы Кто-то может поспорить насчёт их читабельности, но если правильно извлекать функции, они станут превосходным инструментом. Они также заставят Вас сотрудничать с else case. Плюс, они не позволяют содержать в себе больше кода. Чем меньше ваш код похож на смертельную пирамиду, тем лучше. 9. Никаких больше for loops, только map, reduce и filter Мы, наконец-то, подошли тут к теме функционального программирования, так как эти методы являются основой этого стиля. Хоть названия или теории не особо известны, они действительно помогут избежать тяжело читаемого императивного стиля для выражений for. Эти методы сейчас уже встроены в большинство браузеров, так что используйте их! Но если вам всё же нужна совместимость, можно использовать lodash аналоги. Итак, когда их использовать? Выражение for, имеющее дело со всеми точками входа – это map for + if , вероятно, filter sum или accumulation -  это просто reduce  С помощью сочетания этих методов вы можете сделать много работы в хорошей и краткой форме. 10. Читайте исходники Если документации недостаточно - читайте исходники! Это поможет Вам узнать, как их использовать и даст неплохое представление об их качестве. Плюс, некоторые библиотеки имеют исходники, встроенные в их документацию. Так с ними даже быстрее можно ознакомиться. С нашей IDE можно часто обращаться к источнику с помощью ctrl + clic на имя метода. Это отличный способ убрать магию и набраться вдохновения для собственного кода!   В заключение хотелось бы сказать, что это те принципы, которые помогают лично мне. Жаль, что я не знал их раньше, например, в школе… Зато ты знаешь их сейчас. И ими можно пользоваться практически в любом контексте. Некоторые из них распространяются не только на javascript, так что пользуйтесь ими где угодно :) Также хотелось бы обратить ваше внимание на великого Uncle Bob и его сайт . Вам определённо стоит посмотреть эти видео, даже несмотря на то, что они по большей части касаются Java, идеи применимы и в Javascript. Переведено с источника.
Введення в розробку програм під iOS. Частина 7

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы: выполним рефакторинг проекта “Warehouse” под “iOS”. Создадим удобную структуру расположения модулей в проекте. научимся пользоваться библиотекой “Alamofire”. научимся сохранять объекты в настройки телефона и извлекать их. реализуем функциональность для работы со всеми сервисами, созданными в данном уроке. Откройте проект “Warehouse” под iOS, который мы делали на протяжении всех уроков. Запросы на сервер можно отправлять синхронно и асинхронно. Основное требование Apple, чтобы приложение не зависало и большие операции выполнялись в фоновом режиме. Представим ситуацию: у нас мобильный интернет “GPRS EDGE”, скорость соединения около 54 кб/сек. Если отправлять запрос на получение данных на сервер синхронно, мы блокируем работу приложения и пользователь вынужден ждать. Наша задача - дать пользователю выбор, ждать или нет. Поэтому лучше всего отправлять такой запрос асинхронно. На сегодняшний день существует такая библиотека “Alamofire”, где собраны лучшие практики асинхронного программирования и все оптимизировано за нас. Откройте браузер и перейдите по адресу: “https://github.com/Alamofire/Alamofire”. На сайте нажмите кнопку “Clone of download” и в появившемся окне нажмите “Download Zip”. Когда скачается архив, распакуйте его. Выделите файл “Alamofire.xcodeproj” и папку “Source” и скопируйте их в папку проекта “Warehouse”. Перейдите в проект “Warehouse”. В панели навигации выполните контекстное меню по папке “Warehouse” и в контекстном меню выберите “Add files to 'Warehouse'...”. В открывшемся диалоговом окне выбора файлов  выберите файл “Alamofire.xcodeproj”. Выполните в меню “Product -> Build”, чтобы собрать проект. Затем выделите в панели навигации папку “Warehouse”, в области содержимого откроются свойства проекта. Откройте вкладку “General”, пролистайте область содержимого в самый низ. Установите свойство “Embedded binaries”, нажмите плюсик. В появившемся диалоговом окне, выберите “Alamofire.Framework.iOS” и нажмите кнопку “Add”. Результат должен получиться таким. Выполните в меню “Product -> Build”, чтобы собрать проект. Итак, что мы только что сделали. Мы подключили исходники библиотеки “Alamofire” в проект “Warehouse”. Можно было это сделать и отдельным проектом, но так проще для восприятия. Затем мы подключили сборку “Alamofire.framework.iOS” в приложение “Warehouse”  для возможности обращения к классам данной библиотеки из нашего проекта. Теперь нам надо создать модели данных, в которые мы будем сохранять JSON объекты, полученные с сервера. Также необходимо предусмотреть удобство сохранения и извлечения данных, поскольку запросы будут асинхронными и просто вернуть нужный экземпляр из метода будет проблематично. Именно поэтому мы поступим следующим образом. Отправляем запрос на сервер асинхронно. В метод отправки  добавляем callback метод, который вызовется  после того, как модель была успешно сохранена в настройки телефона. Ждем ответа. Когда ответ пришел, десериализуем объект JSON в нужную модель данных, объявленную в приложении. Сохраняем заполненную модель в настройки телефона. В контроллере, из которого был отправлен запрос, в callback методе  извлекаем модель из настроек телефона и инициализируем данными элементы управления. Создайте следующую структуру папок в панели навигации. В папку “Warehouse”  были добавлены папки “Controllers”, “Protocols”, “Models”, “Services”. В папку “Controllers”  были добавлены папки “Users”, “Registration”, “Authorization”, “Supplies”. В папке “Controllers”  будут храниться модули контроллеров. В папке “Protocols”  будут храниться модули протоколов. В папке “Models”  будут храниться модули моделей данных. В папке “Services”  будут храниться модули обращения к REST сервисам. Сейчас нам необходимо провести некоторую подготовку, перед тем как начать реализовывать приложение. Пока, давайте,  добавим нужные модули. Сам по себе добавленный модуль ничего не делает. Перетащите в папку “Controllers / Supplies”  модули “SuppliesViewController.swift”, “ProductViewController.swift”. Перетащите в папку “Controllers / Authorization”  модуль “ViewController.swift”. Добавьте в папку “Controllers / Registration”  модуль “RegistrationViewController.swift”. Добавьте в папку “Controllers / Supplies”  модуль “ImageCollectionViewController.swift”. Добавьте в папку “Controllers / Users”  модули “UsersViewController.swift”, “UserItemViewController.swift”. Добавьте в папку “Protocols”  модуль “SelectedImageProtocol.swift”. Добавьте в папку “Models”  модули “ProductModel.swift”, “UserModel.swift”. Добавьте в папку “Services”  модули “UserData.swift”, “ProductData.swift”, “Services.swift”, “AuthorizationService.swift”, “ProductService.swift”, “UserService.swift”. У вас должна получиться следующая структура. Как видите, проект стал больше. Он станет еще больше, как только мы заполним добавленные модули. Откройте модуль “Models / UserModel.swift”. Заполните его, как показано ниже. В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя,  полученный от сервера. В классе “UserModel”  мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере. На 9 строке  мы подключаем пространство имен “Foundation”. На 11 строке  мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда. Рассмотрим протокол “NSCoding”. Протокол содержит метод “encode” и инициализатор. Экземпляр класса “NSCoder”  позволяет кодировать и раскодировать свойства класса, значения которых могут сохраняться в хранилище настроек телефона. В методе “encode”  данные извлекаются из свойств и кодируются в формат, нужный для сохранения в настройки телефона. В инициализаторе  данные извлекаются и раскодируются из настроек телефона, затем сохраняются в нужные свойства класса. Вот так это работает. Теперь вернемся и рассмотрим дальше модуль “UserModel.swift”. На 12 строке  мы объявляем поле “_id” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 13 строке  мы объявляем поле “userName” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 14 строке  мы объявляем поле “userPwd” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 15 строке  мы объявляем поле “userDesc” типа “String” и инициализируем его значением по умолчанию, пустая строка. На 17 строке  мы переопределяем инициализатор по умолчанию для класса “NSObject”, тем самым мы говорим, что создание объекта без передачи аргументов в инициализатор будет возможно. На 18 строке  мы вызываем базовый инициализатор из класса “NSObject”. На 21 строке  мы объявляем параметризированный инициализатор. Данный инициализатор  инициализирует поля класса “_id”, “userName”, “userPwd”, “userDesc” значениями, переданными в аргументы инициализатора. На 31 строке  мы реализуем конструктор протокола “NSCoding”. Ключевое слово “required”  обозначает, что вызов этого инициализатора обязателен в первую очередь.   Ключевое слово “convenience”  гарантирует то, что после вызова этого инициализатора  будут вызваны остальные инициализаторы классов, связанных с этим классом по цепочке вверх. На 32 строке  мы раскодируем из настроек телефона  значение свойства “_id”  путем вызова на экземпляре “aDecoder” типа “NSCoder”  метода “decodeObject”, аргумент “forKey”  которого принимает название поля/свойства, которое было сохранено в настройки телефона. Этот метод возвращает значение “Any?”, т.е. неопределенный тип (аналог в C#  - это Object, в Visual Basic - это Variant). Нам необходимо это значение вручную привести к нужному типу поля. В настройках телефона содержится большой файл, который имеет древовидную  структуру словаря ключ и значение. Чтобы привести к нужному значению, мы используем конструкцию приведения к типу “as”. Причем, мы можем управлять этой конструкцией. Например, нам надо привести тип “Any?” к типу “String”. Для этого мы используем конструкцию “as!”. Если же нам надо привести к типу “String?” (nullable тип), тогда мы используем конструкцию “as?”. Зачем мы это делаем. Настройки телефона  разбиваются на разные файлы, для каждого приложения используется свой файл с настройками. Когда приложения устанавливаются на macOS или iOS платформу, они устанавливаются в виде папки и имеют тип “Bundle”. Apple довольно кардинально подошла к этому вопросу, она сделала так, что все настройки для каждого приложения хранятся внутри “Bundle” данного приложения. И когда приложение удаляется, удаляется “Bundle” со всеми настройками с диска операционной системы и не остается никаких следов после удаления. Чего не скажешь о таких платформах, как Windows и Android, которые засоряют все пространство на диске операционной системы вокруг папки, в которую было установлено приложение, и выше. И когда приложение удаляешь, есть риск, что после этого будет необходимо еще и переустановить операционную систему. Отчасти именно из-за этого подхода техника Apple  пользуется такой популярностью и их устройства выигрывают по отказоустойчивости программного обеспечения по сравнению с конкурентами. Если приложение обновляется, то файл настроек остается старый. Чтобы обновить его, надо выполнить запись в него, тогда старые конструкции удалятся, а новые добавятся.   Представим себе ситуацию, сейчас в нашей модели “UserModel”  всего 4 поля. Мы сохранили эту модель в файл настроек телефона. Отправили в магазин, пользователи скачали, установили приложение. Вышла новая версия приложения, в которой мы добавили новое поле в эту модель с именем “userRole” типа “String”. Разумеется, первая операция, которая будет выполнена как обычно - это запрос данных из настроек телефона, но для поля “userRole”  установлено значение “nil” (аналог в C# null) и произойдет ошибка и креш приложения, так как мы не обработали эту ситуацию, а пытаемся через метод “decodeObject” получить значение этого поля и еще и явно привести его к типу “String”  через, например, оператор “as!”. Но в поле “userRole” - значение “nil” и оно не приведется к типу “String” через конструкцию “as!”, но может привестись без ошибок к типу “String?”  через “as?”. Надеюсь, я прояснил понятно, зачем нужна данная проверка. Продолжим. На 32 строке мы проверяем, доступно ли нам значение поля “_id”. Чтобы проверить, равно ли возвращаемое значение “nil”, можно было бы использовать такую конструкцию: “aDecoder.decodeObject(..) == nil”, но такая конструкция не слишком информативна. Поэтому мы используем конструкцию “(aDecoder.decodeObject(..) as? String) == nil”.       Далее, если данная конструкция возвращает значение “true”, тогда мы через тернарный оператор  присваиваем  константе “_id” значение по умолчанию пустая строка, иначе присваиваем реальное значение  поля “_id” из настроек телефона. С 33 по 35 строку  мы выполняем те же операции проверок для полей “userName”, “userPwd”, “userDesc”. На 36 строке  мы вызываем параметризированный конструктор и инициализируем его аргументы объявленными ранее константами. На 47 строке  объявлен вспомогательный метод “Populate”, который переносит данные из экземпляра “dictionary” типа “NSDictionary”  в поля класса “UserModel”. Этот метод мы будем использовать, когда будем создавать запросы к серверу и получать ответы.   В ответе содержится словарь типа “NSDictionary”, в котором находятся свойства с теми же именами, что и поля нашего класса. С 48 по 51 строку мы инициализируем поля класса “UserModel”  значениями из экземпляра “dictionary” c теми же проверками, что мы рассматривали выше. На 53 строке объявлен вспомогательный метод “PopulateArray”, который переносит данные из экземпляра “array” типа “NSArray” в массив объектов типа “UserModel”. Этот метод мы будем использовать, когда в ответе от сервера  придет массив JSON объектов типа “User”, чтобы сконвертировать массив типа “[NSDictionary]” в массив типа “[UserModel]”. На 55 строке  мы объявляем пустой массив типа “[UserModel]” c именем “result”. На 56 строке  мы выполняем цикл “foreach” по элементам массива “array”. На 58 строке  мы создаем новый экземпляр типа “UserModel” с именем “newItem”. На 59 строке  мы проверяем, содержит ли элемент массива  значение “nil”. На 60 строке  мы вызываем на экземпляре “newItem”  метод “Populate”, который перенесет данные из словаря типа “NSDictionary”  в поля экземпляра “newItem”. На 62 строке  мы обращаемся к экземпляру “result” и вызываем у него метод “append”, чтобы добавить новый элемент в массив, и добавляем проинициализированный выше экземпляр “newItem”. На 64 строке  мы возвращаем сконвертированный массив типа “[UserModel]” из метода “PopulateArray”. Откройте модуль “Models / ProductModel.swift”, заполните его в соответствии с содержимым ниже. В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя,  полученный от сервера. В классе “UserModel”  мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере. На 9 строке  мы подключаем пространство имен “Foundation”. На 11 строке  мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда. Рассмотрим протокол “NSCoding”. Данный модуль не нуждается в объяснении, поскольку он по образу и подобию такой же, как и модуль “UserModel.swift”, с разницей лишь в том, что он построен на модели “Product” и содержит соответствующие поля.       Итак, модели данных мы создали, теперь мы сможем с уверенностью сохранять результаты ответа от сервера в нормальном виде и потом использовать их при инициализации элементов управления в соответствующих представлениях :) Теперь приступаем к реализации классов для отправки запросов на сервер и сохранения/загрузки полученных данных в настройки телефона. Откройте модуль “Services / Services.swift”, заполните его в соответствии с содержимым ниже. На 11 строке  объявлен класс с именем “Services”. Данный класс будет содержать вспомогательные свойства и методы, которые мы будем часто использовать при работе с ответом от сервера. На 12 строке  объявлено статическое свойство “host” типа “String” и мы ограничили его функциональность только одним аксессором доступа “get”. Т.е. значение свойства получить можно, но изменить нельзя. В данном свойстве содержится адрес, по которому доступен сервер. В прошлом уроке, когда мы делали сервер, мы использовали адрес “http://localhost:3000” , внутри приложения клиента имеется свой “localhost”, именно поэтому мы используем явный IP адрес “127.0.0.1”, чтобы запрос шел именно на сервер, а не внутрь приложения. На 18 строке  объявлен статический метод “ejectJsonArray”, который принимает аргумент “data” типа “NSData?” и возвращает массив типа “NSArray?”. Данный метод конвертирует экземпляр класса “NSData” в экземпляр класса “NSArray”. В экземпляре “data” будет содержаться массив JSON объектов, полученных от сервера. На 19 строке  объявлена переменная “json” типа “NSArray?” и инициализировано значение по умолчанию “nil”. На 20 строке  мы используем конструкцию “do .. try .. catch” (аналог в C# - “try .. catch”). Символ “_” в блоке “catch” на месте аргумента используется потому, что мы не будем использовать этот аргумент в блоке “catch”. В случае возникновения исключения мы не будем завершать аварийно приложение, а просто вернем значение “nil” по умолчанию, которое будет говорить о том, что конвертация не удалась, поскольку в NSData  не содержится JSON объект. Аналог JSON объекта в Swift - это класс “NSDictionary”. На 21 строке  мы правым операндом обращаемся к классу “JSONSerialization” и вызываем от его экземпляра статический метод “jsonObject”, который первым аргументом принимает данные типа “Data” для конвертации, вторым аргументом принимает опции конвертации. Метод “jsonObject” возвращает экземпляр типа “Any”. Далее мы приводим это значение к нужному нам типу “NSArray?”, если приведение было проведено не успешно, будет возвращено значение “nil”. Результат конвертации присваивается объявленной выше переменной “json”. Обратите внимание на ключевое слово “try” перед конструкцией “JSONSerialization.jsonObject(..)”, использование этого ключевого слова является обязательным, так как метод “jsonObject” имеет в своей сигнатуре ключевое слово “throws”, это означает, что данный метод может генерировать исключения и их необходимо обрабатывать. На 23 строке в случае исключения вызванного методом “jsonObject”  возвращается значение “nil”. На 25 строке в случае успеха операции  возвращается экземпляр типа “NSArray?”. На 28 строке  объвлен метод “ejectJsonDictionary” с той же сигнатурой, что и метод “ejectJsonArray”, но с разницей в возвращаемом значении. В этом методе возвращается экземпляр типа “NSDictionary?”. Метод подразумевает, что в экземпляре “data” типа “NSData?” содержится единичный JSON объект и пытается его  сконвертировать в тип “NSDictionary” , являющийся аналогом JSON объектов в Swift.       Реализацию метода смысла описывать не вижу, она такая же, как и в методе “ejectJsonArray”. Реализуем сохранение моделей данных “UserModel” и “ProductModel” в настройки телефона. Откройте модуль “Services / UserData.swift”, заполните его в соответствии с содержимым ниже. Модуль большой, поэтому будем разбирать его, разбив на две части по функциональности. В первой его части  будут описаны методы сохранения / извлечения единичного экземпляра типа “UserModel” и экземпляра типа массив “[UserModel]”. Во второй его части  будут описаны вспомогательные методы  для обработчиков ошибок ответа от сервера и сохранения данных в настройки телефона. Данные методы были созданы для того, чтобы не копипастить один и тот же код много раз, а стандартизировать и сделать его повторно используемым. На 9 строке  подключено пространство имен “UIKit”, необходимое для классов “UserDefaults”, “NSKeyedArchiver”, “NSKeyedUnarchiver”. В пространстве имен уже содержится подключенное пространство имен “Foundation”, поэтому нет смысла его тоже  подключать для классов “NSArray”, “NSDictionary”, “NSData”. На 10 строке  подключено пространство имен “Alamofire”, необходимое для класса “DataResponse”. На 12 строке  объявлен класс с именем “UserData”. На 13 строке  объявлен метод “saveUserModel”, который принимает аргумент “userModel” типа “UserModel”. Метод будет сохранять поля модели “UserModel” в настройки телефона. На 14 строке  правым операндом мы обращаемся от экземпляра “UserDefaults” к его статическому свойству “standard”, это свойство является источником данных по отношению к файлу настроек телефона и содержит необходимые методы для сохранения / извлечения данных. На 15 строке  правым операндом мы обращаемся к экземпляру “NSKeyedArchiver” и вызываем статический метод “archivedData”. Этот метод принимает один аргумент “withRootObject” типа “Any” и возвращает экземпляр типа “Data”. Метод упаковывает экземпляр типа “UserModel” в экземпляр типа “Data” (аналог NSData) и возвращает его. На 16 строке  мы от экземпляра “userDefaults” вызываем метод “set”, который принимает два аргумента.  Первый аргумент “_” типа “Any?” принимает экземпляр, который необходимо сохранить в настройках телефона. Второй аргумент “forKey” типа “String” необходим, чтобы задать название ключа в словаре настроек  телефона. По этому ключу будет произведено сохранение экземпляра “UserModel”. На 17 строке  мы обращаемся к экземпляру “userDefaults”, вызываем от него метод “synchronize”. Данный метод выполняет сохранение экземпляра модели “UserModel” в настройки телефона (аналог в C#, например,  'using(StreamWriter sw = File.CreateText(“test.txt”)) { sw.Write(“some text”); sw.Flush(); }', из этого примера метод “Write” аналогичен методу “set”, а метод “Flush” аналогичен методу “synchronize”). На 20 строке  мы объявили статический метод “getUserModel”, который не принимает аргументов и возвращает экземпляр типа “UserModel”. В этом методе мы излекаем модель “UserModel” из файла настроек телефона по ключу, указанному в предыдущем методе при сохранении этой модели. На 21 строке  мы получаем экземпляр источника данных настроек телефона. На 22 строке  мы проверяем, если модель “UserModel” ранее не сохранялась в настройки телефона, тогда необходимо создать пустую модель по умолчанию. Вызываем на экземпляре “userDefaults” метод “object”, который принимает аргумент “forKey” типа “String” и возвращает значение типа “Any?”. В качестве аргумента мы передаем ключ, под которым сохраняется данная модель в настройках телефона. Если значение было найдено в словаре, будет возвращен экземпля , который надо будет привести у нужному типу. Если значение не было найдено, будет возвращено значение “nil”. На 23 строке  мы создаем пустой экземпляр типа “UserModel”. На 24 строке  мы возвращаем этот экземпляр из метода. На 26 строке  код будет выполнен при наличии записи по ключу в словаре. Правым операндом мы обращаемся к экземпляру “userDefaults”, вызываем метод “object” и передаем в аргумент “forKey”  ключ, по которому надо извлечь значение. Полученное значение приводим к типу “NSData”. На 27 строке  мы обращаемся к экземпляру “NSKeyedUnarchiver” и вызываем от него статический метод “unarchiveObject”, который принимает аргумент “with” типа “Data” и возвращает значение типа “Any”. Метод конвертирует экземпляр типа “Data” в экземпляр типа “Any”. Возращаемое значение метода мы явно приводим к типу “UserModel”. На 28 строке  мы возвращаем из метода полученный из словаря экземпляр типа “UserModel”. На 31 строке  объявлен метод “saveListOfUsersModel”, который принимает аргумент “listOfUsersModel” типа “[UserModel]” и ничего не возвращает. Метод сохраняет массив “[UserModel]” в файл настроек телефона. Описывать данный метод не имеет смысла, его работа аналогична методу “saveUserModel”. На 38 строке  объявлен метод “getListOfUsersModel”, который не принимает аргументов и возвращает экземпляр типа массив “[UserModel]”. Метод извлекает из настроек телефона массив типа “[UserModel]” по ключу, указанному в методе “saveListOfUsersModel”. Описывать данный метод не имеет смысла, его работа аналогична методу “getUserModel”. Откройте модуль “ProductData.swift”, заполните его в соответствии с содержимым ниже. В первой части описано сохранение / извлечение моделей типа “ProductModel”, “[ProductModel]” в настройки телефона. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”. Во второй части описаны вспомогательные методы для обработки запросов от сервера и сохранения данных. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”. Теперь, наконец то, мы можем реализовать первый класс для работы с сервисом авторизации пользователя и разобрать работу с библиотекой “Alamofire”. Откройте модуль “Services / AuthorizationService.swift”, заполните его в соответствии с содержимым ниже. На 12 строке  объявлен класс с именем “AuthorizationService”.             На 13 строке  объявлен метод “login”, который принимает три аргумента и ничего не возвращает.             Первый аргумент “userName” типа “String” -  это имя пользователя (логин).             Второй аргумент “userPwd” типа “String” -  это пароль пользователя.             Третий аргумент - это “callback” метод, который принимает аргумент типа “Bool” и ничего не возвращает.             Метод с такой сигнатурой, переданный в третий аргумент, будет вызван по завершению обработки ответа от сервера. В случае наличия ошибок  в аргумент этого метода будет передано значение “false”. В случае успешно проведенной операции - значение “true”.             Да, можно было сделать этот аргумент другим типом, например, отдельным классом ошибок, в который можно было сохранять подробно, что за ошибка произошла.             Но это учебный пример, он итак очень большой. Я решил не усложнять этим жизнь  ни себе, ни вам :)             На 14 строке  правым операндом мы вызываем метод “request” из библиотеки “Alamofire”.             Метод принимает 5 аргументов и возвращает значение типа “DataRequest”.             Данный метод используется для отправки HTTP/HTTPS запросов на сервер.             Первый аргумент “_” типа “URLConvertible” принимает адрес, по которому необходимо отправить запрос. Мы его инициализируем значением “\(Services.host)/auth”, которое после подстановки свойства “host”  будет выглядеть вот так: “http://127.0.0.1:3000/auth”.             Второй аргумент “method” типа “HTTPMethod” принимает HTTP глагол. Тип “HTTPMethod”  является перечислением и может принимать следующие значения. Мы инициализируем данный аргумент значением “.post” (это сокращенная запись, полная запись “HTTPMethod.post”)             Третий аргумент “parameters” типа “Parameters?”. Принимает словарь, ключ - значение, мы его заполняем теми же данными, что отправляли в прошлом уроке через утилиту “Postman”. Конкретно, мы передаем значение имени пользователя и его пароль.             Четвертый аргумент “encoding” типа “ParameterEncoding”. Этим параметром задается заголовок запроса “Content-Type”, т.е. указывается тип данных, который будет отправлен на сервер, сами данные задаются в третьем аргументе “parameters”. Мы инициализируем аргумент значением “JSONEncoding.default”, что аналогично записи “application/json”. Тип “ParameterEncoding” является протоколом и имеет следующие реализации себя в классах “URLEncoding”, “JSONEncoding”, “PropertyListEncoding”. Про эти классы вы можете почитать в официальной документации на сайте “Alamofire”.             Пятый аргумент “headers” типа “HTTPHeaders?” имеет значение по умолчанию “nil”, в нашем вызове метода он не используется. Данный аргумент задается, если вы желаете отправить в заголовках запроса на сервер какую- либо информацию, например, “cookie”. Если у вас сервер написан на Web API и вы используете для авторизации OWin , с настройкой надо использовать “cookie” как ключ авторизации пользователя. Тогда при отправке запросов вам будет необходимо заполнять этот аргумент. Пример его заполнения может выглядеть вот так: Этот пример взят из моего приложения “Smart Payment”,  написанного на Swift 2.2 c минимальной версией iOS 8.3  библиотекой “Alamofire” версии 2. Чтобы этот пример работал на Swift 3, надо заменить значение параметра “encoding” на “JSONEncoding.default”. Этот пример не относится к нашему приложению и его переписывать не нужно.             На 16 строке  мы вызываем от экземпляра “r” типа “DataRequest”  метод “responseJSON”. Этот метод выполняет запрос на сервер и содержит три аргумента.             Первый аргумент “queue” типа “DispatchQueue?”  по умолчанию содержит значение “nil”. Используется при переопределении потока выполнения запроса.             Второй аргумент “options” типа “JSONSerialization.ReadingOptions” по умолчанию содержит значение “.allowFragments”. Используется для задания опция отправки запроса.    Тип “ReadingOptions” является структурой и имеет такое объявление. Третий аргумент “completionHandler” типа делегат “@escaping (DataResponse) -> Void”. Данная сигнатура принимает один аргумент типа “DataResponse” и ничего не возвращает. В этом аргументе содержится ответ от сервера. Сам метод вызывается после запроса, когда сервер отправил ответ на клиент.             Обратите внимание, на 16 строке, когда мы вызываем метод “responseJSON”, у нас нет круглых скобок после метода, а идут сразу фигурные скобки, это называется “closure” подход. В фигурных скобках мы объявляем лямбда метод с сигнатурой, соответствующей третьему аргументу метода “responseJSON”.             Запись “response in” означает следующее: “response” - это аргумент лямбда метода, а само тело метода начинается после ключевого слова “in”. Аргумент “response” имеет тип “DataResponse”.             На 17 строке  мы проверяем, если ответ пришел с ошибкой, то это означает, что данных нет и пришло пустое значение типа “nil”. Мы обращаемся к экземпляру “response” и вызываем свойство “result” типа “Result” и от свойства “result” вызываем свойство “value” типа “Value?”. Если в свойстве “value” пришло пустое значение, значит запрос не был выполнен, возможно, по причине недоступности сервера или отсутствия подключения к интернету.             На 18 строке  мы вызываем “callback” метод “completion” со значением “false”.             На 21 строке, в случае если проблем с подключением к серверу не возникло, мы правым операндом вызываем от экземпляра “Services” метод “ejectJsonDictionary”, который ранее уже подробно рассматривали. В аргумент “data” мы передаем значение свойства “data”, вызванного от экземпляра “response”. Свойство “data” имеет тип “Data?”. Левому операнду “json” присваивается экземпляр “NSDictionary”, в котором содержится JSON объект пользователя, прошедшего авторизацию, который был отправлен сервером на клиент.             На 22 строке  мы проверяем, если пришел JSON объект с ошибкой, тогда возвращаем нужный флаг в “callback” методе “completion”. Помним, что в предыдущем уроке мы создавали JSON объект с ошибкой на сервере, в случае если пользователь ввел неверные данные авторизации,  JSON выглядел вот так '{ “Error”: “Authorization error”}'.             У словаря “json” есть возможность получить все ключи, для этого надо вызвать свойство “allKeys”, которое имеет тип “[Any]”. Но,  поскольку выполнить поиск строки в массиве объектов нельзя, мы оборачиваем массив объектов в массив типа “Array” и явно приводим его к нужному нам типу массива “[String]”. Мы это делаем, поскольку знаем наверняка, что ключи JSON объекта имеют тип “String”. Затем от полученного экземпляра типа “[String]”  мы вызываем метод “contains”, в аргумент которого передает название свойства JSON объекта ошибки, значение “Error”. Если такой JSON объект пришел в ответе, значит пользователь ввел неправильные логин или пароль, или ввел несуществующего пользователя.             На 23 строке  мы вызываем “callback” метод “completion” с аргументом “false”.             На 25 строке  мы создаем пустой экземпляр “userModel” типа “UserModel”.             На 26 строке  мы вызываем на экземпляре “userModel”  метод “Populate”  в аргумент “dictionary”, передаем экземпляр “json”. Метод “Populate”  перенесет данные из экземпляра “json” типа “NSDictionary” в поля экземпляра “userModel”.             На 27 строке  мы обращаемся к экземпляру “UserData” и вызываем статический метод “saveUserModel”, чтобы сохранить данные экземпляра “userModel” в настройки телефона. В аргумент “userModel”  мы передаем экземпляр “userModel”.             На 28 строке  мы вызываем “callback” метод “completion” и передаем в него результат операции “true”.             Позже в контроллере, где мы будем вызывать метод “login”, будет передан “callback” метод “completion”, объявленный в этом контроллере. Тем самым решается проблема ожидания ответа от сервера в контроллере, так как есть “callback” метод. Внутри этого метода мы проверим результат операции и затем обратимся к экземпляру “UserData” и вызовем метод “getUserModel”, который вернет нам экземпляр “UserModel”, с которым мы сможем работать на стороне контроллера.             Откройте модуль “Services / UserService.swift”, заполните его, как показано  ниже. На 12 строке  объявлен класс “UserService”.             На 13 строке  объявлен метод “get”, который принимает два аргумента и ничего не возвращает.             Первый аргумент “id” типа “String?”  принимает идентификатор пользователя “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список пользователей, если передать конкретный идентификатор, будет возвращен конкретный пользователь.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 29 строке  объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание пользователя на сервер.             Первые три аргумента “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя. Идентификатор пользователя в операции создания не нужен. Так как идентификатор пользователю присваивается на стороне сервера и данный метод выполняет как раз эту операцию.             Четвертый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 37 строке  объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных пользователя на сервер.             Первые 4 аргумента “id”, “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя.             Пятый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 45 строке  мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление пользователя на сервер.             Первый аргумент “id” типа “String?”  принимает идентификатор пользователя “_id”.             Второй аргумент “completion” типа делегат “(Bool) -> Void”, используется для получения сигнала, что ответ был получен и основные операции были выполнены.               Откройте модуль “Services / ProductService.swift”, заполните его, как показано ниже На 12 строке  объявлен класс “ProductService”.             На 13 строке  объявлен метод “get”, который принимает два аргумента и ничего не возвращает.             Первый аргумент “id” типа “String?”  принимает идентификатор товара “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список товаров, если передать конкретный идентификатор, будет возвращен конкретный товар.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 29 строке  объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание товара на сервер.             Первые три аргумента “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные товара. Идентификатор товара в операции создания не нужен. Так как идентификатор товару присваивается на стороне сервера и данный метод выполняет как раз эту операцию.             Четвертый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 37 строке  объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных товара на сервер.             Первые 4 аргумента “id”, “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные пользователя.             Пятый аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             На 45 строке  мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление товара на сервер.             Первый аргумент “id” типа “String?”  принимает идентификатор товара “_id”.             Второй аргумент “completion” типа делегат “(Bool) -> Void”  используется для получения сигнала, что ответ был получен и основные операции были выполнены.             Заметьте, адреса запросов, используемых нами при отправке на сервер, совпадают с теми, что мы использовали в прошлом уроке в утилите “Postman”.             На этом урок завершен.             На следующем уроке мы продолжим разработку клиента, создадим контроллеры и представления для редактирования пользователей, подключим работу сервисов к странице входа в систему, создадим форму регистрации пользователя.
Введення в розробку програм під iOS. Частина 6

Автор: Volodymyr Bozhek

Здравствуйте,  дорогие читатели. В этом уроке мы: Установим менеджер установки пакетов “Brew”; Научимся пользоваться базой данных “Mongo DB” и изучим библиотеку “Mongoose”; Установим и сконфигурируем базу данных “Mongo DB”; Научимся запускать демон (службу) “Mongod” и подключаться к ней; Научимся просматривать документы базы данных “Mongo DB” с помощью приложения “Robomongo”; Создадим REST сервисы для товаров и пользователей с использованием “NodeJS”; Научимся тестировать REST сервисы с помощью расширения “Chrome” браузера “Postman”; Итак, вы скорее всего задались вопросом, почему же я выбрал именно такие технологии для написания REST сервисов. Почему не выбрал то, что уже давно у всех на слуху, такие технологии как Web API + Entity Framework. Ответ прост, я выбрал ровно те технологии, которые явно подходят платформе, на которой мы ведем разработку macOS. Да, можно было установить Xamarin Community Edition, в нем создать ASP.NET проект и сделать сервисы на Mono.NET. Еще был вариант сделать сервисы на виндовс платформе, развернутой на виртуальной машине в веб сервере IIS с сетевым адаптером мост. Это для того, чтобы веб сайт и сервисы были доступны на хостовой машине macOS в браузере. Возможно, в одном из моих будущих видео курсов по разработке iOS приложений я буду использовать именно платформу Windows и Visual Studio для разработки REST сервисов. Но в этом уроке  мы будем делать сервисы на NodeJS,  и вы сами убедитесь в том, насколько это просто и как мало кода надо для этого написать :) В качестве среды разработки я буду использовать WebStorm, он довольно хорошо себя оправдал как один из продуктов JetBrains. В этом уроке мы не будем работать с приложением “Warehouse” под iOS, поскольку мы должны приступить к изучению библиотеки “Alamofire”. А для нее необходимы REST сервисы, которых у нас сейчас нет. Показывать работу на всяких httpbin, parse и других сервисах тестирования я не буду, этого полно в сети интернет, вы и сами будете видеть это в поиске нужной вам информации. “Alamofire” -  это асинхронная сетевая библиотека для работы с REST сервисами. В большинстве туториалов  не рассказывается, как сделать REST сервисы и клиент к ним на разных платформах. Мне хотелось бы нарушить этот стереотип и предоставить вам учебные примеры по созданию полнофункционального REST сервиса и полнофункционального iOS клиента в составе приложения “Warehouse” к нему. Проще говоря, дать готовое решение  для быстрого понимания, что и как делается. Это то, чего обычно не хватает новичкам и то, чего обычно нигде не выкладывают. Приходится самостоятельно разбираться и искать нужную информацию. Откройте “WebStorm”. Выберите в меню “File -> New Project”. Выделите тип проекта “Node.js Express App”. В поле “Location”  укажите название проекта “Warehouse” и путь, по которому вы будете сохранять данный проект. В поле “Template”  выберите “EJS”. Нажмите кнопку “Create”. Вы увидите диалоговое окно с предложением, откуда взять библиотеку “NodeJS”. Оставьте пункт по умолчанию “Download from the Internet” и нажмите кнопку “Configure”. Будет загружен из сети интернет “NodeJS”, “Express” и другие библиотеки. Панель навигации будет выглядеть у вас так: Рассмотрим, что создал нам данный шаблон. В папке “bin”  хранится файл “www”, в нем находится настройка сервера “NodeJS”, с этого файла происходит запуск нашего сервера. В папке “node_modules”  находятся библиотеки, которые были автоматически загружены  шаблоном проекта NodeJS. В папке “public”  находятся ресурсы, используемые нашим сервером (картинки, CSS стили, скрипты). В папке “routes”  находятся скрипты, которые должны содержать маршрутизацию относительно текущего URL,  введенного пользователем. Например,  файл с именем “users.js”  вызывется, когда пользователь введет в браузере URL: “http://localhost:{port}/users”. Файл “index.js”  вызывется, когда пользователь введет URL: “http://localhost:{port}”. Если сравнивать с паттерном MVC (Model View Controller), то в папке “routes”  находятся “контроллеры”. В папке “views”  находятся представления. Расширение “*.ejs”  говорит о том, что содержимое данного файла будет транслироваться через библиотеку “Express JS”. Данная библиотека создана в помощь, чтобы можно было проще создавать маршрутизацию в NodeJS  и проще создавать пользовательский интерфейс. Внутри этих файлов “*.ejs”  содержится обычная “HTML” разметка. Файл маршрутизации и файл представления должны называться одинаково, если они будут связаны. В текущем проекте связаны только “index.js” и “index.ejs”. Маршрут “users.js”  не имеет явного представления, для него преставление  генерируется средствами “NodeJS”. Наша задача создать только REST сервисы, поэтому работу с “EJS”  мы не будем рассматривать в данном уроке. Рассмотрим содержимое файла “www” в папке “bin”. На 7 строке  через библиотеку “Require JS”  мы подключаем содержимое модуля “app.js”  в текущий исполняемый модуль “www” и запускаем это содержимое на исполнение. В файле “app.js”  содержатся настройки для работы нашего сервера. На 8 строке  мы задаем режим отладки для нашего сервера. На 9 строке  мы подключаем модуль “http”  для работы с HTTP запросами. На 15 строке  задается порт “3000”, на этом порту сервер будет принимать входящие запросы к нему. На 16 строке  устанавливает этот порт для свойства “port” сервера. На 22 строке  создается сервер на основе настроек экземпляра “app”. На 28 строке  сервер запускается и начинает прослушивание на порту “3000”. На 29 строке  привязывается функция “onError”, вызываемый при возникновении ошибок. На 30 строке  привязывается функция “onListening”, вызываемая  при начале прослушивания запросов сервером. Остальной код в данном модуле не представляет для нас интереса. Чтобы запустить приложение, выполните в меню “Run -> Run bin/www”, сервер запустится. Затем откройте браузер и введите URL: “http://localhost:3000”, вы увидите приветствие. Чтобы остановить сервер, выполните в меню “Run -> Stop”. Теперь необходимо установить базу данных “Mongo DB”. Подробнее об установке  вы можете прочитать тут. Проще всего устанавливать базу данных  через менеджер пакетов “Brew”. Давайте установим его. Перейдите в браузере по адресу, на сайте будет предложено скопировать строку “/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)” в терминал. Откройте терминал и скопируйте в него строку установки “Brew”. После установки  вы увидите следующее: Теперь необходимо установить базу данных “Mongo DB”. Установка базы данных через “Brew”  проще тем, что “Brew”  сам заполняет нужные переменные среды окружения и вам не надо этого делать вручную  в случае с установкой базы данных вручную. Перейдите в “WebStorm”, откройте внутри него терминал. И введите команду “brew install mongodb && mongod”, данная команда скачает и установит “Mongo DB”, а также выполнит настройки по умолчанию для демона “mongod”. Выполните в терминале команду “sudo mkdir -p /data/db”. Введите свой пароль и нажмите кнопку “Enter”. Данная команда создаст папку “data” и в ней папку “db”, эта папка используется по умолчанию для добавления базы данных “Mongo DB”. Команда “sudo”  используется, чтобы выполнить остальную часть команды от имени администратора.       Теперь необходимо установить в наш проект библиотеку “Mongoose”  для осуществления возможности работы с базой данных их кода приложения сервера . Выполните в терминале команду “npm install mongoose”. Документацию по этой библиотеке можно почитать тут:  Данную команду мы выполняли через  встроенный в NodeJS  менеджер пакетов, так как устанавливаем данную библиотеку исключительно для нашего проекта, а не глобально. Откройте терминал, введите команду “mongod”. Данная команда запускает сервер базы данных “Mongo DB”, который принимает входящие подключения на порту “27017”. На картинке выше  вы можете в этом убедиться “waiting for connections on port 27017”. Демон запустили, теперь перейдите в “WebStorm”, в терминале внутри введите команду “mongod –version”, вы увидите версию базы данных “Mongo DB”, которая была установлена на ваш компьютер. Теперь необходим клиент с GUI  для работы с базой данных. Откройте браузер, перейдите по адресу, нажмите кнопку “Download”, скачайте и установите приложение. После установки приложения  откройте его. Нажмите на иконку: Откроется приложение “Robomongo”. Нажмите кнопку “Connect to local or remote Mongo DB instance”. Откроется диалоговое окно подключения к базе данных. Нажмите кнопку “Create”. Откройте диалоговое окно настройки подключения к базе данных. Заполните настройки, как показано на картинке выше и только на вкладке “Connection”, нажмите кнопку “Save”. После этого в окне “Mongo DB connections”  у вас появится добавленное подключение. Выделите его и нажмите кнопку “Connect”. Нажмите правой кнопкой в панели навигации по “Warehouse” и в контекстном меню выберите пункт “Create Database”. В поле “Database Name”  введите “warehouse”, нажмите кнопку “Create”. На картинке выше  видно, что база данных “warehouse”  была успешно добавлена. Супер :) Теперь надо добавить в проект модель и схему базы данных, они создаются с помощью библиотеки “Mongoose”. Перейдите в “WebStorm”. В панели навигации, нажмите правой кнопкой по “Warehouse”, в контекстном меню выберите “New -> Directory”. Создайте папку с именем “models”. Создайте в папке “models”  модули “Product.js” и “User.js”. А в папке “routes” создайте модули “auth.js”, “products.js”. Откройте модуль “Product.js”, заполните его, как показано на картинке ниже: На 1 строке  мы подключаем библиотеку “mongoose” и создаем ее экземпляр с именем “mongoose”. На 2 строке  мы правым операндом создаем схему таблицы товаров и присваиваем ее левому операнду с именем “ProductSchema”. Таблица товаров содержит поля: скрытое поле “_id”, типа “String”, в этом поле содержится уникальный идентификатор строки. скрытое поле “__v”, типа “Number”, в этом поле содержится версия API  по умолчанию 0. Версия API  нужна для того, чтобы можно было менять реализацию сервисов и иметь несколько версий  для обратной совместимости. поле “productImage”, типа “String”, тут будет храниться название картинок из нашего проекта под iOS, названия от “tool001” до “tool012”. поле “productName”, типа “String”, тут будет храниться название товара. поле “productDescription”, типа “String”, тут будет храниться описание товара. На 7 строке  мы правым операндом создаем экземпляр модели с именем “Product” и указываем создать экземпляр на основе схемы, описанной в экземпляре “ProductSchema”.      Библиотека “mongoose”  создает данный экземпляр и присваивает его левому операнду. Свойство “exports”  содержит экземпляры объектов, которые  будут доступны из модуля  при подключении его через библиотеку “Require JS”. Откройте модуль “User.js”, заполните его, как показано на картинке ниже: На 1 строке  мы подключаем библиотеку “mongoose” и создаем ее экземпляр с именем “mongoose”. На 2 строке  мы правым операндом создаем схему таблицы товаров и присваиваем ее левому операнду с именем “UserSchema”. Таблица пользователей содержит поля: скрытое поле “_id”, типа “String”, в этом поле содержится уникальный идентификатор строки. скрытое поле “__v”, типа “Number”, в этом поле содержится версия API  по умолчанию 0. Версия API  нужна для того, чтобы можно было менять реализацию сервисов и иметь несколько версий для обратной совместимости. поле “userName”, типа “String”, тут будет храниться логин пользователя. поле “userPwd”, типа “String”, тут будет храниться пароль пользователя. поле “userDesc”, типа “String”, тут будет храниться описание пользователя. На 7 строке  мы правым операндом создаем экземпляр модели с именем “User” и указываем создать экземпляр на основе схемы, описанной в экземпляре “UserSchema”. Затем присваем созданный экземпляр свойству “exports” данного модуля. Откройте модуль “auth.js”, заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  подключаем модуль “User.js” и создаем его экземпляр с именем “User”. На 4 строке  строим маршрут типа “http://localhost:3000/auth/”. На экземпляре “router”  вызываем функцию “post”. Вызов данной функции говорит, что созданный с помощью него маршрут будет доступен только через HTTP глагол POST.       Первым аргументом задаем маршрут, вторым аргументом задаем функцию обработчик, который будет вызван при переходе по данному маршруту с учетом заданного HTTP глагола. Т.е. когда пользователь отправит POST запрос по адресу. Функция обработчик принимает три аргумента. Первый аргумент “request”  содержит экземпляр запроса по данному маршруту.     Второй аргумент “response”  содержит экземпляр ответа по запросу по данному маршруту. В этот экземпляр можно добавлять любой ответ, который вы хотите, чтобы пользователь получил. Третий аргумент “next”  содержит текущий итератор в стеке запросов по маршрутизации. Данный экземпляр необходим, чтобы была возможность перейти на следующую итерацию в стеке маршрутизации. Сам по себе сервер NodeJS  работает асинхронно и никогда никого и ничего не ждет, это нужно учитывать при работе с ним. На 5 строке мы выводим сообщение на консоль. На 6 строке мы на экземпляре “User” вызываем функцию “find”, в которую единственным аргументом передаем специальную функцию обработчик, в которой будет содержаться результат данной операции. Функция “find”  получает все записи из таблицы “User” и присваивает их в JSON формате  второму аргументу функции обработчика с именем “data”. Для  примера, в “data” может содержаться такой JSON: “[{_id:'789787sdfsd78sdfsd7', __v:0, userName: “Test1”,...},{_id:'4444447sdfsd78sdfsd7', __v:0, userName: “Test2”,...}]”. Где квадратные скобки - это массив, а в фигурных скобках - это экземпляр JSON. В аргументе “err”  содержится экземпляр ошибки, если произошла ошибка при выполнении данной функции “find”. На 7 строке мы создаем экземпляр объекта с именем “res”, данный объект содержит свойство “Error”, которое инициализировано значением по умолчанию “Authorization Error”. Как вы уже поняли, это текст ошибки, который вернется на клиент в случае ошибки авторизации клиента. На 8 строке мы проверяем, если при получении данных через функцию “find” произошла ошибка и аргумент “err” не содержит значение “undefined”, мы обращаемся к экземпляру “next” и говорим серверу выполнить следующую итерацию маршрутизации и вернуть ошибку на клиент. На клиент средствами NodeJS  будет возвращена “Stack Trace” ошибки. На 9 строке, если ошибки не было, мы выполняем цикл foreach по массиву JSON объектов “data”. В функцию “forEach”  передается специальная функция, которая содержит три аргумента. Первый аргумент “item”  содержит текущий итерируемый экземпляр JSON. Второй аргумент “i” содержит позицию итератора и имеет тип Number. Третий аргумент “data”  содержит исходный массив объектов JSON, по которым проводится итерация. На 10 строке  мы задаем условие поиска пользователя по его имени и паролю в списке всех пользователей полученной функцией “find”. В условии сравниваются имя и пароль, которые пришли в POST запросе на сервер, с именем и паролем, полученным от функции “find”. На 12 строке, если пользователь был найден, мы присваиваем экземляру “res”  JSON объект найденного пользователя. На 15 строке  мы обращаемся к экземпляру ответа “response”, вызываем на нем функцию “json”, которая принимает данные и возвращает ответ на клиент в JSON формате. В аргумент данного метода мы передаем или экземпляр найденного пользователя, или экземпляр ошибки, что пользователь не найден и авторизация не удалась. На 18 строке  мы экспортируем из модуля экземпляр маршрутизации “router”. Откройте модуль “index.js”. Заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  строим маршрут типа “http://localhost:3000/index/”, доступный через HTTP глагол GET. На 4 строке, на экземпляр ответа вызываем функцию “render”. Данная функция возьмет представление, указанное в первом аргументе функции и вернет его на сторону клиента. Вторым аргументом задается словарь, в который можно добавить свойства со значениями, к которым можно будет обращаться со стороны представления. На 6 строке  мы экспортируем из модуля экземпляр маршрутизации “router”. Откройте модуль “index.ejs”. Заполните его в соответствии с содержимым ниже: Библиотека Express JS позволяет нам обращаться к свойствам, которые мы указали во втором аргументе метода “render”  путем добавления следующей конструкции “<%= Название_Свойства %>”. На этапе отрисовки страницы данные конструкции будут заменены на значение указанного свойства. Откройте модуль “products.js”, заполните его в соответствии с содержимым ниже: На 1 строке  подключаем модуль “express” и создаем его экземпляр с именем “express”. На 2 строке  создаем экземпляр маршрутизатора запросов с именем “router”. На 3 строке  подключаем модуль “Product.js” и создаем его экземпляр с именем “Product”. На 4 строке  строим маршрут типа “http://localhost:3000/products/”. Данный маршрут будет доступен по HTTP глаголу GET. На 5 строке  мы вызываем на экземпляре “Product”  функцию “find”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON массив со списком всех товаров. На 10 строке  строим маршрут типа “http://localhost:3000/products/”. Данный маршрут будет доступен по HTTP глаголу POST. На 11 строке  мы вызываем на экземпляре “Product”  функцию “create”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON объект успешно созданного товара. На 16 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу GET. На 17 строке  мы вызываем на экземпляре “Product”  функцию “findById”. Из названия функции ясно, что функция выполняет поиск по идентификатору товара. Для поиска используется скрытое поле “_id” в модели “Product”, объявленной в модуле “Product.js”. Внутри функции мы проверяем, были ли ошибки, и возвращаем в ответе JSON объект успешно найденного товара. На 23 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу PUT. На 24 строке  мы вызываем на экземпляре “Product”  функцию “findByIdAndUpdate”. Из названия функции ясно, что выполняется поиск товара по его идентификатору “_id”, затем, в случае если найден, обновляются все его поля, кроме скрытых полей. В ответе возвращается JSON объект успешно обновленного товара. На 30 строке  строим маршрут типа “http://localhost:3000/products/{_id}” (например, “http://localhost:3000/products/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу DELETE. На 31 строке  мы вызываем на экземпляре “Product”  функцию “findByIdAndRemove”. Из названия функции ясно, что выполняется поиск товара по его идентификатору “_id”, затем, в случае если найден, товар удаляется. В ответе возвращается удаленный JSON объект. Откройте модуль “users.js”, заполните его в соответствии с содержимым ниже: В модуле “users.js”  я не вижу смысла расписывать подробно, что происходит, поскольку тут все то же самое, что и происходило в модуле “products.js”, рассмотренном выше. Разница только в том, что мы используем другую модель данных “User”. Опишу только доступные маршруты. На 4 строке  строим маршрут типа “http://localhost:3000/users/”. Данный маршрут будет доступен по HTTP глаголу GET. На 10 строке  строим маршрут типа “http://localhost:3000/users/”. Данный маршрут будет доступен по HTTP глаголу POST. На 16 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу GET. На 23 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу PUT. На 30 строке  строим маршрут типа “http://localhost:3000/users/{_id}” (например, “http://localhost:3000/users/789w66s2322kks4676s” ). Данный маршрут будет доступен по HTTP глаголу DELETE. Откройте модуль “app.js”, обновите его в соответствии с содержимым ниже: Конкретно в модуль “app.js”  были внесены следующие изменения. С 8 по 11 строку  были подключены модули “index.js”, “users.js”, “products.js”, “auth.js”. На 13 строке  был подключен модуль “mongoose”. На 14 строке  мы задаем свойство “Promise” в значение “global.Promise”, чтобы начать обмениваться сообщениями с базой данной “Mongo DB”. На 15 строке  мы подключаемся к базе данных “Mongo DB” c названием “warehouse”, мы ее ранее создавали в приложении “Robomongo”. Источник данных “mongodb://localhost/warehouse” можно разбить так: “mongodb://” протокол взаимодействия (аналоги “http://”, “tcp://”, “file://”). “localhost” - это адрес машины, на которой хостится база данных. “warehouse” - имя базы данных. На 16 строке  метод “then”  вызывается в случае успешного подключения к базе данных. На 17 строке  метод “catch”  вызывается в случае сбоя подключения к базе данных. С 33 по 36 строку мы задаем доступные глобальные маршруты, которые будет обрабатывать сервер. Надеюсь, все помнят разницу между сервером и сервисами, это не одно и то же. Сервер - это просто приложение, которое внутри себя способно содержать различные ресурсы. Сервер может запускать внутри себя различные потоки для обработки данных. Сервис - это функционал, который как раз запускается в отдельном потоке внутри сервера. Сам по себе сервис без наличия приложения ,в котором его можно разместить, работать не будет. В случае с WebAPI сервером является Microsoft IIS или отдельно стоящее исполняемое приложение с функцией self  хостинга. Мы закончили написание сервера. Запустите демон “mongod” в терминале. Запустите сервер. Вы увидите обновленное приветствие. Я бы рекомендовал вам прослушать курс “Angular JS”, автор Дмитрий Охрименко. Это один из лучших курсов, которые я когда-либо слушал. После данного курса вы без особых проблем сможете сделать клиентскую часть для сервера на “Angular JS”. Теперь давайте научимся тестировать наши REST сервисы. Тестировать сервисы можно и через Fiddler, но на macOS  у него есть проблемы с отрисовкой и работой. Поэтому откройте браузер и перейдите по адресу. “Postman” - это аналог “Fiddler”, доступный в качестве расширения для браузера “Google Chrome”. После установки расширения “Postman” откройте его. Выполним первый запрос к сервису “users” по HTTP глаголу GET. Заполните поле адрес, выберите глагол “GET”, нажмите кнопку “Send”. Результат придет - пусто. Обратите внимание, что в терминале “WebStorm” тоже пишется логирование вызовов при обращения к сервисам. Откройте приложение “Robomongo”, подключитесь к базе данных. Разверните папку “Collections” базы данных “warehouse”, выполните щелчок правой кнопкой мыши по таблице “users”, в контекстном меню выберите “View Documents”. В “Mongo DB” нет колонок, там есть всего одна колонка, в которой хранится JSON объект. Строки называются документами. Причем, в каждую строку одной таблицы можно класть абсолютно разный по структуре JSON объект. База данных позволяет такую операцию. Видим, что данных нет, это не удивительно, ведь мы еще ничего не добавляли. Давайте добавим. Перейдите в “Postman”, выберите глагол “POST”. Выберите “Content-Type” - “x-www-form-urlencoded”. Внимание, через другие типы “form-data”  “raw” работать не будет. Заданные свойства “userName”, “userPwd”, “userDesc” будут отправлены в запросе как JSON объект “{ userName: 'admin', userPwd: 'abc123!', userDesc: 'Administrator' }”. Нажмите кнопку “Send”. Придет ответ: В ответе мы видим JSON объект модели “User”, созданной на сервере. Перейдите в приложение “Robomongo”, выполните пункт контекстного меню “View Documents” на таблице “users”. Отлично, у нас появился первый пользователь :) Перейдите в “Postman”, выберите HTTP глагол “GET”. Нажмите кнопку “Send”. Вы увидите такой результат. Обратите внимание на квадратные скобки, в ответе пришел массив JSON объектов, а не один объект. Теперь давайте изменим пароль пользователю “admin”, на “12345678”. Нам понадобится идентификатор пользователя, выделите и скопируйте значение свойства “_id”. Выберите HTTP глагол “PUT”. Обновите данные так, как показано ниже. Обратите внимание на добавление идентификатора пользователя после основного маршрута “../users/”. Нажмите кнопку “Send”. В ответе получим старый JSON объект до внесения изменений. Теперь для того, чтобы посмотреть измененный объект, воспользуемся сервисом , который возвращает пользователя по его идентификатору “_id”. Выберите HTTP глагол “GET”, обновите адрес, добавьте в конец адреса идентификатор пользователя. Нажмите кнопку “Send”. Получим такой результат. Видим, что пароль пользователя был успешно обновлен и мы получили в ответе один JSON объект, а не массив пользователей. Теперь попробуем удалить пользователя. Адрес оставьте тот же. Выберите HTTP глагол “DELETE”  и нажмите кнопку “Send”. Видим, что в ответе содержится JSON объект удаленного пользователя.             Теперь проверим, доступен ли пользователь после удаления. Адрес оставьте тот же. Выберите HTTP глагол “GET”, нажмите кнопку “Send”. Видим, что пользователь действительно был удален. Также можете проверить это в приложении “Robomongo”.             Теперь приступим к тестированию сервиса “products”.             Получим список товаров. Товаров нет. Добавим первый товар. Обновим описание товара. Получим обновленный товар по его идентификатору. Удалим товар. На этом мы завершаем урок. К данной статье прикреплен архив с готовым сервером на NodeJS , в помощь  учащимся :) На следующем уроке мы: выполним рефакторинг проекта “Warehouse” под “iOS”; научимся пользоваться CollectionViewController; научимся пользоваться библиотекой “Alamofire”, в конце урока будут доступны исходники полноценного рабочего приложения; научимся сохранять объекты в настройки телефона и извлекать их; реализуем функциональность для работы со всеми сервисами, созданными в данном уроке; добавим представление регистрации; добавим представление редактирования пользователей. Материалы к статье тут.
Project Manager в IT компанії

Автор: Редакция ITVDN

В ноябре 2016 года на ITVDN появятся первые видео курсы по управлению проектами. Дмитрий Охрименко, CEO ITVDN, встретился с Еленой Петровой, новым бизнес-партнером ITVDN и автором курсов по Project Management, чтобы обсудить вопросы, которые интересуют пользователей нашего образовательного ресурса. Дмитрий: Елена, пользователи ITVDN - в основном программисты. Расскажи, пожалуйста, в чем суть Проектного Управления и чем новый курс будет полезен для разработчика? Елена: Как ты знаешь, разработчики работают в команде, которой управляет Project Manager. Если нет PM` a, то есть Team Lead, и в этом случае он - менеджер, который организовывает процесс работы. Разработчику нужно понимать, почему от него что-то требуют, ведь бытует мнение, что работа разработчика — это творчество, а на самом деле это не совсем так, есть ограничения. Есть ограничения по требованиям, по каким-то условиям в компании, в зависимости от модели бизнеса. Например, в той компании, в которой работает разработчик, она диктует определенные правила работы. Опять же, в зависимости от того, какая организационная структура - матричная, проектная или функциональная - это все очень влияет на его полномочия.   Дмитрий: Надеюсь, это есть в курсе? Елена: Конечно, в курсе есть и это, также в курсе мы подробно рассматриваем состав команды. Кстати, это тоже очень полезно для разработчиков - знать, с кем он работает и какие обязанности у других людей. Мы рассматриваем бизнес аналитика, какие у него задачи и возможности, какие задачи и обязанности у sales manager. Достаточно часто бывает такое, что у разработчиков, у всей команды вцелом, включая PM`a, есть определенное недовольство sales`ами. Дмитрий: Обязательно кто-то должен быть виноват во всем. Елена: Да, продали то, что нереализуемо или еще что-то. В таком случае мы пытаемся в этом курсе показать, что у всех есть определенные интересы, но успех бизнеса будет только в том случае, если они согласуют между собой все эти интересы и построят такой процесс, когда кому-то придётся чем-то пожертвовать для общего блага компании. Дмитрий: На основе какой методологии управления проектом будет построен курс? Какая это методология? Елена: Я бы могла сказать, что курс построен на PMBoK, но на самом деле нет. PMBoK- это обо всем, это идеальный вариант, сбор лучших практик со всего мира с разных проектов, но далеко не все это применимо в жизни. Каждый PM должен знать, что требует PMBoK, это никогда не помешает и только разнообразит его методы управления. Но применять в жизни он должен очень выборочно и очень осторожно. Далеко не всегда то, что применимо в NASA, применимо для компании, которая занимается e-commerce. Дмитрий: По Вашему мнению, что самое сложное в работе PM`a? Самое интересное и самое сложное? Елена: Как ни странно, PM - это посредник, даже больший посредник чем sales. И ему нужно быть очень гибким, ему нужно понимать комбинации различных интересов. Самое важное и сложное то, с чем многие PM`ы "факапят", когда они становятся на сторону команды, но на самом деле PM выражает интересы бизнеса, не заказчика, не команды. Он - наемный работник, он работает на бизнес, его задача, чтобы клиент был доволен, но и при этом бизнес не пострадал. Есть разные способы не навредить и получить выгоду, об этом я тоже буду говорить в курсе, но самое важное здесь - правильно выбрать позицию. Ни в коем случае ни перед кем не очернять заказчика или компанию. PM очень часто overtime-ит, потому что он пытается объять необъятное. Нужно сконцентрироваться на общих интересах для проекта, удовлетворить каждого разработчика все равно не получится. Он должен занять очень умеренную позицию – это сложно, но интересно. Я бы сказала, что для PM`a очень важен склад характера, когда он готов рассматривать различные решения той или иной проблемы и принимать комплексное решение. Как говорят, успех проекта - это в срок, в рамках бюджета и объема работ, также это успех и для PM`a. Но, на самом деле ,это не совсем так. Самое важное - это получить выгоду и не растерять команду. То, о чем мы говорили: на чью сторону должен стать PM, если есть конфликт между заказчиком и командой. Становиться на чью-то сторону нельзя. Нужно правильно уметь объяснить команде, почему то или иное решение было принято. Дмитрий: PM должен преподнести так проблему, чтобы и команда осталась в плюсе, и проект принес то, что бизнес изначально планировал получить. Из Вашего опыта, например, разработчики общаются с тестировщиками. Тестировщик - это отдельный типаж работников в ИТ компании, а разработчик — это другой типаж. У каждого есть свои особенности и фишки. Что самое сложное в общении между PM и разработчиком? И есть ли вообще такие сложности? Елена: Сложности в общении зависят только от человека. Если он готов идти на компромиссы и заинтересован в результате, то всё решаемо. На мой взгляд, есть более серьезные проблемы. Когда разработчик не понимает, что нужно сделать. Он хочет что-то сделать и делает это хорошо, но со своей точки зрения. Из-за этого бывают конфликты, причем зачастую между разработчиком и бизнес-аналитиком, если такой, конечно, есть. В маленьких компаниях обычно все функции бизнес-аналитика выполняет PM или Team Lead разработчик. Если не было уделено достаточно внимания проработке бизнес-потребности заказчика, то разработки могут быть потом и не применимы в жизни.   Дмитрий: То есть, проблема обычно в непонимании предметной области? Елена: Я бы не сказала, что это так. Скорее, это непонимание потребности заказчика. Ведь у него есть свои ограничения, свои требования по внешним условиям. У нас был как-то случай. Проект нужно сделать к определенному сроку. Начинается футбольный сезон и все – должен быть запущен продукт. Тут были ограничения по скоулпу и по функционалу, который действительно нужен. Ведь заказчик не всегда понимает, что ему нужно. Он дает материал, но после анализа команда понимает, что нужно ограничиться и делать только необходимое. Тут как раз и начинается работа и PM, и бизнес-аналитика, и команды разработчиков, поскольку чрезмерное увлечение архитектурой, например, или «как было бы хорошо сделать» не всегда работает. При этом, упрощая, нужно правильно масштабировать проект, чтобы впоследствии можно было бы как-то дорабатывать продукт. Дмитрий: Еще вопрос. У меня есть много знакомых, которые работают PM, и они регулярно пытаются изучать программирование на C#, JS, Java. Вот насколько это вообще нужно PM? Правильно ли это – пытаться понять работу разработчика, влезать в нее, помогать? Елена: Думаю, они должны знать, что такое объектно-ориентированное программирование, должны знать основные технологии. На мой взгляд, достаточно понимать, в какой предметной области ты работаешь, какого типа продукт ты создаешь и какие технологии сейчас применимы к разработке подобных продуктов. Если PM будет нырять глубже, это будет только мешать. Точно так же и разработчики – я рекомендую только первый курс «Введение в PM», это даст им понимание, в какой среде они работают, почему именно так устроена компания, почему именно такие решения принимает менеджер или руководитель компании. Разработчикам желательно знать и понимать такие вещи, от этого будет зависеть успешность их работы. Дмитрий: Насколько реалистична ситуация, когда разработчик становиться PM? Например, некоторые наши студенты на вопросы «Кем хотите быть?», «Как Вы видите свое дальнейшее развитие?» кто-то отвечает, что через какое-то время хочет стать Team Lead/Senior Developer, кто-то хочет стать PM. Насколько такой процесс развития разработчиков реалистичен? Елена: Все вполне реально. Есть замечательные PM – выходцы из разработчиков. Но тенденция такова, что лучше разработчику стать Product менеджером, тем более, что сейчас активно стараются создать тенденцию развития продуктового бизнеса. И разработчику с опытом и навыками, которыми он владеет, с его отношением к работе, более интересно будет управлять продуктом. Разработчику ,скорее, нужно больше знаний в бизнес-анализе и в управлении людьми. Например, управление расписанием, рисками и т.д. Дмитрий: А кто будет идеальным PM? Какой портрет человека? Чем он должен заниматься? Елена: Лично мое мнение из того, с чем я сталкивалась, такое, что идеальные PM выходят из QA специалистов. Вот, действительно, очень хороши. Говорят, что девушки - лучшие PM, нежели мужчины. Тут можно поспорить. Лишь до определенной степени это правда. У девушек слишком живой ум, они впечатлительнее мужчин, а PM должен четко понимать границы проекта, границы компании. И какие бы эмоции или мысли не возникали, хороший PM всегда должен придерживаться этих рамок. Я знаю отличных PM и девушек, и парней, и разработчиков, и даже тех, кто не связан с IT. Сейчас многие идут в IT на PM, ведь программирование для этого учить не нужно. Не всегда в таком сценарии все получается, но бывают и удачные случаи. Дмитрий: Ты сказала, что программисту лучше расти в сторону Product менеджмента. В чем кардинальные отличия между продуктовыми компаниями и аутсорсинговыми, с точки зрения PM? Елена: В продуктовой компании главный Product менеджер. Дмитрий: PМ там не нужен? Елена: Нет, PМ там нужен, обязательно. Product менеджер разрабатывает стратегию развития продукта, а какие-то элементы или дополнения к продукту идут как проекты. Вот ими, обычно, и занимается PМ, подчиняясь Product менеджеру. В продуктовых компаниях заказчиком является Product менеджер, в то время как в аутсорсинговых компаниях заказчик вне бизнеса. Поэтому в аутсорсе приходиться комбинировать интересы внешнего заказчика и внутреннего бизнеса. В продуктовых компаниях, на мой взгляд, у PМ меньше ответственности: он принимает меньше решений, у него меньше посредничества. Дмитрий: Он просто управляет отдельными маленькими проектами, которые нужны для общего развития продукта? Елена: Да, верно. У PМ в продуктовых компаниях вполне определенные организационные функции – это скучнее, но безопаснее, чем в аутсорсе. Зато есть четкие задачи, четкое понимание продукта, ситуации, связей. А ответственность за идею и главную цель несет Рroduct менеджер. В аутсорсинговых компаниях нет как такового Product менеджера, а РМ приходиться постоянно лавировать. Работа более изматывающая, но некоторые находят это более живым и интересным. Дмитрий: Чтобы стать Product менеджером, какие знания и навыки нужно культивировать? Что читать и где учиться? Елена: Сейчас есть очень много информации для Product менеджеров. И вебинаров хватает на тему «Кто такой Product менеджер?», и курсы появляются. На мой взгляд, вне зависимости от того, разработчик ты, тестировщик или еще кто-то, чтобы стать Product менеджером, необходимо пройти через бизнес-аналитика. Нужно получить навыки бизнес-аналитика, поскольку даже в аутсорсинговой компании главный человек, который работает с продуктом – это аналитик. Хороший специалист изучает бизнес заказчика, рынок, конкурентов, продукт, он должен понимать, какие "фичи" этого продукта будут эффективными с точки зрения экономической эффективности. И это первый шаг для того, чтобы стать Product менеджером. Дмитрий: Какой второй? Елена: У Product менеджера еще больше функций, помимо аналитики. Он является, в первую очередь, носителем идеи самого продукта, «гореть» самой идеей данного продукта. Также Product менеджер должен понимать, какие бизнес-потребности не только своих пользователей он решает, но и свои - почему он выгоден. Почему вообще владелец бизнеса спонсирует производство этого продукта? Product менеджер – это смесь технической роли, аналитики и серьезного понимания бизнеса. Так что, если разработчик не планирует идти вглубь технологий, а заняться карьерным ростом, то сначала это бизнес-аналитика, потом Product менеджер, ну а там уже много вариантов. Многие создают свои компании, именно пройдя такой путь. Дмитрий: Они получают понимание бизнеса, видят, как работает сама «кухня». Елена: Да. Сейчас очень много компаний, где руководители – это бывшие разработчики. Но им не хватает именно понимания того, как организовать работу, процесс. Дмитрий: Думаю, нашим студентам будет полезна эта информация про РМ и Product менеджера. Елена: Надеюсь. Я записывала курс «Введение в РМ» не столько, как курс по Product менеджменту, это курс по менеджменту в IT компании. Как я говорила, он будет полезен любому человеку, который работает с командами. Это может быть Team Lead или HR. HR-ам и рекрутерам это будет мега-полезно, потому что они занимаются поиском и подбором персонала и им нужно задавать правильные вопросы, уметь правильно оценить резюме. А также будет полезно sales’ам, они не всегда понимают, как работает операционная сторона. Если они прослушают курс, то поймут, чем занимается РМ, по каким вопросам с ним можно консультироваться. Дмитрий: Кто больше всего критикует работу PМ? Елена: Разработчики. Они говорят, что РМ не нужны, они мешают работать. Я частично с этим мнением согласна, но ведь одна из задач РМ – это устранять проблемы для разработчиков. А задача разработчиков – взаимодействовать, участвовать в организации процесса работы. От них требуется помощь РМ, потому что он не всегда может обосновать, почему то или иное решение приняла команда. И опять же, если в проекте идет что-то не так, в этом виноват не один только РМ, в этом виновата вся команда. Дмитрий: Но РМ в первую очередь, потому что… Елена: Да, потому что не смог организовать команду, чтобы она вместе предотвратила эту проблему. Я очень рада, что записываю данный курс, потому что в нем смогу отразить свой опыт работы в процессном менеджменте. Я отлично видела роли и их взаимодействие на уровне продукта, организации, компании и т.д. Дмитрий: Какого опыта у тебя больше? С чем больше работала? Елена: Я, на самом деле, процессный аналитик. Мое основное достижение – это руководство процессом оценивания компаний по модели CMMI. Это модель управления процессами в компании, она охватывает как инженерные процессы, самые простые, так и процессы управления проектами, управления организации и поддержки (системные администраторы и т.д.). Любой человек, который занимается организацией рабочих процессов, знает, что есть такой инструмент, как теория ограничений, она показывает, где в цепочке производства компании самые узкие места, и проблема обычно на стыке разных специальностей, на стыке разных профессий, разных отделов.
Введення в розробку програм під iOS. Частина 5

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы научимся: Добавлять иконку приложения; Добавлять картинки в приложение. Разберем, как пользоваться “Assets”. Откройте проект “Warehouse”. В панели навигатора откройте модуль “Assets.xcassets”.             Нажмите “App Icon”, вы увидите область с контейнерами для добавления иконок приложения. На рисунке выше  нас интересуют два контейнера “iPhone App iOS 7-10 60pt”, тут задается иконка приложения. В разделе “iPhone Notification iOS 7-10 20pt” задается иконка, которая будет отображаться при получении “Push” уведомлений от вашего приложения в заголовке сообщения. В разделе “iPhone Spotlight – iOS 5,6 Settings – iOS 5-10 29 pt”  задается иконка приложения, которая будет отображаться в диалоге поиска вашего телефона (если приложение под iOS 5 или 6) и в настройках телефона. В разделе “iPhone Spotlight iOS 7-10 40pt”  задается иконка, которая будет отображаться в диалоге поиска вашего телефона. Теперь давайте разберем, что означают метки “2х” и “3х”.  Метки- это размер иконок. Например, ваша исходная иконка имеет размер “16х16”, для нее будет задана метка “1х”. Соответственно, для метки “2х”  будет размер “32х32” (16+16 x 16+16), т.е. в два раза больше исходной иконки под меткой “1х”. Для метки “3х”  будет размер “48х48” (16+16+16 x 16+16+16), т.е. в три раза больше исходной иконки под меткой “1х”. Принято именовать иконки следующим образом: Иконка с меткой 1х используется для iOS ниже 7 версии. Вам ее не нужно делать, так мы используем язык программирования “Swift” в нашем приложении и доступен он, только начиная с версии “iOS 8”, не ниже. В “iOS” ниже версии 8  доступен для использования язык программирования “Objective C”, его я не буду разбирать вообще, поскольку у него не удобочитаемый синтаксис и для новичков он будет сложным. Т.е., должно быть в таком формате “[Имя исходной иконки][Размер в “pt”][Метка].[Расширение файла иконки]”. Таблица с размерами и метками иконок для раздела “iPhone Spotlight – iOS 5,6 Settings – iOS 5-10 29 pt”: Таблица с размерами и метками иконок для раздела “iPhone Spotlight iOS 7-10 40pt”: Таблица с размерами и метками иконок для раздела “iPhone App iOS 7-10 60pt” (иконка нашего приложения): Таблица с размерами и метками иконок для раздела “iPhone Notification iOS 7-10 20pt”: Итак, приступим к созданию нашей иконки. Скачайте  файл “warehouse.png” (размер 512х512)  ниже на странице себе на компьютер. Откройте любой графический редактор, которым умеете пользоваться и создайте файлы иконок в соответствии с 4 разделами, приведенными выше в таблицах. Имена файлов должны соответствовать тем, что в 4 таблицах выше. Как пользоваться графическим редактором, рассматривать не стану. Под OS X  есть бесплатный графический редактор “Gimp”, есть платный “Adobe Photoshop”. После обработки данного файла в графическом редакторе должны получиться на выходе следующие файлы: Отлично с этим справились, теперь необходимо перетащить из “Finder” иконки “*.png” в соответствующие места в “App Icon” по разделам. Выполните это, должно получиться вот так: Теперь закройте проект “Warehouse” и откройте его снова. Обратите внимание на иконку приложения, которая появилась в панели инструментов. Запустите приложение. Затем выполните комбинацию клавиш “Shift + Command + H”, чтобы выйти на рабочий стол устройства. Обратите внимание на иконку приложения “Warehouse”, она тоже изменилась. А вот так она выглядит в диалоге поиска “Spotlight”: Теперь разберем, как добавлять картинки в ресурсы и отображать их в элементах управления. Тренироваться будет на списке товаров. Прилагаю список картинок, которые мы будем использовать в ресурсах. Скачайте эти картинки, затем выделите весь список. Откройте модуль “Assets.xcassets” и перетащите из “Finder” в пустую область под “App Icon” выделенный список загруженных картинок. Должно получиться вот так: Все бы хорошо, если бы только не нововведения для картинок, начиная с iOS 10 и Xcode 8. До iOS 10, добавляя картинки подобным образом в ресурсы, все без проблем загружалось в AppStore. Но вот начиная с iOS 10, было принято, чтобы картинки были в двух разных форматах “sRGB” и “Display P3”, предпринято это было для возможности управления насыщенностью цветов. Более подробную информацию, как к этому пришли, можно получить, изучив “WWDC 16, Session 712, Working with Wide Color, Understanding and optimizing for Wide Color Gamut Displays”. Если использовать версию среды разработки, например, Xcode 7.3.1, то в ней с минимальной версией приложения iOS 8.0 не надо делать никаких конвертаций с картинками, все без проблем загружается в AppStore. Если же загружать без конвертации “sRGB and Display P3” со среды разработки “Xcode 8”, то на этапе валидации мы получим ошибку “Invalid Bundle. The asset catalog at '$path' can't contain 16-bit or P3 assets if the app is targeting iOS releases earlier than iOS 9.3”. Да, с одной стороны можно было поставить минимальную версию iOS 9.3, ничего не конвертировать и отправить приложение в AppStore, но тогда бы я потерял пользователей, у кого iOS 8, 8.1, 8.2, 8.3, 9, 9.1, 9.2, 9.2.1. Пришлось разбираться, как это исправить, чтобы выполнить загрузку в AppStore, и при этом минимальная версия так и осталась iOS 8. Для конвертации изображений в OS X есть специальная бесплатная встроенная утилита “ColorSync Utility”. Картинки, которые я показывал выше, имеют формат “RGB”, чтобы убедиться в этом. В “Finder” выполните клик правой кнопкой мыши на картинке, например, “tool001.png” и в контекстном меню выберите пункт “Get info”. Обратите внимание на свойство “Color space”, его значение “RGB”. Создайте где-нибудь две папки с названиями “Tools_sRGB” и “Tools_Display_P3”. В эти папки мы будем складывать сконвертированные картинки. Картинка формата “Display P3” (16 битная) будет автоматически подгружаться, если у пользователя iOS 10, если же у пользователя версия операционной системы ниже iOS 10, будет браться картинка формата “sRGB” (8 битная). Если вы добавите только картинки в формате “sRGB”, тогда, если у пользователя операционная система iOS 10, эти картинки будут каждый раз конвертироваться из формата “sRGB” в формат “Display P3”. Добавить только картинки формата “Display P3” и не добавить картинки формата “sRGB” нельзя, надо или в обоих форматах, или только в формате “sRGB”. Если вы обратили внимание, когда добавляли картинки в “Assets.xcassets”, а затем выделили одну из добавленных картинок, с правой стороны от нее был отображен контейнер “Universal”. Давайте включим поддержку “Display Gamut”. Выделите “tool001.png”, в панели свойств откройте вкладку “Show the attributes inspector”. Найдите свойство “Gamut”, в нем сейчас установлено значение “Any”, установите значение “sRGB and Display P3”.  Перейдите в “Finder”, теперь мы должны сконвертировать исходные загруженные картинки в два формата “sRGB” и “Display P3”. Для этого в “Finder”  выделите картинку “tool001.png”, выполните по ней клик правой кнопкой мыши и в контекстном меню выберите “ColorSync Utility.app”. В открывшемся приложении “ColorSync utility”, внизу в выпадающем списке, измените значение “Match to Profile” на “Assign Profile”. Затем нажмите на выпадающий список “None” и выберите “Display”, затем “sRGB IEC61966-2.1”. Кнопка “Apply” станет активной, нажмите на нее, затем закройте окно ColorSync “tool001.png”, нажав на красную кнопку в правом верхнем углу окна. Вам будет предложено сохранить изменения. Сохраните изменения. Теперь перейдите в папку “Tools_sRGB”, которую мы создавали ранее, и выделите файл “tools001.png”, нажмите на нем правой кнопкой мыши и в контекстном меню выберите пункт “Get info”. Обратите внимание на свойство “Color profile”, оно теперь имеет то значение “sRGB IEC61966-2.1”, которое мы устанавливали через утилиту “ColorSync”. Теперь проделайте такую же операцию, еще раз с исходным файлом изображения “tool001.png”, но на этот раз выберите “Color profile” - “Display P3”. Нажмите кнопку “Apply” и закройте окно ColorSync “tool001.png”, сохраните изменения в папку “Tools_Display_P3”. Затем откройте “Finder”, перейдите в папку “Tools_Display_P3”, выделите файл “tool001.png”, нажмите на нем правой кнопкой мыши и в контекстном меню выберите пункт “Get info”. Обратите внимание на свойство “Color profile”, там установлено значение “Display P3”. Теперь перейдите в Xcode, откройте модуль “Assets.xcassets”, выделите изображение “tool001” и перенесите из “Finder” картинки в контейнер. В поле “1x sRGB” перетащите картинку “tool001.png” из папки “Tools_sRGB”. В поле “1x Display P3” перетащите картинку “tool001.png” из папки “Tools_Display_P3”. Должно получиться вот так: Остальные картинки сконвертируйте самостоятельно в форматы “sRGB” и “Display P3” по той же схеме, что мы делали выше. Ниже вы можете скачать две сконвертированные картинки. Также добавим в “Assets” еще картинку фона страницы авторизации. Она имеет размер “750х1334” и называется “warehouse-view.png”. Картинку тоже необходимо сконвертировать в два формата “sRGB” и “Display P3”. В панели навигации откройте модуль “Main.storyboard”, найдите View с именем “View Controller”, из панели компонентов перетяните на View  компонент “ImageView”. Растяните его на всю область View. Нажмите кнопку “Pin” и задайте такие ограничения: Нажмите кнопку “Add 4 Constraints”. Измените цвет кнопки “Вход” на оранжевый, цвет текста “Нет ошибок” на белый. Откройте модуль “SuppliesViewController.swift”. Обновите модель “supplies”, как показано ниже (заполнены свойства “productImage”): Обновите метод “cellForRowAt indexPath”: Тут изменения только на 92 строке, в ячейке есть встроенный компонент “ImageView”, мы обращаемся к его экземпляру и от него инициализируем свойство “image”. В инициализаторe “UIImage” мы задаем аргумент “named”, название “Image Set” из “Assets”. Запустите приложение. На этом мы завершаем данный урок. Большой получился урок, но тему “Assets” надо было обязательно рассмотреть, чтобы в будущем к ней уже не возвращаться. В следующем уроке мы рассмотрим, как работать с библиотекой “Alamofire” и интегрируем ее в наше приложение. Создадим тестовый сервер, в который добавим таблицу пользователей для авторизации, а в последующих уроках переведем работу списка продуктов на сервер и перепишем клиент на интеграцию с сервером.
Введення в розробку програм під iOS. Частина 4

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы научимся: скрывать клавиатуру мобильного устройства для полей “Text Field”, “Text View”; добавлять  различные типы клавиатур цифровая, e-mail, URL; добавлять панель инструментов на клавиатуру мобильного устройства; добавим панель поиска в список товаров; добавим предварительный просмотр на ячейки списка товаров с использованием аппаратной технологии 3D Touch. Откройте проект Warehouse. В панели навигатора откройте модуль “ViewController.swift”, нам необходимо унаследоваться от протокола “UITextFieldDelegate”: Теперь реализуем этот протокол: Разберем код построчно. На 43 строке мы реализовали метод “textFieldShouldReturn” протокола “UITextFieldDelegate”. Метод вызывается, когда вы на клавиатуре мобильного устройства нажимаете кнопку “Return” (Done, Next). Метод принимает один аргумент, который содержит экземпляр текстового поля, для которого была вызвана клавиатура. Возвращаемое значение данного метода говорит о том, скрывать клавиатуру или нет. Мы возвращаем значение “true”, означающее, что мы хотим скрывать клавиатуру. С 44 по 51 строку идет поиск активного в данный момент текстового поля, куда пользователь установил курсор. Данный код мы добавили, чтобы сделать следующую функциональность. Пользователь ставит курсор в поле “Имя пользователя”, вводит имя пользователя. Затем на клавиатуре нажимает кнопку “Next”, курсор автоматически попадает на поле пароль. После того, как пользователь ввел пароль, он нажимает кнопку “Done” и клавиатура пропадает. Чтобы поставить курсор в текстовое поле, необходимо на экземпляре этого текстового поля вызвать метод “becomeFirstResponder”. Чтобы убрать курсор с текстового поля, необходимо на экземпляре текстового поля вызвать метод “resignFirstResponder”. На 55 строке мы переопредили метод “touchesBegan”. Данный метод объявлен в классе “UIResponder”, класс “UIViewController” наследуется от этого класса. Метод “touchesBegan” вызывается, когда вы нажимаете в любую свободную область экрана, находящуюся вне редактируемого текстового поля. На 56 строке мы обращаемся к внутреннему свойству “view”, которое содержит экземпляр представления, подвязанного к контроллеру. И на нем вызываем метод “endEditing” со значение аргумента “true”. Данный метод посылает представлению событие о том, что необходимо завершить редактирование элементов управления, которые на нем расположены. Но это еще не все. Теперь необходимо сообщить текстовым полям, что мы реализовали для них соответствующий протокол “UITextFieldDelegate” и хотим проинициализировать у них свойство “delegate”. Обновите код в методе “viewDidLoad”: На 30 и 31 строке мы проинициализировали свойство “delegate” для текстовых полей “Имя пользователя” и “Пароль пользователя”. Теперь эти поля знают, откуда им надо вызывать реализацию метода “textFieldShouldReturn”. Откройте модуль “Main.storyboard”, найдите и выделите представление “View Controller”. На этом представлении выделите текстовое поле “Имя пользователя”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Return key” и установите в него значение “Next”. Затем выделите текстовое поле “Пароль пользователя” и установите для его свойства “Return key” значение “Done”. Запустите приложение. Если клавиатура у вас не открылась в симуляторе, выполните в меню симулятора “Hardware -> Keyboard -> Toggle Software Keyboard”. Теперь давайте разберем, какие типы клавиатур имеются и в каком свойстве они задаются. Потренируемся пока на текстовом поле “Имя пользователя”. Выделите это текстовое поле в дизайнере и в панели свойств на вкладе “Show the Attributes inspector” найдите свойство “Keyboard Type”, в этом свойстве задается тип вызываемой клавиатуры для текстового поля. Часто используемые виды клавиатур: Default – клавиатура по умолчанию, содержит в себе все раскладки. Numbers and punctuation – клавиатура содержит цифры и знаки пунктуации. URL – клавиатура содержит все необходимые символы для быстрого ввода URL адреса. Number Pad – клавиатура содержит только цифры, без знаков разделения на дробные составляющие. Phone Pad – телефонная клавиатура. E-mail Address – клавиатура содержит необходимые символы для быстрого ввода e-mail адреса. Decimal Pad – клавиатура содержит цифры и знаки разделения на дробные составляющие. Теперь добавим функциональность закрытия клавиатуры для представления редактирования продукта. Откройте модуль “ProductViewController.swift”. Добавьте наследование от двух протоколов “UITextFieldDelegate”, “UITextViewDelegate”, для класса “ProductViewController”: Реализуем эти протоколы: Методы на 58 строке и 69 мы рассматривали уже ранее, не буду повторяться. На 64 строке мы реализовали метод “textViewDidEndEditing”, данный метод вызывается, когда редактирование многострочного текстового поля было завершено. Обновим код в методе “viewDidLoad”: Запустите приложение. Закрытие клавиатуры будет работать только для текстового поля “Название”, для описания работать не будет. Дело в том что, чтобы скрыть клавиатуру у поля “Text View”, надо добавить дополнительную панель инструментов над клавиатурой и на нее добавить кнопку, которая будет закрывать эту клавиатуру. Разберем, как это сделать. Обновите код: Разберем код построчно. На 37 строке мы вызываем метод “addToolbar”, который добавим ниже. Данный метод принимает аргумент типа “UITextView”, в него мы передаем экземпляр текстового поля, к клавиатуре которого будет привязана панель инструментов с кнопкой закрыть. На 40 строке мы объявили метод “addToolbar”. На 41 строке мы создали экземпляр панели инструментов. Чтобы добавить эту панель на клавиатуру, надо проинициализировать свойство “inputAccessoryView” созданной панелью инструментов. С 42 по 44 строку мы задаем визуальные стили панели инструментов. На 45 строке мы создаем экземпляр кнопки пробел. На 46 строке мы создаем экземпляр кнопки “Х Закрыть” и привязываем к нему метод обработчик “donePressed” на нажатие по кнопке. На 47 строке мы добавляем кнопки на панель инструментов. На 48 строке говорим, что панель инструментов будет принимать действия пользователя. На 49 строке мы вызываем метод “sizeToFit”, который растянет панель инструментов по ширине экрана. На 51 строке инициализируем свойство “delegate” текстового поля. На 52 строке добавляем панель инструментов на клавиатуру. Запустите приложение. Теперь, при нажатии на кнопку “Х Закрыть” на панели инструментов клавиатуры, клавиатура закрывается. Данный код еще удобно использовать для цифровых клавиатур. Добавим поле поиска товаров в представление списка товаров. Откройте модуль “SuppliesViewController.swift”. Добавьте наследование от протоколов “UISearchResultsUpdating” и “UISearchBarDelegate”. Обновите код: Разберем код. На 27 строке мы добавили свойство “filteredSupplies”, в котором будут содержаться найденные товары через поле поиска. На 28 строке мы добавили свойство, которое указывает, активно ли сейчас поле поиска или нет. На 29 строке мы объявили пока еще не инициализированный экземпляр контроллера для поля поиска типа “UISearchController”. Отлично, теперь обновим методы табличного представления “numberOfRowsInSection” и “cellForRowAt indexPath”: Разберем код. На 74 строке мы проверяем, является ли поле поиска в данный момент активным (т.е. мы в данный момент поставили в него курсор). Тогда на 75 строке мы возвращаем количество товаров из отфильтрованной коллекции. Иначе на 78 строке мы возвращаем исходный список товаров. На 84 строке мы изменили получение модели товара. Теперь мы проверяем: если поле поиска в данный момент активно, то возвращаем товар из отфильтрованной коллекции, иначе возвращаем товар из исходной коллекции. Ошибки с индексом за пределами коллекции у нас не будет, так как выше мы обновили реализацию метода “numberOfRowsInSection”. Теперь необходимо добавить метод, который будет инициализировать контроллер для поля поиска: Разберем код этого метода. На 131 строке мы инициализируем свойство “searchController”, которое объявляли ранее. На 132 строке мы указываем ссылку на представление, где находятся данные, которые мы будет фильтровать. На 133 строке мы указываем, доступен ли нам основной контент в ходе поиска данных. На 134 строке мы задаем текст, который будет отображаться в поле поиска. На 135 строке мы инициализируем свойство “delegate” поля поиска, тем самым говоря, откуда ему брать реализацию связанных с ним протоколов. Мы указали, что реализация находится в контроллере “SuppliesViewController”. На 136 строке мы вызываем метод “sizeToFit” на поле поиска, чтобы растянуть панель поиска по ширине экрана. На 137 строке левым операндом мы обращаемся к свойству “tableHeaderView” экземпляра таблицы и правым операндом инициализируем его нашим полем поиска. Тем самым мы добавляем в заголовок нашей таблицы поле поиска. Наше приложение на русском языке. А когда поле поиска становится активным, мы видим справа от него кнопку с текстом на английском языке “Cancel”, давайте сразу это исправим, чтобы потом не возращаться к этому: Разберем построчно код. На 141 строке мы обращаемся к свойству “searchController”, от него вызываем свойство “searchBar” - это наше поле поиска. И на экземпляре поле поиска вызываем свойство “showsCancelButton”, говорящее о том, отображать кнопку “Cancel” справа от поля поиска или не отображать. Мы говорим, что будем отображать. На 142 строке мы объявляем переменную типа “UIButton” с именем “cancelButton”. На 143 строке мы получаем представление, в котором будем производить ниже поиск кнопки с текстом “Cancel” по типу разумеется :) На 144 строке мы проходимся по иерархии вложенных представлений в поле поиска. На 145 строке мы проверяем: если представление имеет тип “UINavigationButton”, то это то представление, которое нам надо, это и есть наша кнопка с текстом “Cancel”. На 146 строке мы инициализируем переменную “cancelButton” экземпляром найденной кнопки. На 147 строке мы задаем текст кнопки “Отмена”. Реализуем протокол “UISearchResultsUpdating”: Разберем код. На 154 строке мы вызываем метод “searchDisplayControllerWillBeganSearch”, который ранее рассматривали уже, и передаем в аргумент “controller” экземпляр контроллера поиска, который у нас есть в аргументе метода “updateSearchResults”. На 155 строке мы проверяем, ввел ли пользователь какой -либо  текст в поле поиска, если ввел, тогда сохраняем этот текст в переменную “searchString”, если не ввел, тогда выходим из метода “updateSearchResults”. На 159 строке мы выполняем фильтрацию данных из основного источника данных по тексту, который ввел пользователь в поле поиска. Для этого у коллекции “supplies” вызываем метод “Filter”, в который передаем лямбда метод с одним аргументом типа “ProductModel”, и возвращаемым значением типа Bool (найден или не найден товар). На 160 строке мы создаем переменную с именем “product” типа “ProductModel” и инициализируем ее экземпляром аргумента “productItem”. Заметьте, лямбда метод будет вызываться столько раз, сколько элементов товара содержит коллекция “supplies”. На 161 строке мы объявляем переменную, в которой будет содержаться результат сравнения текста поиска с названием или описанием нашего товара. На 162 строке мы выполняем сравнение текста текущего товара в лямбда методе с текстом поля поиска . Оба текста путем вызова метода “lowercased” переведены в нижний регистр и теперь регистро независимы. Если по названию товара совпадений не было найдено, пытаемся сравнить по описанию товара. Результат сравнения записываем в переменную результата “res”. На 164 строке возвращаем результат метода “filter”. На 166 строке запускаем переинициализацию таблицы для отображения отфильтрованных данных и скрытия данных, не попавших под фильтр. Реализуем протокол “UISearchBarDelegate”: Разберем код. На 170 строке мы реализовали метод “searchBarTextDidBeginEditing”, который вызывается, когда пользователь начинает вводить текст в поле поиска. В реализации данного метода мы задаем свойство “shouldShowSearchResults” в значение “true”, тем самым давая понять контроллеру поиска, что поле поиска сейчас активно. Затем мы переинициализируем таблицу. На 175 строке мы реализовали метод “searchBarCancelButtonClicked”, который вызывается, когда пользователь нажал кнопку “Cancel” или в нашем случае “Отмена”, справа от поля поиска. В реализации мы говорим, что поле поиска теперь больше не активно и переинициализируем таблицу. На 180 строке мы реализовали метод “searchBarSearchButtonClicked”, который вызывается, когда на клавиатуре, отобразившейся к полю поиска, мы нажали кнопку “Search” (“Поиск”). В реализации метода мы проверяем: если свойство “shouldShowSearchResults” еще не активно, активируем его и переинициализируем таблицу. Затем скрываем клавиатуру для поля поиска на 185 строке. Обновите метод “viewDidLoad”: На 64 строке мы добавили заголовок представления “Продукты”. На 66 строке мы вызвали метод, который выполняет инициализацию контроллера поиска и добавляет его в наше представление. Запустите приложение. Нажмите на поле “Поиск товара”, введите туда, например, букву “Ш”. Все работает :) Теперь в этом уроке нам еще осталось добавить предварительный просмотр товара при выполнении на нем функции 3D Touch. Данная функция поддерживается только на устройствах iPhone 6s / 6s Plus / 7 / 7 Plus. В этих телефонах установлен специальный дисплей, который реагирует на силу нажатия на него. Даже если у вас нет подобного девайса это не проблема, так как данную функциональность можно проверить в симуляторе. Реализуем протокол “UIViewControllerPreviewingDelegate”. Обновите код “SuppliesViewController”: Теперь обновите метод “viewDidLoad”: На 67 строке мы обращаемся к свойству контроллера “traitCollection”, в котором содержится свойство “forceTouchCapability”, и проверяем: если данное устройство поддерживает данную функцию (3D Touch), тогда вызываем метод “registerForPreviewing”, в который первым аргументом передаем ссылку на контроллер, для которого надо применить данную функциональность, и экземпляр таблицы, на содержимом которой эта функциональность и будет выполняться. Добавим реализацию протокола “UIViewControllerPreviewingDelegate”: Разберем код. На 192 строке мы реализовали метод “previewingContext”, который возвращает экземпляр представления , которое надо отобразить в предварительном просмотре. Метод принимает два аргумента. Первый аргумент “previewContext” типа “UIViewControllerPreviewing” принимает экземпляр контроллера предварительного просмотра. Второй аргумент “location” типа “CGPoint” содержит позицию места, где пользователь выполнил усиленное нажатие по ячейке таблицы. На 193 строке мы получаем экземпляр “indexPath” путем вызова на таблице метода “indexPathForRow”, аргументом которого мы передаем аргумент “location”. Данный экземпляр нам необходим, поскольку у него имеется свойство “row” и по значению этого свойства мы сможем получить нужную модель товара. На 194 строке мы производим поиск контроллера ”ProductViewController” по его “Storyboard ID”. Это представление редактирования товара. На 195 строке мы передаем идентификатор товара в свойство “id” найденного выше контроллера. На 196 строке мы передаем в свойство “sourceRect” полную область ячейки, по которой было выполнено “3D Touch”. Благодаря этому данная область будет выделена, когда вы выполните нажатие “3D Touch” в таблице. На 197 строке возвращаем найденный контроллер “ProductViewController”. На 199 строке возвращается пустое значение, если пользователь выполнил нажатие “3D Touch” вне области таблицы. На 202 строке мы реализовали метод “previewingContext” перегруженный, который ничего не возвращает и отображает представление того контроллера, который мы вернули в методе выше. Запустите приложение. На этом завершим урок. На следующем уроке мы рассмотрим создание иконки нашего приложения и разберемся, как пользоваться “Assets”.
Введення в розробку програм під iOS. Частина 3

Автор: Volodymyr Bozhek

Здравствуйте, дорогие читатели. В этом уроке мы: создадим форму редактирования товара; рассмотрим понятие делегата и удобство его использования; добавим действия редактирования / удаления товара в списке товаров. Откройте проект Warehouse. В первую очередь, давайте изменим цвет View входа в систему с “Light Green” на “White”, я явно перестарался с выбором цвета в самом начале :). В будущих уроках, когда разберем, как пользоваться Assets, мы заменим фон на красивую картинку, подходящую по смыслу к этому приложению, а пока сделаем нейтральный цвет, не бросающийся в глаза. Для этого в панели навигатора откройте модуль “Main.storyboard”, выделите “View Controller”, затем в панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Background” и установите значение “White”. Теперь найдите в библиотеке компонентов компонент “Table View Controller” и перетащите его в свободную область дизайнера рядом с “Supplies View Controller”, созданным в прошлом уроке. Выделите только что добавленный компонент. Найдите кнопку в дизайнере “Show Document Outline”, нажмите на нее, откроется панель документов. На этой панели разверните компонент “Table View Controller” и выделите компонент “Table View”. Затем в панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Content”. По умолчанию у этого свойства установлено значение “Dynamic Prototypes”, установите значение “Static Cells”, в дизайнере вы увидите, что интерфейс контроллера изменился и отобразилось 3 строки. Разберем значения этого свойства. Значение “Dynamic Prototypes” используется, если вам необходимо иметь динамический набор данных, который будет постоянно изменяться. Установив это значение, обязательно необходимо переопределить методы протокола “UITableViewDataSource” для корректной работы контроллера. Значение “Static Cells” используется, если вам необходимо иметь статические данные, которые не будут меняться. Установив это значение, вам не нужно переопределять методы протоколы “UITableViewDataSource”. В данном примере мы устанавливаем это значение с целью удобства размещения элементов управления на нашей View. У нас будут ячейки с таким содержимым: ячейка под картинку, идентификатор товара, название товара, описание товара. Сейчас у нас 3 ячейки, надо чтобы было 4. В первой ячейке будет компонент “Image View”, в остальных 2 ячейках будут элементы управления “Label” и “Text Field”, в последней 4-й ячейке будут элементы управления “Label” и “Text View”. На панели документов для “Table View Controller”  выделите компонент “Table View Section”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Rows”. Сейчас у этого свойство установлено значение “3”. Измените значение на “4”, у вас появится 4 ячейка. В панели документов раскройте компонент “Table View Sections”, вы увидите список ячеек типа “Table View Cell”. Выделите первую ячейку. В панели свойств откройте вкладку “Show the Size inspector”. На этой вкладке можно управлять размерами компонента. Найдите свойство “Custom” и отметьте его галочкой. Затем найдите свойство “Row Height”. Установите значение этого свойства “144”, высота ячейки изменится. Перетащите на эту ячейку из библиотеки компонентов  компонент “Image View” и растяните его на всю область ячейки. Выделите компонент “Image View” и нажмите кнопку “Pin”. В открывшемся диалоговом окне снимите галочку с поля “Constrain to margin” и отметьте позиционирование относительно всех стенок ячейки. Нажмите кнопку “Add 4 Constraints”. В панели документов выделите вторую ячейку. Перетащите из библиотеки компонентов компонент “Label” на эту ячейку. Поместите его с левого края ячейки и растяните по высоте ячейки. В панели свойств откройте вкладку “Show the Size inspector”, установите свойство “Width” в значение “120”. Откройте вкладку “Show the Attributes inspector”, установите безымянное свойство с текстом “Label” на текст “Идентификатор”. Найдите свойство “Font”, нажмите на кнопку со значком “T”, откроется диалоговое окно задания свойств шрифта. Установите свойство “Style” в значение “Bold”, свойство “Size” в значение “12”. Нажмите кнопку “Done”. Представление должно выглядеть так: Из библиотеки компонентов перетащите компонент “Text Field” и расположите рядом с компонентом “Label”. Растяните компонент “Text Field” на оставшуюся ширину ячейки. Выделите элемент управления “Label”, нажмите кнопку “Pin”. Снимите галочку с поля “Constrain to margin”, отметьте позиционирование относительно верхней и левой стенок ячейки. Отметьте галочкой поле “Height”. Нажмите кнопку “Add 3 Constraints”. Выделите элемент управления “Text Field”, нажмите кнопку “Pin”. В диалоговом окне снимите галочку с поля “Constrain to margin”, установите позиционирование относительно левой, верхней, правой стенок ячейки, отметьте галочкой поле “Height”. Нажмите кнопку “Add 4 Constraints”. В панели документов выделите 3 ячейку. Повторите для нее все операции, которые были применены ко второй ячейке. Измените текст элемента управления “Label” на “Название”. Должно получиться вот так: В панели документов выделите последнюю ячейку, на вкладе “Show the Size inspectors”  установите в поле “Row Height” значение “144”, предварительно отметив поле “Custom” галочкой. Перенесите на ячейку два компонента “Label” и “Text View”. В          Установите ограничения для элемента управления “Label”, как мы устанавливали для предыдущих элементов управления “Label”, а для элемента управления “Text View” такие же ограничения, как для “Text Field”. Для элемента управления “Label” установите текст “Описание”, для элемента управления “Text View” уберите текст по умолчанию. Должно получиться вот так: В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductViewController.swift”. Измените код, как показано ниже: Перейдите снова в дизайнер , выделите “Table View Controller”, с которым проводили изменения, в панели свойств, откройте вкладку “Show the Identity inspector”. Установите свойство “Class” значением “ProductViewController”, свойство “Storyboard ID” значением “ProductViewController”. В панели инструментов нажмите кнопку “Show the Assistant editor”. Для элемента управления “Image View” создайте переменную с именем “imageView”.       Во второй ячейке, для элемента управления “Text Field”, создайте переменную с именем “idTxt”. В третьей ячейке, для элемента управления “Text Field”, создайте переменную с именем “nameTxt”. В четвертой ячейке, для элемента управления “Text View”, создайте переменную с именем “descriptionTxt”. Должно получиться вот так: В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductModel.swift”. Добавьте в него следующий код: Разберем добавленный код построчно. На 9 строке мы подключили пространство имен “Foundation” для класса “NSObject”. На 11 строке мы объявили класс с именем “ProductModel”, который наследуется от класса “NSObject” для того, чтобы считаться типом данных. На 12 строке мы объявили переменную с именем “productId” типа “Int” и инициализировали ее значение по умолчанию “-1”. На 13 строке мы объявили поле с именем “productImage” типа “String” и инициализировали ее значение по умолчанию “”. На 14 строке мы объявили поле с именем “productName” типа “String” и инициализировали ее значение по умолчанию “”. На 15 строке мы объявили поле с именем “productDescription” типа “String” и инициализировали ее значение по умолчанию “”. На 17 строке мы объявили параметризированный инициализатор класса “ProductModel” (это тоже самое, что и конструктор в языке программирования C#). Инициализатор - это метод, который вызывается при создании нового экземпляра класса. Данный инициализатор принимает аргументы с такими же именами, как имена полей в классе. Для того, чтобы инициализировать внутренние поля класса значениями полей аргументов. Мы левым операндом обращаемся к самому себе, через ключевое слово “self” и обращаемся к нужному нам полю, а правым операндом задаем значение аргумента. Мы плавно подходим к созданию делегата. В панели навигатора добавьте новый модуль “Swift File” с именем “ProductProtocol.swift”. Добавьте в него следующий код: Разберем добавленный код. На 11 строке мы объявили протокол с именем “ProductProtocol”. Протокол - это тоже самое, что и интерфейс в языке программирования C#. Экземпляр протокола через инициализатор создать нельзя, поскольку это только прототип, который не содержит реализации и используется в качестве описания и задания соглашений, которые разработчик должен реализовать сам в будущем. На 12 строке мы объявили метод прототип с именем “changeProduct”,  который принимает аргумент типа “ProductModel” с именем “model”. Откройте модуль “ProductViewController.swift”, добавьте поле с именем “delegate” типа “ProductProtocol?” и поле с именем “id” типа “Int” со значением по умолчанию “-1”. Должно получиться вот так: Добавьте после метода “viewDidLoad”, новые методы с именами “initNavigationButtons”, “backToParent”, “saveData”, которые ничего не принимают и ничего не возвращают. Код: Разберем построчно добавленный код. На 21 строке мы вызываем метод “initNavigationButtons”, когда View находится в процессе загрузки, чтобы инициализировать кнопки навигации. На 24 строке мы объявили метод с именем “initNavigationButtons”. На 25 строке мы переопределяем левую кнопку навигации, задаем ей текст “Отмена” и обработчик события на нажатие, метод “backToParent”. На 26 строке мы создаем кнопку с именем “saveButton” и текстом “Сохранить”, добавляем для нее обработчик событие на нажатие, метод “saveData”. На 27 строке мы обращаемся к экземпляру панели навигации контроллера “navigationItem” и вызываем от него метод “setRightBarButton”, который принимает два аргумента. Первый аргумент - это экземпляр добавляемой на панель навигации кнопки, второй аргумент - это стиль добавления кнопки, добавить с анимацией или без. Сам метод, как видно из названия, добавляет кнопку справа на панель навигации. Это вариант, как добавить правую кнопку из кода. На 30 строке мы объявили метод с именем “saveData”. На 31 строке мы обращаемся к нашему делегату с именем “delegate” и проверяем, если он инициализирован, тогда вызываем у него метод “changeData” и передаем туда модель данных, заполненную данными собранными с элементов управления. На 32 строке мы создаем экземпляр типа “ProductModel” с именем “model”. На 33 строке мы вызываем у делегата метод “changeData” и передаем туда экземпляр “model”. Данный метод будет реализован в классе “SuppliesViewController”. На 35 строке мы возвращаемся на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View. На 38 строке мы объявляем метод с именем “backToParent”. Данный метод делает переход на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View. Теперь откройте модуль “SuppliesViewController.swift”. Нам необходимо обновить его реализацию: Разберем обновленный код построчно. На 12 строке в классе SuppliesViewController мы наследуемся от протокола “ProductProtocol”. На 13 строке мы заменили тип данных для поля “supplies”. Тип данных был “[String]”, стал “[ProductModel]”. И сделали это поле статическим для того, чтобы была возможность доступа к нему из контроллера “ProductViewController”. На 29 строке мы реализовываем метод “changeProduct” протокола “ProductProtocol”. Именно этот метод будет вызываться при нажатии кнопки “Сохранить” в контроллере “ProductViewController”. На 30 строке мы проверяем идентификатор продукта, если “-1” - это означает, что это новый продукт. Иначе существующий продукт, и мы его обновляем. На 32 строке мы правым операндом генерируем идентификатор продукта путем вызова метода “makeNewProductId”. Данный метод возвращает значение типа “Int”, которое мы присваем левому операнду. Левый операнд - это поле “productId” экземпляра “ProductModel”. На 33 строке мы добавляем новый продукт в коллекцию продуктов. На 36 строке мы объявляем переменную с именем “changedIndex” типа “Int” со значением по умолчанию “-1”. В эту переменную мы запишем найденную ниже в цикле позицию продукта в коллекции “supplies”. На 37 строке мы в цикле проходимся по элементам коллекции “supplies” и ищем продукт, который нужно обновить. На 38 строке описано условие поиска продукта. На 39 строке, когда продукт был найден, мы сохраняем его позицию в переменную “changedIndex”. На 40 строке выходим из цикла. На 43 строке проверяем, если продукт был найден , тогда обновляем этот продукт. С 44 по 46 строку мы обновляем свойства продукта , значениями свойств модели, переданной аргументом метода “changeProduct”. На 49 строке мы запускаем переинициализацию “DataSource” для табличного представления, чтобы данные в ячейках обновились и добавились/удались обновленные данные. На 52 строке мы объявили метод с именем “makeNewProductId”, который ничего не принимает и возвращает значение типа “Int”. На 53 строке левым операндом мы объявили переменную с именем “sortProducts” типа “[ProductModel]”. Правым операндом мы на экземпляре коллекции “supplies” вызываем метод “sorted”, который принимает лямбда метод сортировки коллекции объектов и возвращает отсортированную коллекцию. У лямбда метода есть два аргумента, в них находятся экземпляры сравниваемых объектов из коллекции “suppliers”. Данный метод возвращает значение типа “Bool”. На 54 строке мы возвращаем условие сортировки по возрастанию. Сортировка производится по свойству “productId” класса “ProductModel”. На 56 строке мы обращаемся к последнему элементу отсортированной коллекции, тем самым получаем максимальный идентификатор продукта в коллекции и увеличиваем его на единицу. Теперь надо обновить реализацию метода “cellForRowAt indexPath”: Разберем код построчно. На 69 строке мы изменили вид обращения к коллекции “supplies”. На 73 строке мы изменили для экземпляра ячейки тип стиля. Был “.plain”, стал “subtitle”. На 74 строке мы левым операндом объявили константу с именем “model” типа “ProductModel”. Правым операндом мы инициализируем константу значением из коллекции “supplies”. На 75 строке мы записываем в свойство “tag” идентификатор продукта для этой ячейки. На 76 строке в элемент управления “Label”, который находится в верхней области ячейки, мы записываем название продукта. На 77 строке в элемент управления “Label”, который находится в нижней области ячейки, мы записываем описание продукта. На 78 строке мы активируем в ячейке информационную кнопку справа. При нажатии на эту кнопку мы будем редактировать продукт. Теперь нам надо подвязаться на событие нажатия на информационную кнопку в ячейке. Для этого необходимо переопределить метод протокола “UITableViewDelegate” ,реализованного в классе “UITableViewController” с именем “accessoryButtonTappedForRowWith”. Разберем код построчно. На 83 строке мы получаем экземпляр типа “ProductModel”, привязанный к ячейке, и сохраняем его в константу с именем “model”. На 84 строке мы производим поиск контроллера с именем “ProductViewController” в модуле “Main.storyboard”. На 85 строке на найденном экземпляре контроллера обращаемся к свойству “delegate” типа “ProductProtocol” и инициализируем его ссылкой на самого себя, тем самым говоря, что в этом классе уже реализован метод этого протокола с именем “changeProduct” и вызывать метод надо именно из этого класса. На 86 строке мы на найденном экземпляре контроллера обращаемся к свойству “id” и инициализируем его идентификатором продукта, привязанного к текущей ячейке. На 87 строке мы открываем контроллер “ProductViewController”. Редактирование мы почти сделали, осталось добавить инициализацию редактируемых данных в контроллере “ProductViewController”. Давайте реализуем еще в этом контроллере удаление данных. Для этого надо переопределить два метода объявленных в протоколе “UITableViewDelegate” и реализованных в классе “UITableViewController”. Эти методы называются “canEditRowAt IndexPath” и “commit editingStyle”. Обновите свой код в соответствии с представленным ниже: Разберем код построчно. На 90 строке мы переопределили метод “canEditRowAt IndexPath”, данный метод возвращает значение , которое говорит о том, активировать удаление строк или нет. На 91 строке возвращаем значение активировать. На 94 строке мы переопределили метод “commit editingStyle”, данный метод вызывается, когда мы нажали кнопку удалить на ячейке. На 95 строке мы удаляем продукт из коллекции “supplies”. На 96 строке обновляем содержимое таблицы. Теперь добавим кнопку добавления нового товара. Для этого обновим код метода “initNavigationButtons”.   Разберем код построчно. На 100 строке мы создали экземпляр кнопки с текстом “Новый продукт” и обработчиком события на нажатие - метод “newProduct”. На 101 строке мы добавили выше созданную кнопку в панель навигации контроллера в область справа. На 104 строке мы объявили метод “newProduct”, которые ничего не принимает и ничего не возвращает. На 105 строке мы производим поиск контроллера “ProductViewController” в модуле “Main.storyboard”. На 106 строке мы на найденном экземпляре контроллера обращаемся к свойству “delegate” и инициализируем его ссылкой на самого себя. На 107 строке мы  на найденном экземпляре контроллера обращаемся к свойству “id” и говорим, устанавливая значение “-1”, что мы создаем новый продукт. Теперь откройте модуль “Main.storyboard”, в нем на контроллере “ProductViewController” выделите элемент управления с идентификатором “idTxt”. В панели свойств на вкладке “Show the Attributes inspector” найдите свойство “User Interaction Enabled” и снимите галочку. Тем мы самым мы запретили редактирование значения этого поля. Теперь перейдите в модуль “ProductViewController.swift”. Обновите в нем код, как это показано ниже: Разберем добавленный код построчно. На 19 строке мы добавили метод с именем “initData”, который ничего не возвращает и принимает аргумент с именем “id” типа “Int”. На 20 строке мы проверяем идентификатор продукта, если он больше значения “-1”, значит продукт уже существует и содержит внутри себя данные, которые можно отобразить на текущей View. На 22 строке мы ищем продукт по идентификатору продукта, переданному аргументом в методе в коллекции продуктов “supplies”. С 23 по 25 строку мы заполняем элементы управления на пользовательском интерфейсе данными найденного продукта. На 33 строке в методе “viewDidLoad” мы добавили вызов метода “initData” и передали ему аргумент поле “id”, которое мы задаем в контроллере “SuppliesViewController”. Запустите приложение. На анимации выше видно, что мы вошли в систему, открыли товар на редактирование, откредактировали товар и изменения сохранились. Затем удалили товар. Теперь давайте проверим добавление нового товара: Проверили, добавление нового товара работает хорошо. Но все равно есть один нюанс, наше приложение на русском языке. А когда выполняли на ячейки жест справа на- лево, то показывалась кнопка удалить “Delete” на английском, давайте исправим это, чтобы показывалось русское название “Удалить”. Откройте модуль “SuppliesViewController.swift”, нам необходимо переопределить метод, объявленный в делегате “UITableViewDelegate” c именем “titleForDeleteConfirmationButtonForRowAt” и реализованный в классе “UITableViewController”. Добавьте следующий код, после метода “backToParent”: Я думаю, этот метод не стоит разбирать построчно, тут и так все понятно. Запустите приложение. На этом завершим наш урок. В следующем уроке мы разберем технологию 3D Touch и применим ее к нашему приложению для предварительного просмотра товаров. Добавим поле поиска в заголовок контроллера со списком товаров, реализуем поиск товаров. Реализуем протокол “UITextFieldDelegate”, чтобы скрывать клавиатуру при нажати кнопки “Ввод” на клавиатуре мобильного устройства. Разберем, как добавить собственную панель интрументов с кнопками.
Notification success