Сражаться из страха или злости — значит вести войну без конца.
Мы почти не работали ранее с файлами - в случае с iOS это почти и не требуется, система сама способна выдать Вам документы из пакета, в котором находится приложение. Наш самый главный совет в данном уроке будет таким - если Вы можете обойтись без манипуляций файлами, то смело поступайте именно так.
Даже если Вашему приложению требуется работать с данными, то чаще всего такие манипуляции не будут сильно сложными.
Помните, что стандартные механизмы доступа к файлам предполагают чаще всего запись и считывание целых объектов - операции по нахождению конкретной точки, особенно для нетекстового файла, и вставки туда чего-либо могут быть неэффективны. В будущем мы рассмотрим механизмы, которые помогут нам решить эту проблему иначе.
Для работы с файлами используется тип из Foundation - FileManager
(корреспондирующий ссылочный тип NSFileManager
). В большинстве случаев Вам не придётся создавать свой собственный объект - будет достаточно воспользоваться стандартным статическим FileManager.default
. Плюсом данного подхода будет то, что такой объект заведомо потокобезопасен и Вы можете отправлять ему сообщения из множества потоков одновременно!
Ограничения на взаимодействие с файлами в разных операционных системах различается. Данный урок лучше всего выполнять в консольном приложении для macOS или использовать в каком-нибудь методе
viewDidLoad
для iOS. Мы будем демонстрировать работу программы на macOS и iOS.
Чтобы иметь возможность демонстрировать работу сразу на двух устройствах, мы создаем workspace
- объединение нескольких проектов и сущностей в одном.
Используйте следующий путь для создания рабочего пространства File -> New -> Workspace
В нем создайте два проекта - для iOS и macOS, как Вы это делали ранее, однако на этапе выбора директории выберите Add To
и укажите название текущего воркспейса.
Теперь создайте в папке c macOS Swift-файл для нашего класса-обёртки. Мы назвали его FileWrapping
.
import Foundation
public class FileWrapping {
static func doWork() {
}
}
Теперь добавьте в main.swift
вызов данной функции.
Нажмите правой кнопкой по проекту для iOS и выберите Add File to...
Затем пролистайте папки вверх, пока не найдёте файл-обёртку, добавьте его в данный проект. Теперь на него есть ссылка в обоих проектах.
Не используйте данный метод для создания кросс-кода - для этой цели существуют фреймворки, о которых мы поговорим позднее.
Добавьте вызов doWork
в метод viewDidLoad
нашего единственного контроллера.
Весь дальнейший код будет писаться нами в теле
doWork
Давайте создадим себе небольшую обёртку, дабы сократить количество кода:
let manager = FileManager.default
Давайте попробуем вывести временную директорию для файлов:
print(manager.temporaryDirectory)
print(manager.temporaryDirectory.path)
В первом случае мы получаем ссылку URL
на директорию, во втором извлекаем путь до неё. Отличие ссылки от пути в том, что ссылка содержит также протокол доступа. В случае для файлов - это file://
.
URL - крайне универсальный способ работы с данными - он может ссылаться на самые разные ресурсы, не только файлы и сайты. В дальнейшем мы узнаем ещё несколько способов применить этот тип.
Также можно получить путь до текущей рабочей директории:
print(manager.currentDirectoryPath)
// /Users/gorloff/Library/Developer/Xcode/DerivedData/final@10@1_1-bdkbrmfqfxwmzfgmrfzjklaurcqo/Build/Products/Debug
Рабочую директорию для приложения-непесочницы можно изменить методом changeCurrentDirectoryPath(_:)
:
//manager.changeCurrentDirectoryPath("/")
Однако для приложений песочниц это не даст ничего хорошего...
Запустите программу на macOS, симуляторе и реальном устройстве, Вы получите три вывода соответственно:
//macOS
//file:///var/folders/6c/b82vsq25317f_9w0g07jf0qr0000gn/T/
//iOS simulator
//file:///Users/gorloff/Library/Developer/CoreSimulator/Devices/C4B5A863-9E2D-4DD3-883F-EF5823479DEC/data/Containers/Data/Application/37BF337D-2EAA-4405-A621-177D0A0B2030/tmp/
//iOS real device
//file:///private/var/mobile/Containers/Data/Application/F45C522C-22A9-478A-B2ED-39CBE498F307/tmp/
Папка для временных файлов управляется системой:
Далее мы можем комментировать все сообщения вывода, чтобы не захламлять консоль.
Для переключения между целями используется то же меню, что и для выбора целевого устройства. Учтите, что если Вы запустите приложение, то теперь команды управления его активностью относятся именно к нему, пока Вы не прекратите выполнение.
Помните, что директория для временных файлов является директорией именно для
временных
файлов. Не используйте её для хранения долговременных файлов - система не гарантирует и даже не обещает, что файлы в ней будут живы в течение времени, после высвобождения управления ими.
Попробуем получить домашнюю директорию пользователя:
print(manager.homeDirectoryForCurrentUser.path) // "/Users/gorloff"
Это сработает только на macOS, выдав ошибку компиляции на всём остальном. Это связано с тем, что данная часть API Foundation доступна только на macOS. Как же быть, если мы хотим заставить программу работать в случае отсутствия какого-то API на целевой платформе? Мы воспользуемся условными инструкциями компилятора:
#if !targetEnvironment(simulator)
print(manager.homeDirectoryForCurrentUser.path)
#endif
В отличие от препроцессора в других языках, данная система не обрабатывает исходный код - они работают на этапе компиляции и в рантайме. Указанная директива заставит данный код выполниться для устройств, не являющихся симулятором. Она полезна для создания заглущек для функций, доступных на реальном устройстве, но отсутствующих в симуляторе (камера, реальная геолокация и так далее).
Обратите внимание, что при смене конфигурации для выполнения в верхнем коде меняется и обработка кода. Все конструкции внутри директив компилятора проверяются, даже если никогда не будут выполнены.
Боле правильным будет заменить условие на проверку текущей системы на macOS
:
#if os(macOS)
print(manager.homeDirectoryForCurrentUser.path)
#endif
Есть также метод, позволяющий получить домашнюю директорию пользователя по его имени:
#if os(macOS)
print(manager.homeDirectory(forUser: "gorloff")?.path) // Optional("/Users/gorloff")
print(manager.homeDirectory(forUser: "Вячеслав Горлов")?.path) // Optional("/Users/gorloff")
print(manager.homeDirectory(forUser: "root")?.path) // Optional("/var/root")
print(manager.homeDirectory(forUser: "mistress")?.path) // nil
#endif
Данный метод возвращает опциональную ссылку, которая может быть нулевой, если пользователя с именем не существует или же его домашняя директория недоступна.
Имя пользователя это не
Вячеслав Горлов
, хотя macOS и позволяет использовать их взаимозаменяемо. Однако старайтесь использовать истинное латинское имя. Вы можете получить его, запустив терминал
Вы можете получить важные директории для работы использовав метод менеджера url(for:in:appropriateFor:create:)
:
do {
let documentsURL = try manager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
print(documentsURL)
} catch {
print(error)
}
FileManager.SearchPathDirectory
- перечисление, содержашее привязки к типовым директориям. Мы получаем здесь директорию для документов. На macOS - это общая папка для пользователя, на iOS - используется каждым приложением отдельно. Её цель - хранение долговременных данных. Объектов данного типа крайне много, чтобы на данном этапе мы не останавливались на них, Вы можете прочесть о них подробнее на сайте документации AppleFileManager.SearchPathDomainMask
- маска домена, в которой идёт поиск директории. Удовлетворяет OptionSet
, а значит может быть использованы по-одному или в массиве. Выделяют:
userDomainMask
- отсчёт идёт от домашней директории пользователя ~
localDomainMask
- место, откуда объекты доступны всем пользователям системыnetworkDomainMask
- место, откуда ресурсы доступны по сети /Network
systemDomainMask
- предоставляется Apple и не может быть изменена /System
allDomainsMask
- все домены вместе.itemReplacementDirectory
, то есть запрос на создание временной директории.Мы обернули вызов в do-catch
, так как операция потенциально может выбросить исключение. Заметьте, что если Вы попробуете получить с помощью данного способа домашнюю директорию пользователя на iOS, то программа скомпилируется. Просто будет получено исключение.
Дальнейшая работа будет проходить внутри блока
do-catch
, чтобы отлавливать возможные ошибки.
Аналогично работает метод urls(for:in:)
- он возвращает массив со всеми ссылками на директории по заданному домену (первый метод возвращает лишь первую):
let applicationURLs = manager.urls(for: .applicationDirectory, in: .allDomainsMask)
//[file:///Users/gorloff/Applications/, file:///Applications/, file:///Network/Applications/]
print(applicationURLs)
Возвращает ссылки на папки с приложениями.