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
.
Читайте также:
- Swift: 7 секретов оптимизации
- 16 полезных расширений для SwiftUI
- Как сделать кастомные шорткаты для Siri
Читайте нас в Telegram, VK и Дзен
Перевод статьи Rob Sturgeon: 3 Powerful Swift Tricks From WWDC 2022