-
Novedades de Swift
Acompáñanos para conocer las últimas novedades sobre Swift. Descubre los avances más recientes del lenguaje, que incluyen mejoras en la ergonomía del día a día, una mejor concurrencia y un código de alto rendimiento más seguro. Explora las mejoras y actualizaciones en el flujo de trabajo y la interoperabilidad de lenguajes en Swift integrado.
Capítulos
- 0:07 - Introducción
- 0:44 - Mejoras en el uso diario del lenguaje
- 1:55 - Disponibilidad de anyAppleOS
- 3:02 - Atributo @diagnose
- 3:52 - Selectores de módulos (::)
- 5:59 - Actualizaciones de las bibliotecas
- 6:16 - Biblioteca estándar
- 7:31 - Actualizaciones de Swift Testing
- 9:29 - Subprocess 1.0
- 10:14 - Foundation
- 11:59 - Más allá de las plataformas de Apple
- 12:35 - Interoperabilidad de Swift–C (atributo @C)
- 15:09 - Swift-Java
- 16:03 - Compatibilidad con el editor
- 16:44 - WebAssembly (Wasm) y JavascriptKit
- 18:08 - Swift integrado
- 19:59 - Ajuste de rendimiento
- 21:29 - Control del optimizador: @inline(always) y @specialized
- 24:29 - Sistema de propiedad y tipos no copiables
- 26:18 - Protocolo Iterable y accesores Borrow/Mutate
- 28:57 - Nuevos tipos de biblioteca estándar: UniqueBox, UniqueArray, Ref
- 31:11 - El futuro de Swift
Recursos
Videos relacionados
WWDC26
-
Buscar este video…
-
-
1:12 - Better Swift Concurrency diagnostics (catching in the task)
Task { do { try lander.fly(to: moon) } catch { lander.abort() } } -
1:21 - Better Swift Concurrency diagnostics (saving the task for later)
let landingTask = Task { try lander.fly(to: moon) } defer { await orbiter.rendezvous(with: lander) } try await orbiter.justHangOut(waitingFor: landingTask) -
1:27 - Better 'Sendable' conformances
final class Spacecraft: Sendable { ... weak let dockedAt: SpaceStation? ... } class Mission: ~Sendable { ... } class CrewedMission: Mission, @unchecked Sendable { ... } -
1:48 - More accessible memberwise initializers
struct Briefing { internal var topic: String internal var scheduledAt: Date private var attendees: [Person] = [] } // Generated memberwise initializers: // extension Briefing { // private init(topic: String, scheduledAt: Date, attendees: [Person] = []) { // self.topic = topic // self.scheduledAt = scheduledAt // self.attendees = attendees // } // // internal init(topic: String, scheduledAt: Date) { // self.topic = topic // self.scheduledAt = scheduledAt // self.attendees = [] // } // } -
2:03 - 'anyAppleOS' availability (before)
extension Mission { @available(macOS 27, iOS 27, watchOS 27, tvOS 27, visionOS 27, *) func showStatus() { ... } @available(macOS 27, iOS 27, watchOS 27, visionOS 27, *) @available(tvOS, unavailable) func launch() { ... } #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) func makeLiveActivityWidget() -> some Widget { ... } #endif } -
2:17 - 'anyAppleOS' availability (after)
extension Mission { @available(anyAppleOS 27, *) func showStatus() { ... } @available(anyAppleOS 27, *) @available(tvOS, unavailable) func launch() { ... } #if os(anyAppleOS) func makeLiveActivityWidget() -> some Widget { ... } #endif } -
2:40 - Controlling warnings with '@diagnose'
@diagnose(DeprecatedDeclaration, as: ignored, reason: "Flying with surplus hardware") func makeApolloSoyuzMission() -> Mission { CrewedMission( rocket: makeSaturnIRocket(), payload: makeApolloCSM(), crew: [.daniellePoole, .nathanMorrison] ) } @diagnose(StrictMemorySafety, as: warning) func uplinkCommand(from receiver: inout Receiver, to computer: inout Computer) { let commandSize = receiver.receiveInt() receiver.withReceivedData(byteCount: commandSize) { computer.receiveUplinkedCommand($0) } } @diagnose(ErrorInFutureSwiftVersion, as: error) func fetchPosition() -> (x: Double, y: Double, z: Double) { return self.rotation } -
3:47 - Clarifying code with module selectors
import Rocket import GiftShopToys let rocket1 = SaturnV() // could mean `Rocket::SaturnV` or `GiftShopToys::SaturnV` let rocket2 = Rocket.SaturnV() // prefers `Rocket::Rocket.SaturnV` let rocket3 = Rocket::SaturnV() // correctly finds `Rocket::SaturnV` -
5:00 - Clarifying code with module selectors (module selectors work on members, too)
// // Module Chemistry // public protocol Flammable { ... } extension Flammable { /// Set `self` on fire. public func fire() { ... } } // // Module HumanResources // import Chemistry public protocol Employee { ... } extension Employee { /// Remove `self` from job. public func fire() { ... } } public class LaunchPadTechnician: Employee, Flammable { ... } // // Module main // import HumanResources import Chemistry let launchPadTechnician = LaunchPadTechnician(...) launchPadTechnician.HumanResources::fire() -
6:26 - Task cancellation
// Radio for help extension Radio { func send(_ data: [UInt8] { if Task.isCancelled { return } // ... } } extension EmergencyTransponder { func sendSOS() { radio.send(makeSOSPacket()) } } -
6:40 - Task cancellation shield
// Radio for help extension Radio { func send(_ data: [UInt8] { if Task.isCancelled { return } // ... } } extension EmergencyTransponder { func sendSOS() { withTaskCancellationShield { radio.send(makeSOSPacket()) } } } -
6:53 - Constructing a new dictionary
// Map values with keys func makeCalendarDisplayNames(for missions: [Mission: LaunchWindow]) -> [Mission: String] { let new: [Mission: String] = .init( uniqueKeysWithValues: missions.lazy.map { mission, launchWindow in (mission, makeDisplayName(for: mission, in: launchWindow)) } ) return new } -
7:06 - Dictionary.mapKeyedValues
// Map values with keys func makeCalendarDisplayNames(for missions: [Mission: LaunchWindow]) -> [Mission: String] { missions.mapKeyedValues { mission, launchWindow in makeDisplayName(for: mission, in: launchWindow) } } -
7:14 - The new FilePath type
// FilePath handling macOS-named resources var path: FilePath = "/var/www/static" path.components.append("WWDC") print(path.components) // [ "var", "www", "static", "WWDC" ] var path: FilePath = "/var/www/static/..namedresource/rsrc" print(path.components) // [ "var", "www", "static" ] -
7:41 - Issue Severity
// Issue severity @Test(arguments: allRockets) func testBurn(rocket: Rocket) throws { rocket.burn(for: .seconds(150)) let remaining = rocket.propellantKg / rocket.totalPropellantKg if remaining < 0.10 { Issue.record( "\(rocket.name) remaining fuel is below 10% reserve target", severity: .warning ) } #expect(remaining > 0.02, "\(rocket.name) propellant critically low - abort") } -
7:52 - Test Cancellation
// Test Cancellation @Test(arguments: allRockets) func testBurn(rocket: Rocket) throws { // solid-fuel rocket engines can't be stopped if rocket.engineType == .solid { try Test.cancel("\(rocket.name) has solid fuel") } rocket.burn(for: .seconds(150)) let remaining = rocket.propellantKg / rocket.totalPropellantKg if remaining < 0.10 { Issue.record( "\(rocket.name) remaining fuel is below 10% reserve target", severity: .warning ) } #expect(remaining > 0.02, "\(rocket.name) propellant critically low - abort") } -
8:34 - XCTest interoperability: Using XCTest from Swift Testing
// XCTest interoperability: Using XCTest from Swift Testing func checkedTransmitAndReceive(on radio: Radio, packet: Packet, expectedByteCount: Int) throws -> [UInt8] { try radio.transmit(bytes: packet.data) let bytes = try radio.receive() XCTAssertEqual(bytes.count, expectedByteCount) return bytes } @Test func pingTest() throws { let radio = Radio() let bytes = try checkedTransmitAndReceive(on: radio, packet: .ping, expectedByteCount: 8) #expect(bytes == [0x00, 0x00, 0xf0, 0x37, 0x0f, 0xc7, 0x00, 0x01]) } -
8:48 - XCTest interoperability: Using Swift Testing from XCTest
// XCTest interoperability: Using Swift Testing from XCTest class RadioTests: XCTestCase { func testPingPacketTransmission() { let radio = Radio() let bytes = try checkedTransmitAndReceive(on: radio, packet: .ping, expectedByteCount: 8) #expect(bytes == [0x00, 0x00, 0xf0, 0x36, 0x0f, 0xc7, 0x00, 0x02]) } } -
10:01 - Subprocess Output Stream
// Subprocess output streaming let result = try await Subprocess.run(.name("ls"), input: .none, output: .sequence, error: .string(limit:4096)) { execution in execution.standardOtput.strings().filter { $0.hasSuffix(".obj") } } for try await objectFiles in result.closureOutput { print("Object file: \(objectFile)") } -
10:37 - Progress Manager - Concurrency
// Progress reporting - Concurrency let manager = ProgressManager(totalCount: 100) try await rocket.launch(mission.subprogress(assigningCount: 100)) extension Rocket { func launch(_ progress: consuming Subprogress? = nil) async throws { let stage = progress?.start(totalCount: 3) try await ignite(); stage?.complete(count: 1) try await liftoff(); stage?.complete(count: 1) try await stageSeparation(); stage?.complete(count: 1) } } -
10:37 - Progress Manager - progress reporting
// Progress reporting - progress reporting let manager = ProgressManager(totalCount: 100) try await rocket.launch(mission.subprogress(assigningCount: 100)) Task { for await update in Observations({ mission.fractionCompleted }) { print("🚀 Mission \(Int(update * 100))%") } } -
10:37 - Progress reporting - metadata
// Progress reporting - metadata extension Rocket { func ascend(_ progress: consuming Subprogress) async throws { let stage = progress.start(totalCount: 3) stage.detlaV = 3_400; try await burn(); stage.complete(count: 1) stage.detlaV = 2_100; try await stageSeparation(); stage.complete(count: 1) stage.detlaV = 1_800; try await coast(); stage.complete(count: 1) } } print("Δv to orbit: \(mission.summary(of: \.deltaV)) m/s") -
20:56 - Directly control inlining (source code)
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> { var result = makeInts(randomized: false) for value in values { result[Int(value)] += 1 } return result } func makeInts(randomized: Bool) -> [256 of Int] { if randomized { InlineArray { _ in Int.random(in: (.min)...(.max)) } } else { InlineArray(repeating: 0) } } -
21:01 - Directly control inlining (inlined, but not optimized)
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> { var result = if false { // InlineArray { _ in Int.random(in: (.min)...(.max)) } // } else { // Inlined code InlineArray(repeating: 0) // } // for value in values { result[Int(value)] += 1 } return result } func makeInts(randomized: Bool) -> [256 of Int] { if randomized { InlineArray { _ in Int.random(in: (.min)...(.max)) } } else { InlineArray(repeating: 0) } } -
21:07 - Directly control inlining (inlined and optimized)
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> { var result = InlineArray(repeating: 0) // Inlined and optimized code for value in values { result[Int(value)] += 1 } return result } func makeInts(randomized: Bool) -> [256 of Int] { if randomized { InlineArray { _ in Int.random(in: (.min)...(.max)) } } else { InlineArray(repeating: 0) } } -
21:30 - Directly control inlining (preventing inlining)
@inline(never) func makeInts(randomized: Bool) -> [256 of Int] { if randomized { InlineArray { _ in Int.random(in: (.min)...(.max)) } } else { InlineArray(repeating: 0) } } -
21:39 - Directly control inlining (forcing inlining)
@inline(always) func makeInts(randomized: Bool) -> [256 of Int] { if randomized { InlineArray { _ in Int.random(in: (.min)...(.max)) } } else { InlineArray(repeating: 0) } } -
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> { var result = makeInts(randomized: false) for value in values { result[Int(value)] += 1 } return result } // Note: Specialized function doesn't actually have a directly callable name. func `histogram of [UInt8]`(of values: [UInt8]) -> [256 of Int] { // var result = makeInts(randomized: false) // // for value in values { // result[Int(value)] += 1 // Specialized code } // // return result // } // -
@specialized(where Values == [UInt8]) func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> { var result = makeInts(randomized: false) for value in values { result[Int(value)] += 1 } return result } // Note: Specialized function doesn't actually have a directly callable name. func `histogram of [UInt8]`(of values: [UInt8]) -> [256 of Int] { // var result = makeInts(randomized: false) // // for value in values { // result[Int(value)] += 1 // Specialized code } // // return result // } // -
25:46 - Associated types can be '~Copyable' and '~Escapable'
protocol Iterable<Element, Failure>: ~Copyable, ~Escapable { associatedtype Element: ~Copyable associatedtype IterableIterator: IterableIteratorProtocol<Element, Failure>, ~Copyable, ~Escapable associatedtype Failure: Error = Never func makeIterableIterator() -> IterableIterator var underestimatedCount: Int { get } } protocol IterableIteratorProtocol<Element, Failure>: ~Copyable, ~Escapable { associatedtype Element: ~Copyable associatedtype Failure: Error = Never mutating func nextSpan(maximumCount: Int) throws(Failure) -> Span<Element> mutating func skip(by maximumOffset: Int) throws(Failure) -> Int } -
27:28 - The problem with existing accessors
@safe public struct UniqueBox<Value>: ~Copyable { private let valuePointer: UnsafeMutablePointer<Value> public init(_ value: consuming Value) { valuePointer = UnsafeMutablePointer.allocate(capacity: 1) valuePointer.initialize(to: value) } public var value: Value { get { valuePointer.pointee } set { valuePointer.pointee = newValue } } deinit { valuePointer.deinitialize(count: 1) valuePointer.deallocate() } } -
28:19 - 'borrow' and 'mutate' accessors
@safe public struct UniqueBox<Value: ~Copyable>: ~Copyable { private let valuePointer: UnsafeMutablePointer<Value> public init(_ value: consuming Value) { valuePointer = UnsafeMutablePointer.allocate(capacity: 1) valuePointer.initialize(to: value) } public var value: Value { borrow { valuePointer.pointee } mutate { &valuePointer.pointee } } deinit { valuePointer.deinitialize(count: 1) valuePointer.deallocate() } } -
30:14 - Using 'MutableRef' to eliminate repeated accesses (with un-hoisted access)
func updateCount<Key: Hashable>( for key: Key, from sets: [Set<Key>], in counts: inout [Key: Int] ) { for set in sets { if set.contains(key) { counts[key, default: 0] += 1 } } } -
30:34 - Using 'MutableRef' to eliminate repeated accesses (hoisted by 'inout' parameter)
func updateCount<Key: Hashable>( for key: Key, from sets: [Set<Key>], in counts: inout [Key: Int] ) { func updateCountImpl(count: inout Int) { for set in sets { if set.contains(key) { count += 1 } } } updateCountImpl(count: &counts[key, default: 0]) } -
30:41 - Using 'MutableRef' to eliminate repeated accesses (hoisted by 'MutableRef')
func updateCount<Key: Hashable>( for key: Key, from sets: [Set<Key>], in counts: inout [Key: Int] ) { var countRef = MutableRef(&counts[key, default: 0]) for set in sets { if set.contains(key) { countRef.value += 1 } } }
-