Презентация нового фреймворка от Apple стала настоящей сенсацией. В один момент эксперты в области разработки приложений под яблочные устройства превратились в джунов. SwiftUI бросает вызов существующим стандартам и видимо навсегда оставит в прошлом многие, уже устоявшиеся, подходы в разработке ios приложений. Огромное количество open source фрейворков, собравшие тысячи звёзд на гитхабе, со временем прекратят своё существование. Если кто-то ждал «второго пришествия» в мире мобильной разработки, это судя по всему оно.
Но как это стало возможным? Почему Apple в один момент решила отказаться от инструментов на разработку и развитие которых было потрачено столько лет !? Что-ж, современный мир быстро меняется и существующие решения перестают удовлетворять растущие потребности. Сложность программных продуктов и приложений постоянно растёт и теперь требуются более умные и продвинутые инструменты. “Simple path to make great app” — эта фраза которая очень часто звучала на wwdc. SwiftUI призван взять на себя огромную часть работы, с которой приходилось сталкиваться разработчику снова и снова, от приложения к приложению. Все это стало возможным благодаря двум основным нововведениям: созданию эффективного DSL для описания интерфейса и использованию реактивного подхода для распространения данных. В этой статье хотелось бы остановиться на первом из них.
Декларативный подход и DSL.
Декларативный подход подразумевает, что вы описываете ожидаемый результат вместо того чтобы описывать последовательность команд благодаря которым этот результат можно получить .
Сравните два подхода для отображения кнопки на экране:
Используя SwiftUI вы описываете только то, что непосредственно касается отображаемой кнопки:
- действие кнопки
- текст внутри, его цвет и отступ до границ кнопки
- цвет кнопки и скругление углов
В привычном UIKit вам необходимо знать как работает отображение элементов на экране, чтобы добиться того же результата.
Во первых нужно помнить, что cornerRadius задается на внутреннем объекте (CALayer), а не на самой кнопки (следует также учитывать, что его иногда нужно заново отрисовывать, когда меняются размеры кнопки).
Помимо этого необходимо также знать, что такое констрейнты, селекторы, зачем нужен атрибут @objc и почему нужно устанавливать флаг translateAutoresizingMaskIntoConstraint в false
Декларативный подход с которым пришел к нам SwiftUI, использует то, что называется DSL (Domain Specific Language). Переводя на русский — «Язык предметной области», смысл его заключается в том, что вы используя язык высокого уровня такой как swift, описываете специфичные для вашей предметной области примитивы на которых и будете строить ваше преложение, вместо того чтобы напрямую реализовывать его на фреймворках предоставляемых самим языком. По сути, вы строите свой язык программирования поверх имеющегося. В данном случае доменом (или предметной областью) является задача построения интерфейса. Swift UI предоставляет свой DSL в виде набора простых и понятных компонентов таких как Text, Button, Spacer, модификаторов, а также задает правила их использования. Такой подход интуитивно понятный, и часто зная только DSL иногда можно легко догадаться как получить желаемый результат.
Но в отличие от того DSL который можно было бы реализовать для своего собственно приложения используя swift 5.0, яблочники идут еще дальше и очищают свой DSL по максимуму, делая его более компактным и понятным. И такой шаг конечно не возможен без изменения в самом языке. Поэтому новый фреймворк идет бок о бок с новой версией языка.
Swift 5.1 вобрал в себя ключевые изменения благодаря которым стал возможен простой и понятный синтаксис SwiftUI. Давайте взглянем на основные из них:
Function builders. [SE-XXXX]
Благодаря этой фичи, стал возможен подобный синтаксис
HStack {
Text("Очень")
Text("страный")
Text("код")
}
Конструктор для HStack выглядит следующим образом:
init(…, content: @ViewBuilder () -> Content)
@ViewBuilder — это структура объявленная с атрибутом @_functionBuilder, которая и добавляет “магии”. Компилятор разворачивает такой блок в нечто подобное:
HStack {
return ViewBuilder.buildBlock(Text(“Очень”), Text(“страный”), Text(“код”))
}
Используя @_functionBuilder можно создавать свои собственные билдеры, например, @HTMLBuilder для создание HTML документа:
html {
head {}
body{
p { "Hello HTML" }
}
}
Implicit returns from single-expression functions [SE-0255]
Если в функции или вычислимом свойстве используется только одно выражение то «return» будет подставлен неявно, что избавляет от необходимости указывать return например в body:
var body: some View {
Text(“Hello World”)
}
Synthesize default values for the memberwise initializer [SE-0242]
В предыдущих версиях swift такой код не собирался бы:
struct someSwift {
let name: String
let count: Int = 0
}
let mySwift = someSwift(name: "me", count: 4)
Компилятор создаст только один конструктор принимающий name, Для Swift 5.1 компилятор создаст в данном примере два конструктора, и код корректно скомпилируется.
Opaque return types [SE-0244]
Это изменение позволяет, используя ключевое слово some перед типом возвращаемого значения, скрыть его внутренние ассоциативные или generic типы.
“Скрывание” позволяет вернуть протокол с ассоциативным типом из функции и избежать подобной ошибки:
Protocol ‘MyPAT’ can only be used as a generic constraint because it has Self or associated type requirements
Данная фича имеет еще одно полезное свойство - она позволяет избежать явного указания generic параметров для возвращаемого типа и вместо:
struct EightPointedStar: GameObject {
var shape: Union<Rectangle, Transformed<Rectangle>> {
return Union(Rectangle(),
Transformed(Rectangle(), by: .fortyFiveDegrees)
}
}
использовать более простое объявление:
struct EightPointedStar: GameObject {
var shape: some Shape {
return Union(Rectangle(),
Transformed(Rectangle(), by: .fortyFiveDegrees)
}
}
Property wrappers [SE-0258]
Еще одна интересная функция Swift 5.1 это делегаты свойств. Они позволяют зашить в свойство определенную логику, объявив его с нужным вам атрибутом. Разберем на примере.
Довольно часто в приложениях приходится использовать UserDefaults. Например чтобы реализовывать логику отображения приветственных экранов мы будем записывать в UserDefault значение и проверять его наличие при запуске:
var isFirstLaunch: Bool {
get { return UserDefaults.standard.object(forKey: key) == nil }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
Благодаря property wrappers мы можем создать атрибут который будет содержать всю необходимую логику взаимодействия с UserDefaults и переиспользовать его на любом свойстве.
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
и теперь можно легко объявлять такое свойство:
@UserDefault(key: "IsFirstLaunch", defaultValue: false)
static var isFirstLaunch: Bool
Apple использует эту фичу для биндинга состояния объекта на View
struct SettingsView: View {
@State var saveHistory: Bool
@State var enableAutofill: Boolvar body: some View {
return VStack {
Toggle(isOn: $saveHistory) { ...}
Toggle(isOn: $enableAutofill) { ... }
}
}
}
Как мы можем видеть Apple сильно постарался чтобы их SwiftUI выглядел максимально просто, универсально и чисто. Что в свою очередь избавит новые разработанные приложения от множества багов, сделает их более стандартизированными и приятными. А также значительно повысит скорость разработки, позволяя больше сосредоточится на уникальной ценности приложения.
Thanks for reading ✌🏼