Как сформировать свифт без 1с

Обновлено: 05.07.2024

Про Core Data и Swift написано не так много, как хотелось бы, особенно это касается русскоязычного сегмента Интернета. При этом большинство статей и примеров используют довольно примитивные модели данных, чтобы показать только саму суть Core Data, не вдаваясь в подробности. Данной статьей я хотел бы восполнить этот пробел, показав немного больше о Core Data на практическом примере. Изначально, я планировал уместить весь материал в одну статью, но в процессе написания стало ясно, что для одной публикации объем явно великоват, а так как из песни слов не выкинешь, то я все-таки разобью данный материал на три части.

Вместо Введения

Core Data — это мощный и гибкий фреймворк для хранения и управления графом вашей модели, который заслуженно занимает свое место в арсенале любого iOS-разработчика. Наверняка вы, как минимум, слышали об этом фреймворке, и не один раз, и если по каким-то причинам вы его еще не используете, — то самое время начать это делать.

Так как голая теория, как правило, довольно скучна и плохо усваивается, рассматривать работу c Core Data мы будем на практическом примере, создавая приложение. Такие распространенные примеры работы с Core Data, как «Список дел» и им подобные, на мой взгляд, не слишком подходят, так как используют всего одну сущность и не используют взаимосвязи, что является существенным упрощением работы с данным фреймворком. В данной статье мы разработаем приложение, где будет использоваться несколько сущностей и взаимосвязей между ними.

Предполагается, что читатель знаком с основами разработки под iOS: знает Storyboard и понимает MVC, умеет использовать базовые элементы управления. Я сам переключился на iOS недавно, поэтому, возможно, в статье есть ошибки, неточности или игнорирование best practices, просьба за это сильно не пинать, лучше аргументированно ткнуть носом, чем поможете мне и другим начинающим iOS-разработчикам. Я буду использовать Xcode 7.3.1 и iOS 9.3.2, но все должно работать и в других версиях.

Общие сведения о Core Data

Как было сказано выше, Core Data — это фреймворк для хранения и управления объектным графом вашей модели данных. Конечно, управлять и, тем более, хранить данные можно и без Core Data, но с этим фреймворком это намного приятнее и удобнее.

На мой взгляд, важно понять основные компоненты и принцип работы Core Data сразу целиком. То есть кривая обучения предполагает порог входа, немного выше среднего, если так можно выразиться. Ключевыми компонентами Core Data, которые используются всегда, являются следующие:

  • managed object model (управляемая объектная модель) — фактически это ваша модель (в парадигме MVC), которая содержит все сущности, их атрибуты и взаимосвязи;
  • managed object contexts (контекст управляемого объекта) — используется для управления коллекциями объектов модели (в общем случае, может быть несколько контекстов);
  • persistent store coordinator (координатор постоянного хранилища) — посредник между хранилищем данных и контекстом, в которых эти данные используются, отвечает за хранение данных и их кэширование

Давайте, продолжим рассмотрение Core Data на примере.
Создайте новый проект на основе шаблона Single View Application и на странице выбора опций нового проекта поставьте флажок «Use Core Data».


При установке данного флажка Xcode добавит в проект пустую модель данных и некоторое количество программного кода для работы с Core Data. Разумеется, можно начать использовать Core Data уже в существующем проекте: в этом случае надо самостоятельно создать модель данных и написать соответствующий программный код.

По умолчанию, Xcode добавляет код для работы с Core Data в класс делегата приложения ( AppDelegate.swift ). Давайте рассмотрим его более детально, он начинается с комментария:


Здесь четыре переменные, все они инициализируются с помощью замыкания. Однако, первая из них, applicationDocumentsDirectory — просто вспомогательный метод, который возвращает директорию для хранения данных. По умолчанию, это Document Directory , можно изменить, но маловероятно, что вам это действительно надо. Реализация проста и не должна вызывать затруднений для понимания.


Следующее определение — managedObjectModel — более интересно, так как имеет самое непосредственное отношение к Core Data:


Логика программного кода незамысловата — получаем из сборки приложения некий файл с расширением momd и создаем на основании его объектную модель данных. Осталось выяснить, что это за файл такой. Посмотрите на файлы в Навигаторе проекта (Project navigator), там вы найдете файл с расширением xdatamodel — это наша модель данных Core Data (как с ней работать мы рассмотрим чуть позже), которая при компиляции проекта включается в файл-сборку приложения с расширением momd .


Идем дальше, — persistentStoreCoordinator — наиболее объемное определение, но, несмотря на несколько устрашающий вид, не стоит его пугаться — большую часть кода занимает обработка исключений:


Здесь на основе объектной управляемой модели создается координатор постоянного хранилища. Затем мы определяем, где именно должны храниться данные. И в заключении подключаем собственно само хранилище ( coordinator.addPersistentStoreWithType ), передав соответствующему методу в качестве параметров тип хранилища и его расположение. По умолчанию используется SQLite. В двух других параметрах могут передаваться дополнительные параметры и опции, но на данном этапе нам это не надо, поэтому просто передадим nil .

Последнее определение — managedObjectContext — уверен, проблем с ним не будет:


Здесь мы создаем новый контекст управляемого объекта и присваиваем ему ссылку на наш координатор постоянного хранилища, с помощью которого он и будет читать и писать необходимые нам данные. Деталь, заслуживающая внимания — аргумент конструктора NSManagedObjectContext . В общем случае, может быть несколько рабочих контекстов выполняемых в разных потоках (например, один для интерактивной работы, другой — для фоновой подгрузки данных). Передавая в качестве аргумента MainQueueConcurrencyType , мы указываем, что данный контекст должен быть создан в основном потоке.

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


Здесь важно отметить: вся работа с данными (создание, модификация, удаление) всегда происходит в рамках какого-либо контекста. Фактическая запись в хранилище будет выполнена только при явном вызове функции сохранения контекста.

Создание модели данных

Для создания Модели данных используется встроенный редактор. Так как мы поставили флажок «Use Core Data» при создании нового проекта, то у нас уже есть пустая модель данных, автоматически создания Xcode. Давайте ее откроем и создадим модель данных для нашего приложения.


Мы будем создавать приложение для учета заказов от контрагентов на выполнение определенных услуг. Это приложение не будет очень сложным, но в нем будет несколько различных сущностей, тесно связанных между собой. Это позволит показать различные аспекты и приемы работы с Core Data. Итак, у нас будет два справочника: «Заказчики» и «Услуги», и один документ «Заказ», в котором может быть несколько услуг.

Термины «Справочник» и «Документ» я взял из терминологии «1С: Предприятие», потому что именно эту систему мне очень сильно напоминает Core Data. Схожая логика построения сущностей (справочников/документов), аналогичные параметры атрибутов, инкапсулирование операций чтения/записи данных, кэширование и много другое. Я бы сказал, что «1С: Предприятие» — это следующий уровень абстракции работы с данными по отношению к Core Data.

Ладно, давайте напишем свое «1С: Предприятие» с блэкджеком и с нормальным дизайном!

Создание справочников

Давайте начнем с Заказчиков. В редакторе модели данные добавьте новую сущность (кнопка с подписью «Add Entity» внизу) и назовите ее «Customer» . Эта сущность будет олицетворять Заказчика (одного). У сущности могут быть атрибуты, взаимосвязи и получаемые свойства (fetch-свойства). Немного упростив, можно сказать, что разница между атрибутами и взаимосвязями в типе возможных значений: атрибуты поддерживают только простые типы данных (строка, число, дата и пр.), взаимосвязи — это ссылка на другую сущность (более подробно про взаимосвязи мы поговорим через несколько минут). Fetch-свойства — это аналог вычисляемых свойств, то есть значение вычисляется динамически (и кэшируется) на основании предопределенного запроса.

Можно провести следующую аналогию с СУБД:

  • модель данных — схемы базы данных
  • сущность — таблица базы данных
  • атрибуты и взаимосвязи — поля таблицы


Следующая важная часть — Инспектор модели данных, вы видите его справа от редактора модели данных. С его помощью можно задавать различные атрибуты и параметры для сущностей, атрибутов сущностей (уж простите за тавтологию), взаимосвязей и других объектов. Например, сущность можно отметить как абстрактную, либо задать для нее родительскую сущность (принципы такие же, как и в целом в ООП).

Для атрибута сущности список доступных параметров меняется в зависимости от типа атрибута. Например, для числовых значений можно задать нижнюю и/или верхнюю границу, для даты можно задать допустимый диапазон. Также для большинства типов значений можно задать значение по умолчанию.

Важным свойством атрибута является Optional (опциональный). Смысл у него точно такой же, как в программном коде Swift: если атрибут помечен как Optional, то его значение может отсутствовать, и наоборот, — если такой пометки нет — запись сущности будет невозможна. По умолчанию, все атрибуты отмечаются как опциональные. В нашем случае, атрибут name не должен быть опциональным (надо снять флажок Optional), так как Заказчик без имени лишен какого-либо практического смысла.

На этом создание сущности Customer можно считать завершенным. Давайте создадим и настроим следующую сущность — Услуги. Создайте новую сущность — Services и добавьте два атрибута: name (наименование услуги) и info (дополнительная информация). Тип данных в обоих случаях — String , атрибут name — не должен быть опциональным. В общем, все то же самое, что с предыдущей сущностью, никаких проблем здесь возникнуть не должно.


Создание документа «Заказ»

Переходим к документу «Заказ» — здесь все немного сложнее. Так как в одном документе у нас может быть несколько различных услуг, а для каждой услуги будет своя сумма, то документ у нас будет представлен двумя сущностями:

  • «шапка» документа, где будет содержаться дата документа, заказчик и ссылка на табличную часть
  • строка табличной части документа, где будет содержать Услуга и ее стоимость, а также ссылка на «шапку» документа.

Начнем с «шапки» документа — создадим новую сущность «Order» и добавим три атрибута (здесь все уже знакомо по созданию предыдущих сущностей):

  • date — дата документа, тип Date , не опциональный
  • paid — признак оплаты, тип Boolean , не опциональный, значение по умолчанию — NO
  • made — признак выполнения заказа, тип Boolean , не опциональный, значение по умолчанию — NO


Обратите внимание, что взаимосвязи по умолчанию тоже являются Optional. Кроме того, в Инспекторе атрибутов присутствуют следующие очень важные свойства, которые мы сейчас подробно рассмотрим:

  • Type (тип связи)
  • Delete Rule (правило удаление)
  • Inverse (обратная связь)
Type (тип связи)

Если вы работали с какими-либо базами данных, то это понятие вам наверняка знакомо. Здесь нам предлагается на выбор два варианта: To One и To Many. To One — означает, что наш Заказ связан к одним конкретным Заказчиком, To Many — с несколькими заказчиками. В нашем случае надо оставить значение по умолчанию — To One.

Delete Rule (правило удаления)

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

  • No Action (Не выполнять никаких действий) — Core Data не будет выполнять какие-либо действия, в том числе уведомлять о таком удалении; сущность «будет думать», что удаления не было. В этом случае, вы должны самостоятельное реализовать необходимое поведение приложения. Маловероятно, что вы захотите это использовать.
  • Nullify (Аннулирование) — при удалении связи, ее значение будет установлено в nil . Наиболее распространенный вариант, используется по умолчанию.
  • Cascade (Каскадное удаление) — при удалении связи, автоматически будут удалены все заказчики, ссылающиеся на нее (явно не наш случай)
  • Deny (Отказ) — правило, противоположное предыдущему, его суть в том, что нельзя удалить объект, пока на него есть хотя бы одна ссылка. Такой подход, например, применяется в отношении всех объектов в «1С: Предприятие».
Inverse (обратная связь)

Мы добавили связь «Заказа» с «Заказчиком», но «Заказчик» ничего не знает о «Заказах», в которых он участвует. Об этом же нас предупреждает и Warning.


Давайте это исправим, создайте для сущности Customer новую взаимосвязь с именем orders , выберете Destination = Order и в качестве обратной связи укажите, созданную нами ранее связь customer . Еще один момент — так как у одного Заказчика может быть, в общем случае, много документов — изменим тип связи на To Many .


Если вы вернетесь в сущность «Order» , то увидите, что обратная связь уже установлена автоматически в значение orders .

Давайте теперь сделаем табличную часть нашего документа. Добавьте новую сущность с именем «RowOfOrder» . У нас будет один атрибут — «sum» («Сумма за услугу») с типом Float (это вы уже умеете делать, не буду расписывать подробно) и две взаимосвязи («Услуга» и «Заказ»). Давайте начнем с Заказа — добавьте новую взаимосвязь с именем order и назначением ( Destination ) равным Order . Так как строка документа может принадлежать только одному документу, то тип связи ( Type ) должен быть To One . Ну а если мы решим удалить документ, то логично, что его строки тоже должны быть удалены, потому Delete Rule у нас будет Cascade .


Теперь возвращаемся в сущность Order, чтобы создать обратную связь. Добавьте новую связь с именем rowsOfOrder (Destination = RowOfOrder, Inverse = order) . Не забудьте изменить тип связи на To Many (так как в одном документе может быть несколько строк).


Осталось в сущность RowOfOrder добавить только связь с сущностью Услуга. С учетом всего вышесказанного этого не должно быть сложным, все по тому же сценарию. Добавляем для сущности «RowOfOrder» новую взаимосвязь с именем service (Destination = Service) , остальное оставляем по умолчанию. Затем для сущности Service добавляем новую взаимосвязь «rowsOfOrders» (Destination = rowOfOrder, Inverse = service) и устанавливаем тип связи равным To Many .


Важное замечание! После создания модели данных ее нельзя менять — при первом запуске приложения Core Data в соответствии с моделью данных создает хранилище, а при последующих — проверяет структуру хранилища на соответствие. Если по каким-либо причинам структура хранилища не соответствует модели данных, то происходит критическая ошибка времени выполнения (то есть приложение у вас будет неработоспособно). Как же быть в случае, если модель данных требуется изменить — для этого необходимо использовать механизм миграции Core Data, это отдельная тема повышенной сложности, и мы не будем ее рассматривать в рамках данной статьи. Есть и другой вариант — можно просто удалить приложение с устройства (или эмулятора), а при старте приложения Core Data просто создаст новое хранилище с новой структурой. Очевидно, что данный способ уместен только на этапе разработки приложения.

В заключение данной статьи давайте взглянем на ее графическое представление, для этого переключите Editor Style редактора модели данных (находится внизу) в положение Graph.


Вы видите созданные нами сущности с атрибутами и все их взаимосвязи в виде графической структуры. Линия с обычной стрелкой на конце означает связь To One, с двойной стрелкой — To Many. Графический вид хорошо помогает сориентироваться в объемных моделях.

На этом первая часть закончена, в следующей статье будет много кода, мы будем создавать сами объекты, связывать их между собой, познакомимся с NSEntityDescription и NSManagedObject , а также напишем вспомогательный класс, существенно повышающий удобство работы с Core Data.



Вместе с релизом в open source языка Swift 3 декабря 2015 года Apple представила децентрализованный менеджер зависимостей Swift Package Manager.

К публичной версии приложили руку небезызвестные Max Howell, создатель Homebrew, и Matt Thompson, написавший AFNetworking. SwiftPM призван автоматизировать процесс установки зависимостей, а также дальнейшее тестирование и сборку проекта на языке Swift на всех доступных операционных системах, однако пока его поддерживают только macOS и Linux. Если интересно, идите под кат.

Минимальные требования – Swift 3.0. Чтобы открыть файл проекта потребуется Xcode 8.0 или выше. SwiftPM позволяет работать с проектами без xcodeproj-файла, поэтому Xcode на OS X не обязателен, а на Linux его и так нет.

Стоит развеять сомнения – проект еще в активной разработке. Использование UIKit, AppKit и других фреймворков iOS и OS X SDK как зависимостей недоступно, так как SwiftPM подключает зависимости в виде исходного кода, который потом собирает. Таким образом, использование SwiftPM на iOS, watchOS и tvOS возможно, но только с использованием Foundation и зависимостей сторонних библиотек из открытого доступа. Один единственный import UIKit делает вашу библиотеку непригодной для распространения через SwiftPM.

Все примеры в статье написаны с использованием версии 4.0.0-dev, свою версию можете проверить с помощью команды в терминале

Идеология Swift Package Manager

Для работы над проектом больше не нужен файл *.xcodproj — теперь его можно использовать как вспомогательный инструмент. Какие файлы участвуют в сборке модуля, зависит от их расположения на диске — для SwiftPM важны имена директорий и их иерархия внутри проекта. Первоначальная структура директории проекта выглядит следующим образом:

  • Sources – исходные файлы для сборки пакета, разбитые внутри по директориям продуктов – для каждого продукта отдельная папка.
  • Tests – тесты для разрабатываемого продукта, разбивка на папки аналогично папке Sources.
  • Package.swift – файл с описанием пакета.
  • README.md – файл документации к пакету.

Основные компоненты

Теперь давайте разберемся с основными компонентами в SwiftPM:

  • Модуль (Module) – набор *.swift–файлов, выполняющий определенную задачу. Один модуль может использовать функционал другого модуля, который он подключает как зависимость. Проект может быть собран на основании единственного модуля. Разделение исходного кода на модули позволяет выделить в отдельный модуль функцию, которую можно будет использовать повторно при сборке другого проекта. Например, модуль сетевых запросов или модуль работы с базой данных. Модуль использует порог инкапсуляции уровня internal и представляет собой библиотеку (library), которая может быть подключена к проекту. Модуль может быть подключен как из того же самого пакета (представлен в виде другого таргета), так и из другого пакета (представлен в виде другого продукта).
  • Продукт (Product) – результат сборки таргета (target) проекта. Это может быть библиотека (library) или исполняемый файл (executable). Продукт включает себя исходный код, который относится непосредственно к этому продукту, а также исходный код модулей, от которых он зависит.
  • Пакет (Package) – набор *.swift–файлов и manifest-файла Package.swift, который определяет имя пакета и набор исходных файлов. Пакет содержит один или несколько модулей.
  • Зависимость (Dependency) – модуль, необходимый для исходного кода в пакете. У зависимости должен быть путь (относительный локальный или удаленный на git-репозиторий), версия, перечень зависимостей. SwiftPM должен иметь доступ к исходному коду зависимости для их компиляции и подключения к основному модулю. В качестве зависимости таргета может выступать таргет из того же пакета или из пакета-зависимости.

Получаем, что зависимости выстраиваются в граф – у каждой зависимости могут быть свои собственные и так далее. Разрешение графа зависимостей – основная задача менеджера зависимостей.

Замечу, что все исходные файлы должны быть написаны на языке Swift, возможности использовать язык Objective-C – нет.

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

Далее рассмотрим простой пример с подключением к проекту зависимости Alamofire.

Разработка тестового проекта

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


Далее инициализируем пакет с помощью команды


В результате создается следующая иерархия исходных файлов


В условиях отсутствия индекса файла проекта *.xcodeproj менеджеру зависимостей нужно знать, какие исходные файлы должны участвовать в процессе сборки и в какие таргеты их включать. Поэтому SwiftPM определяет строгую иерархию папок и перечень файлов:

  • Package-файл;
  • README-файл;
  • Папка Sources с исходными файлами – отдельная папка для каждого таргета;
  • Папка Tests – отдельная папка для каждого тестового таргета.


для сборки пакета или для запуска теста Hello, world!

Добавление исходных файлов

Создадим файл Application.swift и положим его в папку IPInfoExample.

Выполняем swift build и видим, что в модуле уже компилируется 2 файла.


Создадим директорию Model в папке IPInfoExample, создадим файл IPInfo.swift, а файл IPInfoExample.swift удалим за ненадобностью.


После этого выполним команду swift build для проверки.

Добавление зависимостей

Откроем файл Package.swift, содержание полно описывает ваш пакет: имя пакета, зависимости, таргет. Добавим зависимость Alamofire.


Далее снова swift build, и наши зависимости скачиваются, создается файл Package.resolved c описанием установленной зависимости (аналогично Podfile.lock).

В случае если в вашем пакете только один продукт, можно использовать одинаковые имена для имени пакета, продукта и таргета. У нас это IPInfoExample. Таким образом, описание пакета можно сократить, опустив параметр products. Если заглянуть в описание пакета Alamofire, увидим, что там не описаны таргеты. По умолчанию создаются один таргет с именем пакета и файлами исходного кода из папки Sources и один таргет с файлом-описанием пакета (PackageDescription). Тестовый таргет при использовании SwiftPM не задействуется, поэтому папка с тестами исключается.


Чтобы удостовериться в правильности создания модулей, таргетов, продукта, можем выполнить команду


В результате для Alamofire получим следующий лог:


Если у пакета несколько продуктов, то в качестве зависимости мы указываем пакет зависимости, а уже в зависимости таргета указываем зависимость от модуля пакета. Например, так подключен SourceKitten в нашей библиотеке Synopsis.


Так выглядит описание пакета SourceKitten. В пакете описаны 2 продукта


Synopsis использует продукт-библиотеку SourceKittenFramework.

Создание файла проекта

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


и в результате получим в корневой папке проекта файл IPInfoExample.xcodeproj.
Открываем его, видим все исходники в папке Sources, в том числе с подпапкой Model, и исходники зависимостей в папке Dependencies.

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


Проверка подключенной зависимости

Проверим, корректно ли подключилась зависимость. В примере делаем асинхронный запрос к сервису ipinfo для получения данных о текущем ip-адресе. JSON ответа декодируем в модельный объект – структуру IPInfo. Для простоты примера не будем обрабатывать ошибку маппинга JSON или ошибку сервера.


Далее можем воспользоваться командой build в Xcode, а можем выполнить команду swift build в терминале.

Проект с исполняемым файлом

Выше описан пример для инициализации проекта библиотеки. SwiftPM позволяет работать с проектом исполняемого файла. Для этого при инициализации используем команду


Привести текущий проект к такому виду также можно, создав файл main.swift в директории Sources/IPInfoExample. При запуске исполняемого файла main.swift является точкой входа.
Напишем в него одну строчку


А затем выполним команду swift run, в консоль выведется заветное предложение.

Синтаксис описания пакета

Описание пакета в общем виде выглядит следующим образом:

  • name – имя пакета. Единственный обязательный аргумент для пакета.
  • pkgConfig – используется для пакетов модулей, установленных в системе (System Module Packages), определяет имя pkg-config-файла.
  • providers – используется для пакетов системных модулей, описывает подсказки для установки недостающих зависимостей через сторонние менеджеры зависимостей – brew, apt и т.д.
  • products – описание результата сборки таргета проекта – исполняемый файл или библиотека (статическая или динамическая).

Выше в пакете описано 4 продукта: исполняемый файл из таргета tool, библиотека Paper (SwiftPM выберет тип автоматически), статическая библиотека PaperStatic, динамическая PaperDynamic из одного таргета Paper.

    Dependencies – описание зависимостей. Необходимо указать путь (локальный или удаленный) и версию.

Читайте также: