Core Data — спасение или головная боль iOS-разработчика?
Core Data – тот самый фреймворк от Apple, который вызывает у iOS-разработчиков противоречивые чувства: от «божественно элегантно» до «что за дьявольщина тут творится». И знаете что? Оба лагеря по-своему правы.

Представьте себе швейцарский армейский нож для работы с данными в iOS и macOS приложениях – это и есть Core Data. Только вместо штопора и ножниц у нас тут инструменты для хранения объектов, их кэширования, и даже (о, чудо!) встроенная поддержка отмены действий. А ещё он умеет сохранять данные для офлайн-использования – потому что интернет может пропасть в самый неподходящий момент, верно?
Но давайте начистоту: Core Data – это не база данных, хотя многие почему-то думают именно так. Это, скорее, целый фреймворк для управления объектным графом вашего приложения (звучит умно, да?). Под капотом он, конечно, использует SQLite для хранения данных, но это примерно как говорить, что автомобиль – это двигатель. Технически верно, но немного мимо сути.
И знаете что самое интересное? При правильном использовании Core Data может значительно упростить вашу жизнь как разработчика. Хотя, признаюсь, кривая обучения тут напоминает американские горки – сначала страшно, потом весело, а в конце уже даже руки поднимать не хочется.
- Основные компоненты Core Data
- Управляемая объектная модель (Managed Object Model)
- Контекст управляемых объектов (Managed Object Context)
- Координатор постоянного хранилища (Persistent Store Coordinator)
- Создание и настройка модели данных
- Определение сущностей и атрибутов
- Установление связей между сущностями
- Основные операции с данными в Core Data
- Добавление новых объектов
- Извлечение данных (Fetch Requests)
- Обновление существующих объектов
- Удаление объектов
- Работа с отношениями между сущностями
- Типы отношений
- One-to-One
- One-to-Many
- Many-to-Many
- Навигация по связям
- Обработка ошибок и отладка
- Обработка ошибок при сохранении контекста
- Использование инструмента Core Data Debugging
- Оптимизация производительности
- Использование ленивой загрузки (Lazy Loading)
- Минимизация количества запросов
- Миграция данных
- Легкая миграция (Lightweight Migration)
- Ручная миграция
- Примеры использования Core Data в реальных приложениях
- Приложения для заметок
- Менеджеры задач
- Заключение
- Рекомендуем посмотреть курсы по обучению iOS разработчиков
Основные компоненты Core Data
Давайте разберём этот «швейцарский нож» на составляющие. Только, в отличие от настоящего ножа, собирать обратно придётся самостоятельно (шутка, хотя иногда и правда так кажется).
Управляемая объектная модель (Managed Object Model)
Представьте, что вы архитектор, который проектирует здание. Только вместо этажей и комнат у вас сущности и атрибуты. Managed Object Model – это ваш чертёж, blueprint всего приложения. Здесь вы определяете, что у пользователя есть имя (надеюсь!), email (а как же без него) и, возможно, список любимых котиков (потому что какое приложение обходится без котиков?).
Контекст управляемых объектов (Managed Object Context)
А вот это – уже ваша рабочая песочница. Только вместо совочка и формочек тут операции CRUD (Create, Read, Update, Delete – для тех, кто всё ещё притворяется, что помнит эту аббревиатуру). Контекст отслеживает все изменения ваших объектов, как сталкер из той самой игры – ничего не упустит. Хотите создать нового пользователя? Через контекст. Удалить старого? Тоже через него. Изменить что-то? Ну, вы поняли.
Координатор постоянного хранилища (Persistent Store Coordinator)
Это тот самый парень, который отвечает за то, чтобы ваши данные не испарились после перезапуска приложения. Представьте его как очень ответственного библиотекаря, который точно знает, где лежит каждая книга (читай – объект), и может быстро её достать. По умолчанию он использует SQLite, хотя может работать и с XML, и с binary форматом (но давайте будем честны – кто-то реально использует что-то кроме SQLite?).
И вот что забавно – все эти компоненты должны работать вместе, как слаженный оркестр. Только дирижёр тут вы, и если что-то пойдёт не так… Ну, скажем так, приложение вряд ли сыграет симфонию. Скорее что-то из жанра экспериментального джаза.
А знаете, что самое интересное? Apple очень постаралась сделать всё это максимально простым для использования. Настолько, что иногда даже не понимаешь – то ли это действительно так просто, то ли ты что-то упускаешь. Спойлер: обычно второе.
Создание и настройка модели данных
Так, настало время поговорить о том, как же собственно создать эту самую модель данных в Xcode. И нет, это не так просто, как «нарисовать сову» – хотя местами похоже.
Определение сущностей и атрибутов
Помните, как в детстве мы играли в конструктор? Так вот, создание модели данных – примерно то же самое, только вместо кубиков у нас сущности, а вместо цветов – атрибуты. И да, собрать что-то осмысленное тут тоже не всегда получается с первого раза.
Начнём с того, что в Xcode есть специальный редактор моделей данных (спасибо, Apple, что хоть это не забыли). Выглядит он как смесь таблицы Excel с графическим редактором – немного пугающе на первый взгляд, но жить можно.
В этом редакторе мы создаём наши сущности (Entity) – что-то вроде чертежей для наших будущих объектов. Каждой сущности можно (и нужно!) добавить атрибуты. И тут начинается самое веселое – выбор типов данных. String, Int, Date, Bool – всё как в Swift, только с небольшим «но»: все они автоматически становятся опциональными (optional). Видимо, Core Data исповедует философию «лучше null, чем exception».
Установление связей между сущностями
А теперь самое интересное – связи между сущностями. Это как социальная сеть, только для ваших данных. У нас есть несколько типов отношений:
- one-to-one (как моногамные отношения)
- one-to-many (как подписчики в Instagram)
- many-to-many (как друзья в Facebook)
И знаете что? Каждую такую связь нужно настраивать с обеих сторон (да-да, как в здоровых отношениях). Причём если вы забудете об этом – Xcode будет настойчиво напоминать вам warning’ами. Очень настойчиво. До тех пор, пока вы не сдадитесь и не сделаете всё правильно.
Отдельного упоминания заслуживает Delete Rule – правило, определяющее, что делать со связанными объектами при удалении основного. Варианты тут как в детективе:
- Nullify (сделать вид, что ничего не было)
- Cascade (удалить всё, что связано)
- Deny (запретить удаление, если есть связи)
- No Action (пусть будет что будет)
Выбор правильного правила удаления – это примерно как выбор тактики в шахматах: один неверный ход, и вся партия может пойти наперекосяк.
И помните – после того как вы создали и настроили модель, изменить её будет… скажем так, непросто. Core Data не очень любит спонтанность в отношениях. Так что планируйте структуру данных заранее, иначе потом придётся заниматься миграцией данных (о чём мы поговорим позже, и поверьте, это отдельная история).
Основные операции с данными в Core Data
Итак, модель данных у нас есть, теперь давайте разберемся, как же с этим добром работать. Предупреждаю сразу – будет много кода, но я постараюсь разбавить его понятными аналогиями (и щепоткой здорового цинизма).
Добавление новых объектов
Создание нового объекта в Core Data напоминает заполнение анкеты в государственном учреждении – нужно строго следовать протоколу:
let newUser = User(context: managedObjectContext)
newUser.username = "definitely_not_a_bot"
newUser.lastLogin = Date()
newUser.verified = false // будем честными
do {
try managedObjectContext.save()
} catch {
print("Что-то пошло не так, но мы же этого ожидали, правда?")
}
Обратите внимание на try-catch блок – Core Data очень любит намекать нам, что всё может пойти не так. Причём намекает он исключениями, а не мягкими предупреждениями.
Извлечение данных (Fetch Requests)
А вот это уже похоже на квест в RPG – нужно составить правильный запрос, чтобы получить нужные данные:
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "username CONTAINS[cd] %@", "bot")
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \User.lastLogin, ascending: false)]
do {
let suspiciousUsers = try managedObjectContext.fetch(fetchRequest)
print("Нашли \(suspiciousUsers.count) подозрительных пользователей")
} catch {
print("База данных решила, что сегодня не наш день")
}
NSPredicate – это отдельный вид искусства. Синтаксис там настолько интуитивный, что иногда хочется просто написать «дай мне всех пользователей, пожалуйста» (спойлер: не сработает).
Обновление существующих объектов
Обновление объектов выглядит обманчиво просто:
if let user = try? managedObjectContext.fetch(fetchRequest).first {
user.verified = true // повысим доверие к пользователю
do {
try managedObjectContext.save()
} catch {
print("А вот и подвох!")
}
}
Главное помнить – пока вы не вызовете save(), все изменения существуют только в памяти. Как черновик сообщения в мессенджере – пока не нажмёте «отправить», никто ничего не увидит.
Удаление объектов
Удаление объектов в Core Data напоминает удаление фотографий из соцсетей – вроде удалил, но где-то они всё равно остаются:
if let userToDelete = try? managedObjectContext.fetch(fetchRequest).first {
managedObjectContext.delete(userToDelete)
do {
try managedObjectContext.save()
print("Пользователь успешно удалён. Наверное.")
} catch {
print("Пользователь решил остаться с нами")
}
}
И помните – если у объекта есть связи с другими объектами, то в зависимости от настроенных Delete Rules может случиться всё что угодно: от тихого обнуления ссылок до каскадного удаления половины базы данных (да, такое тоже бывает, и обычно в пятницу вечером).
Вот такие они, базовые операции с Core Data. Вроде всё просто, но дьявол, как обычно, кроется в деталях. И в документации. И в неочевидных edge cases. И… ну, вы поняли.
Работа с отношениями между сущностями
Такая сложная тема в жизни, и не менее сложная в Core Data. Давайте разберёмся, как управлять этими цифровыми связями (и почему иногда хочется просто хранить всё в массиве).
Типы отношений
В Core Data, как и в жизни, отношения бывают разные. Давайте разберём их на примере социальной сети (потому что кто не делал свою социальную сеть, тот не iOS-разработчик):
One-to-One
// Пользователь и его профиль
class User: NSManagedObject {
@NSManaged var profile: Profile?
}
class Profile: NSManagedObject {
@NSManaged var user: User?
}
Как брак в идеальном мире – один пользователь, один профиль. Просто и понятно (пока не начнёшь реализовывать).
One-to-Many
// Пользователь и его посты
class User: NSManagedObject {
@NSManaged var posts: NSSet?
}
class Post: NSManagedObject {
@NSManaged var author: User?
}
Как родитель и дети – у одного пользователя может быть много постов, но каждый пост принадлежит только одному автору (ну, в нашей идеальной модели).
Many-to-Many
// Пользователи и группы
class User: NSManagedObject {
@NSManaged var groups: NSSet?
}
class Group: NSManagedObject {
@NSManaged var members: NSSet?
}
Как в группе по интересам – один пользователь может быть в нескольких группах, и в одной группе может быть много пользователей. Звучит просто, но погодите, пока не начнёте это обрабатывать.
Навигация по связям
А теперь самое интересное – как же всем этим пользоваться:
// Получаем все посты пользователя
if let userPosts = user.posts?.allObjects as? [Post] {
// Теперь у нас есть массив постов
// Только не забывайте про опционалы!
}
// Добавляем пользователя в группу
if let user = currentUser, let group = selectedGroup {
group.addToMembers(user)
// Не забываем сохранить контекст!
try? managedObjectContext.save()
}
И знаете что самое «весёлое»? Core Data использует NSSet для хранения связей, а не Array. Потому что порядок – это слишком просто, давайте усложним! Хотите сортировку? Придётся конвертировать в массив и сортировать самостоятельно.
// Сортировка постов по дате
let sortedPosts = (user.posts?.allObjects as? [Post])?.sorted {
$0.createdAt > $1.createdAt
} ?? []
А ещё есть такая прекрасная вещь, как fault (не путать с fault tolerance). Это когда объект вроде бы есть, но его данные ещё не загружены. Попытка обращения к его свойствам приведёт к запросу в базу данных. Неожиданно? Добро пожаловать в мир Core Data!
// Это может привести к неожиданному запросу в базу
if let firstPost = user.posts?.allObjects.first as? Post {
print(firstPost.title) // Здесь происходит fault firing
}
И помните – каждый раз, когда вы устанавливаете связь между объектами, нужно думать о том, что произойдёт при удалении одного из них. Delete Rules – это не просто галочка в редакторе, это ваше спасение от случайного удаления половины базы данных (да, говорю по опыту).
Обработка ошибок и отладка
Добро пожаловать в увлекательный мир отлова багов в Core Data! Или, как я это называю, «почему мои данные решили уйти в закат без предупреждения».
Обработка ошибок при сохранении контекста
Работа с ошибками в Core Data – это как игра в «горячо-холодно», только вместо подсказок у нас загадочные сообщения об ошибках:
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Думали всё так просто? А вот и нет!")
print("Код ошибки: \(error.code)") // Число, значение которого вам ни о чём не скажет
print("Описание: \(error.localizedDescription)") // Обычно тоже не особо помогает
// А вот тут начинается самое интересное
if let errors = error.userInfo[NSDetailedErrorsKey] as? [NSError] {
for detailedError in errors {
print("Детали ошибки: \(detailedError)")
// Спойлер: детали часто ещё более загадочные, чем основная ошибка
}
}
}
Использование инструмента Core Data Debugging
А теперь про встроенные инструменты отладки в Xcode. Apple, конечно, постаралась, но иногда их использование напоминает гадание на кофейной гуще:
- Debug Navigator (CMD + 7) – показывает все запросы к базе данных. Очень полезно, когда вы пытаетесь понять, почему ваше приложение тормозит как Windows 95 на старте.
- View Debug Hierarchy – можно посмотреть все объекты в памяти. Правда, иногда это выглядит как карта метро в час пик.
- Аргументы запуска (Launch Arguments):
-com.apple.CoreData.SQLDebug 1 // для базового логирования SQL
-com.apple.CoreData.SQLDebug 3 // для тех, кто хочет увидеть ВСЁ
Только предупреждаю – включение полного SQL-логирования может превратить вашу консоль в бесконечный поток сообщений. Готовьтесь к тому, что найти там что-то полезное будет как искать иголку в стоге сена.
А ещё есть чудесная штука – breakpoints на fetch-запросы. Поставьте такой брейкпоинт, и вы узнаете много нового о том, как часто ваше приложение ходит в базу данных. Спойлер: обычно чаще, чем вы думаете.
И напоследок мой любимый совет по отладке Core Data: если что-то работает не так, как ожидалось, попробуйте удалить приложение и установить заново. Да, это не самое элегантное решение, но иногда это единственный способ понять, что происходит с вашей базой данных. Только не говорите об этом заказчику!
Оптимизация производительности
Давайте поговорим о том, как заставить Core Data работать быстрее, чем ваша бабушка листает ленту Instagram (а она, между прочим, весьма шустрая).
Использование ленивой загрузки (Lazy Loading)
Ленивая загрузка в Core Data – это как просмотр сериала на Netflix: зачем загружать все серии сразу, если можно посмотреть только первую серию, а остальные подгрузить потом?
class User: NSManagedObject {
// Это загрузится сразу
@NSManaged var username: String
// А это только когда реально понадобится
@NSManaged private var _avatarData: Data?
var avatar: UIImage? {
get {
if let data = _avatarData {
return UIImage(data: data)
}
return nil
}
set {
_avatarData = newValue?.pngData()
}
}
}
Минимизация количества запросов
А вот здесь начинается настоящая магия оптимизации. Представьте, что каждый запрос к базе – это поход в магазин. Лучше купить всё сразу по списку, чем бегать десять раз за каждым продуктом:
// Плохо (как ходить в магазин за каждой морковкой):
users.forEach { user in
let posts = try? context.fetch(Post.fetchRequest(for: user))
// Каждая итерация = новый запрос 😱
}
// Хорошо (как закупиться на неделю вперёд):
let fetchRequest: NSFetchRequest<Post> = Post.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "author IN %@", users)
fetchRequest.relationshipKeyPathsForPrefetching = ["comments"] // Предзагрузка связанных объектов
let allPosts = try? context.fetch(fetchRequest)
Другие советы по оптимизации:
- Используйте батчевые запросы для массовых операций:
let batchRequest = NSBatchDeleteRequest(fetchRequest: User.fetchRequest())
try? context.execute(batchRequest) // Удаляет всё одним запросом
- Правильно настраивайте fetchBatchSize:
let request = NSFetchRequest<User>()
request.fetchBatchSize = 20 // Загружаем по 20 объектов за раз
- Используйте NSFetchedResultsController для таблиц (он как личный ассистент для ваших данных):
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: "UserCache"
)
И помните главное правило оптимизации Core Data: «Преждевременная оптимизация – корень всех зол» (но когда приложение начнёт тормозить, вы будете рады, что знаете эти трюки).
П.С. Если ваше приложение всё равно тормозит – возможно, стоит подумать о переходе на Realm. Но тсс, я этого не говорил! 😉
Миграция данных
Миграция данных – та самая тема, от которой у iOS-разработчиков начинают дергаться глаза. Представьте, что вы переезжаете в новую квартиру, только вместо мебели у вас данные, а грузчики – это Core Data.
Легкая миграция (Lightweight Migration)
Это как переезд с помощью друзей – вроде бы просто, но лучше ничего не разбить:
let container = NSPersistentContainer(name: "MyApp")
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
container.persistentStoreDescriptions.first?.setOption(true, forKey: NSMigratePersistentStoresAutomaticallyOption)
container.persistentStoreDescriptions.first?.setOption(true, forKey: NSInferMappingModelAutomaticallyOption)
container.loadPersistentStores { description, error in
if let error = error {
print("Упс! Кажется, что-то пошло не так: \(error)")
// В этот момент обычно начинается паника
}
}
Легкая миграция работает для простых изменений:
- Добавление нового атрибута
- Удаление существующего атрибута
- Изменение опциональности атрибута (только в сторону Optional!)
- Переименование атрибутов (если очень повезёт)
Ручная миграция
А вот это уже как переезд с роялем через узкую лестницу – сложно, нервно и лучше иметь план действий:
class MigrationManager {
func migrateStore(at storeURL: URL) {
guard let sourceModel = NSManagedObjectModel.modelVersions().first else {
print("Где модель? Кто спрятал модель?!")
return
}
let migrationSteps = [
"V1toV2",
"V2toV3",
// Добавляйте шаги по мере роста вашего приложения
// и его седых разработчиков
]
for step in migrationSteps {
do {
try performMigration(forStep: step)
} catch {
print("Миграция сломалась на шаге \(step)")
print("Ошибка: \(error)")
// Здесь обычно начинается составление резюме
}
}
}
}
Ручная миграция нужна, когда вы делаете сложные изменения:
- Объединение сущностей
- Разделение сущности на несколько
- Изменение типов данных
- Добавление сложной логики преобразования данных
И помните главное правило миграции данных: всегда делайте бэкап перед миграцией. И после. И во время. Вообще, делайте бэкапы постоянно – это как страховка, только работает.
А ещё совет от того, кто обжёгся: тестируйте миграцию на реальных данных. Потому что тестовые данные никогда не содержат тех странных кейсов, которые пользователи умудрились насоздавать за годы использования приложения.
Примеры использования Core Data в реальных приложениях
Давайте рассмотрим несколько реальных сценариев использования Core Data. И нет, мы не будем делать очередной TODO-list (хотя, кого я обманываю – конечно будем, куда же без него).
Приложения для заметок
Вот классический пример – приложение для заметок. Казалось бы, что тут сложного? Но давайте сделаем его чуть интереснее:
class Note: NSManagedObject {
@NSManaged var title: String
@NSManaged var content: String
@NSManaged var createdAt: Date
@NSManaged var lastModified: Date
@NSManaged var tags: NSSet?
@NSManaged var attachments: NSSet?
@NSManaged var location: Location?
// Вычисляемое свойство для красивого отображения даты
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: lastModified)
}
}
class Location: NSManagedObject {
@NSManaged var latitude: Double
@NSManaged var longitude: Double
@NSManaged var note: Note?
}
Менеджеры задач
А теперь давайте сделаем тот самый TODO-list, но с претензией на звание «корпоративное решение для управления задачами»:
class Task: NSManagedObject {
@NSManaged var title: String
@NSManaged var details: String?
@NSManaged var dueDate: Date?
@NSManaged var priority: Int16
@NSManaged var isCompleted: Bool
@NSManaged var subtasks: NSSet?
@NSManaged var assignedTo: User?
@NSManaged var project: Project?
// Умный enum для приоритетов
enum Priority: Int16 {
case low = 0
case medium = 1
case high = 2
case panic = 3 // Когда дедлайн был вчера
var description: String {
switch self {
case .low: return "Можно сделать на следующей неделе"
case .medium: return "Надо бы сделать"
case .high: return "Горит"
case .panic: return "🔥🔥🔥"
}
}
}
}
И вот несколько реальных примеров использования:
- Офлайн кэширование данных с сервера:
func syncWithServer() {
apiClient.fetchTasks { serverTasks in
let context = persistentContainer.viewContext
serverTasks.forEach { serverTask in
// Ищем существующую задачу или создаём новую
let task = Task.findOrCreate(id: serverTask.id, in: context)
task.update(from: serverTask)
}
try? context.save()
// И молимся, чтобы не было конфликтов
}
}
- Поиск по заметкам с сортировкой:
let request: NSFetchRequest<Note> = Note.fetchRequest()
request.predicate = NSPredicate(
format: "title CONTAINS[cd] %@ OR content CONTAINS[cd] %@",
searchText, searchText
)
request.sortDescriptors = [
NSSortDescriptor(keyPath: \Note.lastModified, ascending: false)
]
- Сложные выборки для аналитики:
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isCompleted = YES AND completedDate >= %@", lastMonth as NSDate)
request.propertiesToGroupBy = ["assignedTo"]
request.propertiesToFetch = ["assignedTo", "@count"]
request.resultType = .dictionaryResultType
// Получаем статистику по выполненным задачам
// (если повезёт и запрос не упадёт)

Круговая диаграмма, показывающая распределение задач по приоритетам (низкий, средний, высокий, критический).
Главное помните – какое бы приложение вы ни делали, рано или поздно оно обрастёт таким количеством функций, что простая модель данных превратится в запутанный лабиринт связей. И это нормально! Главное – не забывать документировать эти связи (и делать бэкапы, много бэкапов).
Заключение
Ну что же, мы с вами прошли через все круги Core Data – от простого создания модели до сложных миграций и оптимизаций. Как говорится, «вы входите в Core Data одним человеком, а выходите совсем другим» (обычно с нервным тиком при слове «миграция»).
Core Data – это мощный инструмент, который, как швейцарский нож, может решить множество задач. Да, иногда он кажется излишне сложным (особенно когда вы просто хотите сохранить пару строк текста), но когда дело доходит до серьёзных приложений с множеством связанных данных – тут ему нет равных в экосистеме iOS.
Несколько финальных советов от бывалого:
- Начинайте с простого. Правда, очень скоро всё станет сложным, но хотя бы старт будет легким.
- Документируйте свою модель данных. Через полгода вы забудете, почему решили сделать именно так, а не иначе.
- Делайте бэкапы. Много бэкапов. Ещё больше бэкапов.
- Тестируйте миграции на реальных данных. Пользователи умеют создавать такие комбинации данных, которые вам и не снились.
- Не бойтесь использовать альтернативы (Realm, SQLite) там, где это действительно нужно.
И помните – в мире iOS-разработки нет идеальных решений, есть только компромиссы. Core Data – это компромисс между гибкостью и сложностью, между производительностью и удобством использования.
Если вы хотите углубить свои знания в iOS-разработке и освоить Core Data на профессиональном уровне, рекомендую обратить внимание на специализированные курсы. На KursHub собрана актуальная подборка лучших курсов по iOS-разработке, где опытные преподаватели детально разбирают работу с Core Data, архитектурные паттерны и другие важные аспекты создания надежных и производительных приложений. Инвестиция в качественное обучение сэкономит вам месяцы самостоятельных проб и ошибок.
Рекомендуем посмотреть курсы по обучению iOS разработчиков
Курс | Школа | Цена | Рассрочка | Длительность | Дата начала | Ссылка на курс |
---|---|---|---|---|---|---|
iOS-разработчик
|
Eduson Academy
59 отзывов
|
Цена
Ещё -14% по промокоду
140 000 ₽
400 000 ₽
|
От
5 833 ₽/мес
0% на 24 месяца
16 666 ₽/мес
|
Длительность
7 месяцев
|
Старт
скоро
Пн,Ср, 19:00-22:00
|
Ссылка на курс |
iOS-разработчик с нуля
|
Нетология
42 отзыва
|
Цена
с промокодом kursy-online
125 001 ₽
208 334 ₽
|
От
3 472 ₽/мес
Это кредит в банке без %. Но в некоторых курсах стоимость считается от полной цены курса, без скидки. Соответственно возможно все равно будет переплата. Уточняйте этот момент у менеджеров школы.
6 111 ₽/мес
|
Длительность
13 месяцев
|
Старт
19 мая
|
Ссылка на курс |
iOS-разработчик
|
Яндекс Практикум
87 отзывов
|
Цена
202 000 ₽
|
От
15 500 ₽/мес
На 2 года.
|
Длительность
10 месяцев
Можно взять академический отпуск
|
Старт
3 июня
|
Ссылка на курс |
iOS-разработчик
|
GeekBrains
68 отзывов
|
Цена
с промокодом kursy-online15
132 498 ₽
264 996 ₽
|
От
4 275 ₽/мес
|
Длительность
1 месяц
|
Старт
23 мая
|
Ссылка на курс |
Профессия Мобильный разработчик
|
Skillbox
128 отзывов
|
Цена
Ещё -33% по промокоду
175 304 ₽
292 196 ₽
|
От
5 156 ₽/мес
Без переплат на 31 месяц с отсрочкой платежа 6 месяцев.
8 594 ₽/мес
|
Длительность
8 месяцев
|
Старт
15 мая
|
Ссылка на курс |
А теперь идите и создавайте что-нибудь крутое! Только не забудьте сделать бэкап перед началом работы. И в процессе. И после. В общем, вы поняли.

Миф или мастерство: чем покоряет Rhino 3D?
Программа Rhino 3D давно стала легендой в мире 3D-дизайна — но действительно ли она так хороша? В статье разберём её возможности, фишки и нюансы применения в разных отраслях.

Почему одни и те же ошибки повторяются в проектах снова и снова?
Lessons learned в проектном управлении — не модная методика, а практический инструмент, способный преобразить вашу команду и проекты. Разбираемся, как превратить фейлы в источник роста и сэкономить ресурсы на будущем.

OSINT — как защитить данные от разведки по открытым источникам?
Вы удивитесь, сколько данных о вас и вашей компании можно найти в открытом доступе. Разбираем, как OSINT используется для атак и как минимизировать риски.

Взаимодействие верстальщика и дизайнера: советы для продуктивной работы
Что нужно для успешной работы верстальщиков и дизайнеров? Разбираем инструменты, роли и лучшие методы коммуникации.