Swift 5.7 предлагает много новых решений, которые заинтересуют разработчиков. В статье мы рассмотрим ряд особо полюбившихся мне приемов с конференции Apple WWDC от 2022 года. Эволюция языка программирования упрощает написание кода, который ранее был повторяющимся. Применение опционалов и обобщений и так никогда не вызывало сложностей, а теперь оно становится проще простого. 

Обратимся к примерам, и вы поймете, что я имею в виду. 

 if let для сохранения существующего имени опционала 

До появления Swift 5.7 привязка опционала принимала вид if let myOptional = myOptional. Несмотря на желание сохранить имя опционала, поскольку я привязывал его как константу внутри замыкания, все равно приходилось повторять это имя. Swift 5.7 позволяет написать что-то вроде if let myNewName = myOptional. Так становится проще сохранить существующее имя и не повторяться:

import SwiftUI

struct ContentView: View {
@State var displayedText: String?
@State var inputText: String = ""
var body: some View {
Form {
Section("Input") {
TextField("Write here", text: $inputText)
Button("Set text") {
displayedText = inputText
}
}
Section("Displayed Text") {
if let displayedText {
Text(displayedText)
} else {
Color.clear
}
}
}
}
}

У этого представления есть опциональное свойство @State с именем displayedText, и я опционально связываю его с if let. Когда значение равняется nil, предоставляется пустое пространство в виде прозрачного цвета. Это делается для того, чтобы в Form появилась строка даже при отсутствии установленного значения. 

TextField устанавливает свойство inputText, а Button обновляет displayText этим значением. 

Ключевое слово any для любого типа, соответствующего протоколу с ассоциированным типом 

Всем известно ключевое слово any, но чем оно отличается от some? Одно большое отличие Swift 5.7 заключается в том, что теперь any можно использовать для протоколов с ассоциированными типами. Чтобы протестировать эту возможность, создадим протокол MyProtocol с ассоциированным типом. 

Создаем 2 практически идентичных типа: MyConformingType и MyOtherConformingType.

import SwiftUI

protocol MyProtocol {
associatedtype MyAssociatedType: StringProtocol
var myAssociatedType: MyAssociatedType { get }
}
struct MyConformingType: MyProtocol {
var myAssociatedType = "Hello"
}
struct MyOtherConformingType: MyProtocol {
var myAssociatedType = "World"
}

struct MyType {
var anyExample: any MyProtocol = MyConformingType()
var someExample: some MyProtocol = MyConformingType()
}

struct ContentView: View {
@State var myType = MyType()
var body: some View {
VStack {
Text("myType.anyExample: " + String(describing: type(of: myType.anyExample)))
Text("myType.someExample: " + String(describing: type(of: myType.someExample)))
Button("Change") {
myType.anyExample = MyOtherConformingType()
// Не компилируется, поскольку тип не является 'some MyProtocol'
// myType.someExample = MyOtherConformingType()
}
}
}
}

Здесь есть структура MyType, применяющая оба ключевых слова any и some для создания экземпляров MyConformingType. Их типы отображаются в представлении SwiftUI, а Button предлагает возможность изменить их тип. 

Этот участок кода проясняет отличие между двумя ключевыми словами, поскольку свойство someExample никак нельзя изменить. 

Если вы остановитесь на точке останова в данной функции, то увидите, что someExample имеет тип MyProtocol.

В итоге мы получаем абстрактный тип, обладающий только свойствами, которые требует протокол. 

Я пробовал установить для someExample точно такой же экземпляр MyConformingType, но даже это не считается одним и тем же типом! 

Clock API для задержки выполнения кода 

В iOS 16 появился новый API, который упрощает работу со временем и его измерение. 

Рассмотрим простую форму Form, которая использует 2 вида часов: SuspendingClock, останавливающиеся при переходе приложения в фоновый режим, и ContinuousClock, продолжающие его работу. У каждого вида часов есть Stepper для выбора времени ожидания и Button для запуска режима ожидания. 

import SwiftUI

@available(iOS 16.0, *)
enum ClockType: String {
case Suspending, Continuous
var clock: any Clock {
self == .Suspending ? SuspendingClock() : ContinuousClock()
}
}

@available(iOS 16.0, *)
struct ClockView {
let clockType: ClockType
@State var seconds = 5

func startClock() {
Task {
do {
print("\(clockType.rawValue) clock will wait for \(seconds) seconds...")
if let suspendingClock = clockType.clock as? SuspendingClock {
try await suspendingClock.sleep(until: suspendingClock.now + .seconds(seconds))
} else if let continuousClock = clockType.clock as? ContinuousClock {
try await continuousClock.sleep(until: continuousClock.now + .seconds(seconds))
}
print("Suspending clock waited for \(seconds) seconds")
} catch {
fatalError("\(error)")
}
}
}
}

@available(iOS 16.0, *)
extension ClockView: View {
var body: some View {
Section("\(clockType.rawValue) Clock") {
Stepper("\(seconds) seconds", value: $seconds)
Button("Start \(clockType.rawValue) Clock", action: startClock)
}
}
}

@available(iOS 16.0, *)
struct ContentView: View {
@State var seconds = 5
var body: some View {
Form {
ClockView(clockType: .Suspending)
ClockView(clockType: .Continuous)
}
}
}

Поскольку может сработать функция sleep, я оборачиваю ее в инструкцию do-catch. В результате она оказывается внутри Task, так как в противном случае замыкание будет асинхронным и неприемлемым в качестве действия Button.

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

Читайте нас в TelegramVK и Дзен


Перевод статьи Rob Sturgeon: 3 Powerful Swift Tricks From WWDC 2022

Предыдущая статьяРеализация слайдера изображений и текста на React.js с вариантами оптимизации
Следующая статьяWasp  —  DSL-язык для современных веб-приложений