Slide 1

Slide 1 text

৽OSͷػೳΛݹ͍OSʹ όοΫϙʔτ͢ΔͨΊͷ࢓૊Έ Mike Apurin / @auramagi

Slide 2

Slide 2 text

Mike Apurin ΞϓϦϯɾϛϋΠϧ auramagi auramagi @auramagi@hachyderm.io Ѫೣ ΢ΥϧΧ

Slide 3

Slide 3 text

Apple͕ఏڙ͍ͯ͠ΔόοΫϙʔτ

Slide 4

Slide 4 text

Ͳ͏ͯ͠ݹ͍OSͰ৽ػೳ͕࢖͑ͳ͍ͷ͔ʁ • ຖ೥ɺ৽͍͠OS͕ϦϦʔε͞Εɺ৽ػೳ͕࣍ʑͱ௥Ճ͞ΕΔ • ΄ͱΜͲͷ৔߹͜ΕΒͷ৽ػೳ͸ɺͦͷ೥ͷOSόʔδϣϯʹͷΈؚ·Ε͍ͯΔ • ৽͍͠ػೳ͸ɺ৽͍͠OSͷϑϨʔϜϫʔΫόΠφϦʹؚ·Ε͓ͯΓɺݹ͍OSͰ ͸൓ө͞Εͳ͍ • ݹ͍OSΛαϙʔτͨ͠··Ͱɺ৽ػೳΛ࢖͏ʹ͸όοΫϙʔτ͕ඞཁͱͳΔ

Slide 5

Slide 5 text

ΞϓϦͷϏϧυ

Slide 6

Slide 6 text

ΞϓϦͷϏϧυ / Static Linking

Slide 7

Slide 7 text

ΞϓϦͷϏϧυ / Dynamic Linking

Slide 8

Slide 8 text

ΞϓϦͷϏϧυ / γεςϜϑϨʔϜϫʔΫ

Slide 9

Slide 9 text

ϥΠϒϥϦͷϏϧυ

Slide 10

Slide 10 text

γεςϜϑϨʔϜϫʔΫͷ഑෍

Slide 11

Slide 11 text

γεςϜϑϨʔϜϫʔΫͷswiftinterfaceϑΝΠϧ

Slide 12

Slide 12 text

७ਖ਼όοΫϙʔτ / privateͩͬͨγϯϘϧΛpublicʹมΘΔ • ϑϨʔϜϫʔΫͷϔομʔɾΠϯλϑΣʔεʹ͋Δͷ͸ެ։γϯϘϧ͚ͩ • ݹ͍OSͰ͸ඇެ։ͷΫϥε΍ؔ਺͕ଘࡏ͍ͯͨ͠Βɺ৽͍͠ϦϦʔεʹ߹Θͤͯ ެ։ʹ͢Δ͜ͱͰόοΫϙʔτ͕Մೳ class MyViewController: UIViewController { override func viewIsAppearing(_ animated: Bool) { super.viewIsAppearing(animated) // ... } }

Slide 13

Slide 13 text

७ਖ਼όοΫϙʔτ / σϑΥϧτҾ਺͕มΘΔ • SwiftͰ͸ɺσϑΥϧτҾ਺͕ϑϨʔϜϫʔΫͷόΠφϦʹ૊Έࠐ·Εͣ swiftinterfaceʹଘࡏ͍ͯ͠Δ @frozen public struct RoundedRectangle : SwiftUI.Shape { @inlinable public init( cornerRadius: CoreFoundation.CGFloat, style: SwiftUI.RoundedCornerStyle = .circular ) { // ... } } @frozen public struct RoundedRectangle : SwiftUICore.Shape { @inlinable nonisolated public init( cornerSize: CoreFoundation.CGSize, style: SwiftUICore.RoundedCornerStyle = .continuous ) { // ... }

Slide 14

Slide 14 text

७ਖ਼όοΫϙʔτ / ࣮૷͕·Δ͝ͱswiftinterfaceʹల։͞ΕΔ • ؔ਺Λ @inlinable ΍ @_alwaysEmitIntoClient Ͱम০͢Δͱɺ࣮૷͕ϑϨʔϜϫʔ ΫͷόΠφϦʹ૊Έࠐ·Εͣ swiftinterface ʹιʔείʔυͷ··Ͱίϐʔ͞ΕΔ • όοΫϙʔτʹݶΒͣ SwiftUI Ͱͦͷύλʔϯ͕ଟ͘࢖ΘΕ͍ͯΔ extension SwiftUICore.Shape where Self == SwiftUICore.RoundedRectangle { @_alwaysEmitIntoClient public static func rect( cornerSize: CoreFoundation.CGSize, style: SwiftUICore.RoundedCornerStyle = .continuous ) -> Self { .init( cornerSize: cornerSize, style: style ) } }

Slide 15

Slide 15 text

७ਖ਼όοΫϙʔτ / ࣮૷͕·Δ͝ͱswiftinterfaceʹల։͞ΕΔ .cornerRadius(16) // deprecated .clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(.rect(cornerRadius: 16)) .clipShape(.rect) .clipShape(.circle) .clipShape(.capsule)

Slide 16

Slide 16 text

७ਖ਼όοΫϙʔτ / iOS 18 nonisolated public func onGeometryChange( for type: T.Type, of transform: @escaping (GeometryProxy) -> T, action: @escaping (_ newValue: T) -> Void ) -> some View where T : Equatable nonisolated public func gesture( _ gesture: T, isEnabled: Bool ) -> some View where T : Gesture nonisolated public func tag( _ tag: V, includeOptional: Bool = true ) -> some View where V : Hashable

Slide 17

Slide 17 text

७ਖ਼όοΫϙʔτ / ݹ͍OS͚ͩswiftinterfaceʹ࣮૷͕ల։͞ΕΔ • ݹ͍OSͰγϯϘϧ͕ଘࡏ͍ͯ͜͠ͱʹ͢ΔͨΊͷम০: @backDeployed • ৽OS͸ϥΠϒϥϦͷόΠφϦΛ࢖͍ͭͭɺݹ͍OS͸swiftinterfaceͷ࣮૷Λ࢖͏͜ͱͱ͔ • SwiftUIʹ͸΄ͱΜͲݟͳ͍͕ɺStoreKitͰ͸ಛʹΑ͘࢖ΘΕ͍ͯΔ struct Transaction { @backDeployed(before: iOS 17.2, macOS 14.2, tvOS 17.2, watchOS 10.2, visionOS 1.1) public var currency: Foundation.Locale.Currency? { get { if #available(iOS 17.2, macOS 14.2, tvOS 17.2, watchOS 10.2, visionOS 1.1, *) { return currencyStorage } else { return backing.value( atKeyPath: "currency", sentinel: nil, transform: { String($0).map({ Locale.Currency($0) }) } ) } } } }

Slide 18

Slide 18 text

ϚΫϩ • ϚΫϩࣗମ͸OSͷϦϦʔεʹ͸ؚ·ͣXcodeʹଘࡏ͍ͯ͠ΔͷͰɺXcodeͷϦ Ϧʔεʹ߹Θͤͯݹ͍OSͰ࢖͑ΔϚΫϩͷ௥Ճ͕Մೳ

Slide 19

Slide 19 text

ϚΫϩ / Xcode 16 struct MyCustomValueKey: EnvironmentKey { static var defaultValue: String { "Default value" } } extension EnvironmentValues { var myCustomValue: String { get { self[MyCustomValueKey.self] } set { self[MyCustomValueKey.self] = newValue} } } extension EnvironmentValues { @Entry var myCustomValue: String = "Default value" }

Slide 20

Slide 20 text

ϚΫϩ / Xcode 16 private struct PreviewContent: View { @State var toggled = true var body: some View { Toggle("Loud Noises", isOn: $toggled) } } #Preview("toggle") { PreviewContent() } #Preview("toggle") { @Previewable @State var toggled = true return Toggle("Loud Noises", isOn: $toggled) }

Slide 21

Slide 21 text

OSSͷόοΫϙʔτ

Slide 22

Slide 22 text

OSSͷόοΫϙʔτ • ৽ػೳΛ࢖͍ͨͯ͘όοΫϙʔτॻ͍ͯ͘ΕΔਓ͕ଟ͍͘Δ • GitHubͱ͔Ͱݕࡧ͢Δͱཉ͍͠΋ͷ͕طʹ͋Δ͔΋͠Εͳ͍

Slide 23

Slide 23 text

OSSͷόοΫϙʔτ / SwiftUI৭ʑ • ͋Ε΍͜Εͷ SwiftUI ػೳͷόοΫϙʔτ • StateObject • AppStorage • AsyncImage • .task • ͳͲͳͲ shaps80/SwiftUIBackports

Slide 24

Slide 24 text

OSSͷόοΫϙʔτ / Observation • iOS 17Ͱग़ͨObservationϑϨʔϜϫʔΫΛ
 ·Δ͝ͱόοΫϙʔτ • ΋͸΍ຐ๏ 🪄✨ pointfreeco/swift-perception

Slide 25

Slide 25 text

OSSͷόοΫϙʔτ / Group(subviews:transform:) • iOS 18Ͱ৽͘͠௥Ճ͞Εͨ Group ͷػೳ • GroupͷதͷࢠϏϡʔͰԿΒ͔ͷॲཧΛ͢Δ • iOS 17ҎԼͰ͸ඇެ։ͷVariadicViewΛ࢖͏ඞ ཁ͕͋ͬͨ Lumisilk/SwiftUI-AnySubviews

Slide 26

Slide 26 text

ࣗલ࣮૷ͷόοΫϙʔτ

Slide 27

Slide 27 text

ͦ΋ͦ΋όοΫϙʔτͰ͖Δ͔ʁ • όοΫϙʔτͰ͖ͳ͍΋ͷ͕͋Δ • OSͱີ઀ʹؔΘΔػೳ • WidgetsɺApp IntentsɺAppϥΠϑαΠΫϧ • ݴޠ࢖༻ • RegexɺConcurrency • είʔϓ͕σΧ͗ͯ͢ݱ࣮తʹࣗલ࣮૷Ͱ͖ͳ͍ • SwiftUIશମ

Slide 28

Slide 28 text

όοΫϙʔτʹ޲͍͍ͯΔػೳ • ࠓ·Ͱ΋Մೳͩͬͨ΋ͷ͕ΑΓॻ͖΍͘͢ͳͬͨ • είʔϓ͕ݶΒΕ͍ͯΔ΋ͷ • ࣮૷ͷΠϝʔδ͕͖ͭ΍͍͢΋ͷ

Slide 29

Slide 29 text

ʮόοΫϙʔτʯ͔ʮϑΥʔϧόοΫʯ͔ • όοΫϙʔτ • ৽ػೳͱಉ͡࢓૊ΈΛ࣮૷ͯ͠ಉ͡Α͏ʹ࢖͏ • ϑΥʔϧόοΫ • ৽ػೳͷ࢓૊Έͱؔ܎ͳ͘ɺͦΕʹҧ͍ޮՌΛ࣮૷͢Δ

Slide 30

Slide 30 text

ʮόοΫϙʔτʯ͔ʮϑΥʔϧόοΫʯ͔ • ྫ͑͹ɺiOS 17 Ͱొ৔ͨ͠ ContentUnavailableView Λ࢖͍͍ͨ৔߹ ContentUnavailableView { Label("ݕࡧ݁Ռ͕͋Γ·ͤΜ", systemImage: "magnifyingglass") } description: { Text("ผͷΩʔϫʔυΛ͓ࢼ͍ͩ͘͠͞") }

Slide 31

Slide 31 text

ʮόοΫϙʔτʯ͔ʮϑΥʔϧόοΫʯ͔ @ViewBuilder var unavailableView: some View { if #available(iOS 17, *) { // ... } else { VStack(spacing: 16) { Image(systemName: "magnifyingglass") .font(.system(.largeTitle).weight(.medium)) .imageScale(.large) .foregroundColor(.secondary) VStack { Text("ݕࡧ݁Ռ͕͋Γ·ͤΜ") .font(.title2.bold()) Text("ผͷΩʔϫʔυΛ͓ࢼ͍ͩ͘͠͞") .font(.callout) .foregroundColor(.secondary) } } } }

Slide 32

Slide 32 text

όοΫϙʔτΛ࣮૷ / ద੾ͳωʔϛϯά • ޙ΄Ͳ࡟আ͞ΕΔલఏͷίʔυ • Ͳ͜Ͱ࢖ΘΕ͍ͯΔ͔ɺͲ͏΍ͬͯണ͕͢ͷ͔Λ࣮૷౰͔࣌ΒܾΊΔ • enum Backport ͰωʔϜεϖʔεΛ࡞͓ͬͯ͘ͱศར • ݩωλ͸ Dave DeLong ࢯͷهࣄ • https://davedelong.com/blog/2021/10/09/simplifying-backwards-compatibility- in-swift/

Slide 33

Slide 33 text

όοΫϙʔτΛ࣮૷ / ద੾ͳωʔϛϯά public struct Backport { public let content: Content public init(_ content: Content) { self.content = content } } extension View { var backport: Backport { Backport(self) } } extension Backport where Content: View { func myNewFunction() -> some View { // ... } } // ... Text("iOSDC 2024") .backport.myNewFunction()

Slide 34

Slide 34 text

όοΫϙʔτΛ࣮૷ / OSͷόʔδϣϯͰ࢖͍෼͚Δ • ৽OSͰ͸৽ػೳΛ७ਖ਼ͷ··ɺݹ͍OSͰ͸όοΫϙʔτͱ͍͏࢖͍෼͚ extension Backport where Content: View { func myNewFunction() -> some View { if #available(iOS 18, *) { // ... } else { ... } } }

Slide 35

Slide 35 text

όοΫϙʔτΛ࣮૷ / geometryGroup struct ContentView: View { @State var flag = false @State var flag2 = false var body: some View { Color.cyan .frame(width: 128, height: 128) .overlay { Group { if flag2 { Text("iOSDC") } else { Text("લ໷ࡇ") } } .task(id: flag2) { try? await Task.sleep(for: .seconds(1.5)) flag2.toggle() } } .offset(y: flag ? 100 : -100) .animation(.linear(duration: 1).repeatForever(), value: flag) .onAppear() { flag.toggle() } } }

Slide 36

Slide 36 text

όοΫϙʔτΛ࣮૷ / geometryGroup struct ContentView: View { @State var flag = false @State var flag2 = false var body: some View { Color.cyan .frame(width: 128, height: 128) .overlay { Group { if flag2 { Text("iOSDC") } else { Text("લ໷ࡇ") } } .task(id: flag2) { try? await Task.sleep(for: .seconds(1.5)) flag2.toggle() } } .geometryGroup() .offset(y: flag ? 100 : -100) .animation(.linear(duration: 1).repeatForever(), value: flag) .onAppear() { flag.toggle() } } }

Slide 37

Slide 37 text

όοΫϙʔτΛ࣮૷ / geometryGroup • geometryGroup͸iOS 17Ҏ߱ 😇 • ͨͩ͠ɺ࣮͸ผͷॻ͖ํͰಉ༷ͳޮՌ͕ಘΒΕΔ extension Backport where Content: View { @ViewBuilder func geometryGroup() -> some View { if #available(iOS 17, *) { content.geometryGroup() } else { content.transformEffect(.identity) } } } // ... } .backport.geometryGroup() .offset(y: flag ? 100 : -100) // ...

Slide 38

Slide 38 text

·ͱΊ • Apple७ਖ਼ͷόοΫϙʔτ͕ҙ֎ͱ͋Δ • OSSͷόοΫϙʔτ΋๛෋ • ࣗલͰ࣮૷Ͱ͖ͦ͏ͳػೳͳΒɺόοΫϙʔτʹ௅ઓ͢Δͱ͍͍Α

Slide 39

Slide 39 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠