Swift, Playgrounds и XCPlayground

Перевод статьи “Swift, Playgrounds, and XCPlayground”.
Swift Playgrounds – это интересный инструмент в Xcode 6, позволяющий создавать единственный файл для тестирования кода перед тем, как поместить тестируемый код в основное приложение. До появления Playgrounds, у вас было две возможности — создать новое git ответвление от master ветки приложения, над которым вы работаете, или, как делаю я, открыть новый проект с шаблоном Single View Application и попытаться построить некоторую базу для тестирования того, что вы хотите.
Playgrounds уменьшают суммарное количество шагов между идеей и созданием  прототипа, но есть определенные вещи, которые нельзя выполнить на Playground как она есть, то есть ничего не добавляя. Например, запрос данных с сервера или показ views, нарисованных с помощью класса UIViews.
К счастью, эти возможности требуют всего лишь импорта одного модуля.

XCPlayground

XCPlayground – это новый модуль, у которого есть несколько методов, позволяющих получить максимум возможного от Playgrounds. Добавление его к вашему Playground – это простое добавление import предложения прямо сразу за предложением import UIKit, которое появляется по умолчанию:

import UIKit // это появляется по умолчанию
import XCPlayground // это мы добавляем

Выполнение XCP должно продолжаться бесконечно

Допустим мы хотим создать прототип для некоторого кода, который запрашивает API какого-то сервера и возвращает некоторые JSON данные. Сценарий для этих действий мог бы выглядеть так:

// создаем объект session
let session = NSURLSession(
    configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

// создаем сетевой запрос URL, в данном случае для нашего endpoint
session.dataTaskWithURL(NSURL(string: "http://localhost:8080/notes")!,
    completionHandler: { (taskData, taskResponse, taskError) -> Void in

    // создаем NSArray с полученными JSON данными
    var jsonReadError:NSError?

    let jsonArray = NSJSONSerialization.JSONObjectWithData(
        taskData, options: nil, error: &jsonReadError) as [AnyObject]

}).resume()

Проблема, с которой вы столкнетесь, если запустите приложение, состоит в том, что выполнение кода на вашей Playground происходит так быстро (асинхронно), что у завершающего блока не хватает времени для срабатывания, прежде, чем остановится выполнение кода на Playground. Это означает, что никакой код внутри завершающего блока completionHandler не будет запущен, и вы не получите возвращенные JSON данные.

Решение? Вызовите XCPSetExecutionShouldContinueIndefinitely() в самом начале вашего файла, сразу вслед за предложениями imports, и установите булевский аргумент continueIndefinitely в true. Это скажет вашей Playground, что она не должна останавливаться сразу, и вы сможете увидеть, что jsonArray объект имеет данные, которые возвращаются с помощью API.
Screen Shot 2014-12-31 at 1.49.26 AM

Путь к директории с совместно используемыми данными в XCP

Общей практикой использования приложений – прототипов является прежде всего работа с локальными данными. Часто конструирование API, которое действительно доставляет реальные данные, происходит тут же при создании приложения-прототипа, и, как правило, не принимает во внимание крайние случаи, связанных с асинхронными вызовами, помогая пользователям сосредоточиться на взаимном расположении views и других возможностях пользовательского интерфейса.

В нормальном Xcode проекте вы можете легко импортировать нужные файлы и папки, но Playgrounds этого не поддерживают. Вместо этого, каждый раз, когда Playground открывается, назначается новый случайный контейнер, глубоко скрытый в папке /var. Этот контейнер содержит папку Shared Playground Data, которая символически ссылается на /Users/HOME_FOLDER/Documents/Shared Playground Data, так что все, что вы разместите в этой папке будет доступно на вашей Playground.

Строковая константа XCPSharedDataDirectoryPath всегда содержит ссылку на эту разделяемую папку, так что любой файл, к которому мы хотим иметь доступ в нашем проекте, может быть размещен в этой Shared Playground Data папке, и становится доступным в Playground.

Более разумный путь к разделяемой директории

Размещение всех ваших файлов в этой разделяемой поддиректории /Documents прекрасно до тех пор, пока у вас не появляется много Playgrounds, так что почему бы не организовать все ваши файлы распределенными по подпапкам, связанным с именами Playgrounds, также как вы создаете файлы по проектам? Но это не поддерживается напрямую, без дополнительных усилий, однако я могу создать вспомогательный метод, который позволит это выполнять легко. Вот что я для этого использую :

func pathToFileInSharedSubfolder(file: String) -> String {
    return XCPSharedDataDirectoryPath +
           "/" +
           NSProcessInfo.processInfo().processName +
           "/" +
           file
}

NSProcessInfo().processInfo() создает объект, который содержит тонны информации о том, что в данный момент в Xcode запущено, какая именно Playground запущена! Свойство processName этого процесса – это тоже самое, что и имя файла Playground, поэтому, если у меня есть NetworkPrototype.Playground, то processName будет NetworkPrototype. Это имя добавляется вместе с / в конец пути папки Shared Playground Data. Наконец, я добавляю строку с именем файла, которую я передаю в эту функцию как аргумент file.
Screen Shot 2014-12-31 at 5.30.01 PM

Теперь, закачка содержимого файла внутрь Playground становится немного легче. Похоже на получение JSON объекта из локального JSON файла:

let jsonData = NSFileManager.defaultManager().contentsAtPath(
    pathToFileInSharedSubfolder("data.json"))!

Или, загрузка локального изображения в UIImageView:

let imageView = UIImageView()
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("code-school.png"))

Захват значения XCP

Обработка данных прототипа – это прекрасно, но, возможно, вы захотите создать несколько действительных views. Например, рассмотрим следующий код Playground:

let view = UIView()
view.frame = CGRectMake(0,0,320,568)
view.backgroundColor = UIColor.lightGrayColor()

let imageView = UIImageView()
imageView.frame = CGRectMake(20, 20, 280, 51)
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("code-school.png"))
view.addSubview(imageView)

let label = UILabel()
label.frame = CGRectMake(0, 100, 320, 30)
label.textAlignment = .Center
label.text = "Welcome!"
view.addSubview(label)

Этот код создает view и устанавливает цвет фона в серый, добавляет image view из локального файла (смотри вышеприведенный код), а также метку с текстом “Welcome!”. Один из возможный способов увидеть результирующий view – это кликнуть на крошечной иконке “глаза” в отладочной панеле Playground, но увидеть предварительное изображение можно только один раз, и тут же придется его закрыть и вернуться к кодированию.

Вызывая XCPCaptureValue() и передавая ему идентификатор в виде строки и сам view объект, вы сможете смотреть как он постепенно формируется с течением времени, подобно этому:

...
label.text = "Welcome!"
view.addSubview(label)

XCPCaptureValue('mainView', view)

view – это созданный нами ранее серый UIView объект. Если вы будете дополнительно использовать еще и XCPExecutionShouldContinueIndefinitely как я описал выше, то у вас будет непрерывная модификация предварительного просмотра UIView прямо внутри Playground!

Screen Shot 2014-12-31 at 5.34.09 PM

Ниже находится ссылка на zip файл, содержащий логотип Code School и Playground c кодами этого поста: CodeSchoolProtoPlayground.zip.

Leave a Reply

Your email address will not be published. Required fields are marked *