Skip to content

Instantly share code, notes, and snippets.

@lzell
Created March 21, 2024 00:59
Show Gist options
  • Save lzell/bb33b50d67b00990e85e4ee66d081817 to your computer and use it in GitHub Desktop.
Save lzell/bb33b50d67b00990e85e4ee66d081817 to your computer and use it in GitHub Desktop.
modern_wwd_crash_course_notes.txt
## WWDC notes
2019 Introducing SwiftUI
- Views are structs, value types, passed by value, allocated on the stack.
- If you have something like `let room: Room` as a property, and Room is a
struct, then the surrounding struct has "the size and weight of a room, no
additional allocation or reference counting overhead"
- 17:30
"make liberal use of small, single purpose views in SwiftUI"
"views in SwiftUI are incredibly lightweight"
"You should never hesitate to refactor your SwiftUI code, because extracing a subview has almost no runtime overhead"
- Example of `transition` for animation at 36:20
- What I have been calling `Data`, e.g. `RoomData`, this session calls `Store`,
e.g. `RoomStore`. It's an ObservableObject conformer in this session but is
an @Observable today
- Example of swipe to delete at 43 min
- Use `Group` within SwiftUI preview to see multiple versions of the view 47:30
2019 SwiftUI Essentials
- Decompose into smaller views. Views are low cost abstractions. Do not be afraid to create many small views and compose them.
- Let Apple handle the design, just worry about getting controls in place
- View hierarchy is composed into more efficient view (i.e. we don't have to
worry about using many views like we did with UIKit)
2019 Building custom views with SwiftUI
- Example of layout priority at 17:30
- Instead of using alignment: .bottom in my HStacks, I should be using alignment: .lastTextBaseline (at 19:45)
- Custom alignment when trying to align things in different parts of the view hierarchy 22:30
2019 Data flow through swiftUI:
- Use single source of data truth for views and bring that source up to the top of
the hierarchy, let descendents hold reference to it
- Do not modify view hierarchy directly (e.g. by adding subviews). Instead,
modify the source of truth state and let the view derive itself from that
state.
- When decomposing a view into multiple views, do not create more state (this
would create a second source of truth). Use @binding instead. A @binding adds
a dependency on state, but doesn't own the state. Pass the binding from a
parent view using the $ syntax, e.g. $isPlaying
- I notice that SwiftUI is coupled to Combine for external data in this talk. I
don't think Apple stuck with this, though
- Is @ObjectBinding still a thing? I think it's ObservableObject now
- Use EnvironmentObject instead of ObjectBinding when I don't want to pass a
dependency down through many layers of view. It seems to me that this is
recipe for poor architecture. I will start with explicitly passing deps
unless it's something like 'themeColor' that should be shared on all views.
2019 Modern Swift API Design
- Prefer structs over classes so that I get value semantics (don't need to
perform defensive deep copy in case someone changes my referenced object out
from under me)
- Important: If a struct holds a reference type as a property, copies of that
struct hold copies of the reference! This can lead to really strange API
usage. See minute 9:45 and isKnownUniquelyReferenced at minute 12.
- Avoid "type zoology" of protocols (I think he means taxonomy or classification). Try extending existing protocols first
- Motivating property wrappers starts at 23 min
- Property Wrappers in SwiftUI at 35:45
2023 Discover observation in SwiftUI
- 1:55 "You can use observable types to power your SwiftUI views"
2022 Eliminate data races using swift concurrency
2022 Visualize and optimize swift concurrency
2021 Swift concurrency code along | Swift concurrency: update a sample app
- Speaker is Ben Cohen, the best wwdc presenter
- Ben differentiates between a 'UI model' and a data model, saying that a
UI model may be a projection or subset of the data model. The UI model the @Observable that backs the SwiftUI view.
- The goal is for the concurrency architecture to be "as clear and easy to
describe as the type architecture". I like the sound of that.
- One confusion I've often had with swift is whether reading a property is safe in a
multithreaded context. The answer is no (minute 5:30). It's unclear to me whether he means
you're dealing with stale data or, more seriously, if a crash can occur when
reading on one thread while another thread is writing (Update: a crash can
occur as long as one access is a read and the other is a write, I've
reproduced this in ~/dev/CrashOnSharedAccess. It relies on complex types. I
have not been able to force a crash when reading/writing a value type
concurrently. I have also not been able to reproduce a case where two
simultaneous reads cause a crash. I believe that is safe.)
- The concurrency changes are motivated by Swift's desire for local reasoning (which value types also facilitate)
- Tip: When calling a function or method that expects a completion handler, I should change the API to use `await` at the call site.
- Can't call async function from within a sync function. "Asynchronous
functions have a capability that synchronous functions don't have: the
ability to give up control of the thread they're running on while they await.
To do this, they have a separate way of handling their stack frame, and this isn't
compatible with synchronous functions"
- Introduces Task at 11:15. "This async task is very similar to calling async on the global dispatch queue". <-- This is misleading, see my stackoverflow question.
- ** Use cmd-shift-a to bring up action menu and "add async alternative" to refactor functions with completions in function signature. Minute 13
- Using async functions is only half the story. It does not prevent race conditions or add any thread safety. Actors complete the picture.
- discardableResult at 18:50
- Continuations motivated at 19:40. Use this strategy when refactoring a function that has nested completions. Continuation syntax at 21:30
- withCheckedThrowingContinuation used at 21:40
- First use of actors at 24 min
- "Reference to captured var X in concurrently-executing" error at 25:50
- Using await to call a synchronous function that's annotated with MainActor (or another actor) suspends the calling function while the context switch is made 30:30
- There is a case where I stillw ant to group calls in MainActor.run even if
functions are annotated with @MainActor: if I want to prevent suspensions in
between successive calls (31:46)
- Order of refactor: first create async methods while maintaining existing
shims for legacy calls, then introduce actors. Otherwise easy to get
overwhelmed in compiler errors.
- nonisolated introduced at 38:45. I don't understand when to use it though.
- Dispatch queues for specific operations within a class are a sign that I can pull out a new actor
- Everything up to an await on an actor happens atomically. There is no suspension before an `await`
- When I use an ObservableObject in SwiftUI, always annotate as MainActor (min 55, also in this SO answer: https://stackoverflow.com/a/71876683/143447)
- Minute 58: "...Remember this handler is running on the main thread, so when we create a Task that task will also be running on the main thread".
2021 Protecting mutable state with actors
- Data races occur when two threads concurrently access the same data and one
of them is a write (speaker doesn't mention anything about a crash or
corrupting data on access, but is specifically talking about unexpected
values after two concurrent threads read/write the same state)
- First try to eliminate shared mutable state. Use value types if possible.
Variables of value types mutate locally (I think he means they are not shared
on the heap and therefore can't be accessed by multiple threads).
- "let properties of value-semantic types are truly immutable, so it's safe to
access them from different concurrent tasks" - why call this out if the value
types are copied when they are passed around and mutation is local anyway?
- reminder: array and dictionary have value semantics
- 3:30 changing a value variable from `let` to `var` that is used by two tasks
is not a good solution because "the counter would be referenced by both
concurrent tasks". How can that be? The counter would be copied. Oh, need to
make the copy explicit (3:45). I'm surprised by that.
- Shared mutable state requires synchronization. Existing tools: atomics,
locks, GCD serial queues. Speaker claims these are too hard: "they require
critical discipline to use exactly correctly every single time or end up with
a data race. This is where actors come in"
- 5:00 Actor provides 'isolation' to program state. Access to actor's state is
mutually-exclusive. Strong guarantee that the compiler will check!
- actor is a reference type. Can conform to protocols. Can have methods and properties.
- "The primary distinguishing characteristic of actor types is that they
isolate the instance data from the rest of the program and ensure
synchronized access to that data"
- 6:20 "The increment method, when called, will run to completion without any other code executing on the actor"
- Speaker is using Task.detached instead of Task.
Read this answer again about Task versus Task.detached: https://stackoverflow.com/a/71577170/143447
Swift concurrency: where an innocent question leads you to a whole new
concept that you've never seen mentioned in the docs or any WWDC session, the executor
https://forums.swift.org/t/can-someone-explain-how-task-really-works-in-terms-of-threads/65483/3
- Another to read about actor confusion: https://stackoverflow.com/questions/71556293/how-can-i-avoid-that-my-swift-async-method-runs-on-the-main-thread-in-swiftui
2021 Meet Async/Await
- It's easy to omit calls to a completion handler in guard clauses. It isn't a compilation error to enforce that callbacks are called.
With async/await, Swift can check our work
- One thing I'm realizing at minute 11:30 is this is far different from using a dispatch queue and then performing a sync operation in the context of that queue.
Such an approach does keep work off a main thread, but it blocks the execution thread for the time that the sync call takes
(I'm thinking of Data#contentsOfURL, for example).
In the async/await case, the thread is free to do other work when an await is encountered.
The terminology that the speaker uses is that a function is 'suspended' when it encounters an await.
The thread that is executing a function that suspends is unblocked to do other work.
Nit: "Just because you see an await, it doesn't mean that the function will actually suspend there" min 17:45
- async/await makes us enforce tighter contracts. The compiler can check the entire flow (all paths) of a function.
- read-only properties and initializers can also be async (13:05)
- Once suspended, "the function may resume onto an entirely different thread"! 19:50 (In my testing, this happens often)
- Careful: At all suspension points (i.e. any await) execution may resume in a wildy different app state
- Error message "used in a context that does not support concurrency" 22:54
- Introducting Task at min 23. "An async task packages up the work in the
closure and sends it to the system for immediate execution on the next
available thread, like the async function on a global dispatch queue". <--
This is at odds with minute 58 of 'Swift concurrency code along'
Asked here: https://stackoverflow.com/questions/77687526/understanding-task-behavior-in-swift
2021 Use async/await with URLSession
- Advantages of async/await: linear, concise, native error handling
- old style demo @ 2 min: three different execution contexts
- new style @ 3 min: one execution context that suspends without blocking
- Example of cancelation at 4:50. "concurrency task is unrelated to session data task"
- "The methods we just talked about... wait for the entire response body to arrive before returning".
- For incremental, use URLSession.bytes. "returns when the response headers have been received and deliver the response body as an async sequence of bytes"
2021 Meet AsyncSequence
- 1:44 example of `for await in`
- AsyncSequence "will suspend on each element and resume when the underlying iterator produces a value or throws"
- May be zero or more values, and signifies completion by returning nil from its iterator, just like sequences
- 3:20 In a `for try await in`: "when an error occurs, that's also a point at which
the asyncsequence is at a terminal state. And after an error happens, it will
return nil for any subsequent calls to `next` on the iterator"
- Syntax, and what the compiler produces:
for quake in quakes { ... }
# =>
var iterator = quakes.makeIterator()
while let quake = iterator.next() { ... }
for await quake in quakes { ... }
# =>
var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() { ... }
- If an async sequence throws, use `for try await in`
- Can use `break` and `continue` inside the loop
- Example of cancelation at 7:25 (Use Task {})
- URLSession.shared.bytes(from: url) example at 8:56
- Implement our own AsyncSequence using AsyncStream/AsyncThrowingStream at 11 min
2021 Swift Concurrency behind the scenes
- GCD thread explosion at 8 minutes
- Switching between continuations on a single thread only has the cost of a function call 11:45
- Example usage of TaskGroup to perform many concurrent downloads at min 14
- Context required to bridge a suspension point is stored on the heap 17:20
- Cooperative thread pool will only create as many threads as there are CPU cores 20:30
- Semaphores, which I still reach for frequently, are no longer safe to use with the new concurrency model 26 min
Using them can "violate the runtime contract of forward progress for threads"
- ** Flip on the environment variable "LIBDISPATCH_COOPERATIVE_POOL_STRICT" 1
- Matrix of benefits of actors versus serial queue async/sync at min 30
- Actor hopping at min 31
- Priority inversion is solved with actors at min 36
- Main actor requires special handling where two threads can context switch rapidly back and forth.
"While hopping between actors in the cooperative pool is fast, we still need to be mindful of hops to and from the main actor".
Try to batch work to prevent hops. 38 minutes
2022 What's new in SwiftUI
- Charts
- NavigationStack at 5:50 NavigationStack { List(items) { item in NavigationLink { Detail(item) }}.navigationTitle("Foo") }
- For more control use the `navigationDestination` API at 6:50
- MacOS Window API 10:40 (TODO: use this for AspectShot)
- iOS presentationDetents API (use for action sheet) 11:50 min
- MacOS MenuBarExtra 12:50
- Form styles 14:30
- The middle section of the talk is on various new controls
- Color has a built in gradient now e.g. Color.blue.gradient 28 min
- Shadows are easy to add with `Image(systemName: "calendar").foregroundStyle(.white.shadow(.drop(radius: 1, y: 1))`
- myImage.background { RoundedRectangle(cornerRadius: 6, style: .continuous).fill(.ellipticalGradient(.blue.gradient)) } 29:47
- Grid and GridRow API 31 min
- Advanced Layout API (for use when one of the predefined layouts doesn't work for us), see "Compose custom layouts with SwiftUI"
2023 - Meet SwiftData
- Decorate models with @Model macro to transform stored properties into persisted properties
- "The model container provides the persistent backend for your model types"
- Brief usage with SwiftUI at 4:05
- Model context observe all the changes to your models and provide many of the actions to operate on them" (fetching, saving, etc.)
2023 - Build app with SwiftData
-
2022 The SwiftUI cookbook for navigation
- Watch me
2022 Bring multiple windows to your SwiftUI app
- Watch me
2020 Stacks, grids, and outlines in SwiftUI
- Watch me
2022 Compose custom layouts with SwiftUI
- Geometry reader API at 18:30
2023 Inspectors in SwiftUI: Discover the details
2021 Demystify SwiftUI
- "Identity is how SwiftUI recognizes elements as the same or distinct across multiple updates of your app"
- "Lifetime is how SwiftUI tracks the existance of views and data over time"
- "Dependencies are how SwiftUI understands when your interface when your interface needs to be updated and why"
- 3:30 The same view identity will animate across the screen if moved to a different position; a distinct view will transition with cross fade
- 5:40 SwiftUI does not use pointer identity. It uses value types to support small, single purpose components (see SwiftUI essentials)
- 7:30 scroll-to-top example using explicit `.id` modifier. But even without explicit `.id` every single view has an identifier by structural identity
- 8:45 Intro to structural identity
- Important! 10:10 "this translation is powered by a ViewBuilder, which is a type of
result builder in swift. The View protocol implicitly wraps its body property
in a ViewBuilder, which constructs a single generic view from the logic
statements in our view. The 'some View' return type of our body property is a
placeholder that represents our static composite type, hiding it away so it
doesn't clutter up our code."
2023 Demystify SwiftUI performance
2023 Explore SwiftUI Animation
- 2:15 Clunky description: "SwiftUI tracks a view's dependencies, like this `selected` @State variable.
When an event like a tap comes in, an update transaction is opened. If any of
[the view's] dependencies change, the view is invalidated. At the close of the
transaction, the framework calls `body` to produce a new value in order to
refresh the rendering."
- 5:23 just like CoreAnimation, an animatable attribute has a 'model' and 'presentation' value. SwiftUI interpolates from the presentation to the target model value.
- 5:50 built in animatable attributes are animated off the main thread
- 8:40 AnimatablePair might be handy to conform a custom view to Animatable
- 12:20 Timing curve animations: linear, easeIn, easeOut, easeInOut
- 14:30 Spring curve: smooth, snappy, bouncy
- 15:15 Higher order animations: speed, delay, repeatCount
- 17:00 Custom animations
- 20 min: Intro to transactions
- 21:57 "But how do I animate a programmatic change to a view property?"
- 22:52 SwiftUI provids an animation view modifier: `.animation(.bouncy, value: selected)`
- 24:30 A mistake that I have made where an animation modifier gets applied to a different attribute than I intended. New API to address this.
- Man they made this shit so freaking complicated. How many times does he say
"accidental animations" in the rest of this talk. Did no one think this is a
massive sign that a declarative animation API is shit.
- Read: https://sarunw.com/posts/swiftui-animation/
2021 What's new in AVFoundation
- 3:50 await for loading an AVAsset
- 8:27 load audio tracks with `try await asset.loadTracks(withMediaType: .audio)`; similar for loading duration
- Article on loading media data asynchronously: https://developer.apple.com/documentation/avfoundation/media_assets/loading_media_data_asynchronously
2022 Create a more responsive media app
- 1:46 do not use copyCGImage to generate a thumbnail. Use `image(at:)` on AVAssetImageGenerator or `image(for: [CMTime])`
- 5:00 video composition
- 8:30 AVAsset resource loader for reading custom bytes that make up media
2023 What's new in Voice processing
- 2:20 Start with AVAudioEngine, it's a higher level API than AUVoiceIO
- 4-7 min: description of ducking and related APIs
- 8 min: how to detect a muted talker
2019 What's New in AVAudioEngine
- There is an associated sample project worth checking out called `Building a signal generator`: https://developer.apple.com/documentation/avfaudio/audio_engine/building_a_signal_generator
- It's a command line tool to emit a tone with signal shape (square wave, sine, sawtooth, etc.)
- 1:00 Voice processing for VoIP apps
- 7:23 What's new in AVAudioSession
- 8:33 Apps can allow haptics and system sounds while recording with `allowHapticsAndSystemSoundsDuringRecording`
2020 Record stereo audio with AVAudioSession
- Associated sample project to 'capture stereo audio from built-in-microphones' https://developer.apple.com/documentation/avfaudio/capturing_stereo_audio_from_built-in_microphones
- I created an example of using these APIs in ~/dev/AnimationStutter
- 1:25 modern iPhones have 4+ microphones
- 1-4 minutes, motivating the new property `inputOrientation`
- 5:15 recommendations: Make audio orientation match video/UI orientation, but don't change it over the course of a recording
- 6:55 Code (the steps that I'm interested in are the same for mono)
- Use the AVAudioSession `availableInputs` property to get microphones, and then filter by `portType` to get a specific mic
- Set the `setPreferredInput` on AVAudioSession to the output from step above
- Stereo specific starts at 7:25
- 8 "If your app records video you should set the input orientation before you start recording video"
- 9 Can record audio with either AVAudioRecorder or AVAudioEngine (lower level than AVAudioRecorder)
2021 Safeguard your accounts, promotions, and content
- 5:30 ASUserDetectionStatus.likelyReal machine learning model to infer if traffic is real or scripted
- 7:56 Intro to device check API. Two bits that I can set that will outlive device reset
- 8:45 "You can also choose to set the bits per device on account creation instead,
which will ofer a robust new signal tied to verified hardware that you can
use for ratelimiting account creation in your app. After a period of time,
you can choose to reset the bits. This will facilitate reuse of the promotion
on a timeperiod that you control and could help manage situations where
devices change hands"
- 10 min: motivation of App Attest. "App Attest enables to verify that the apps
connecting to your servers are the apps that you published on the App Store,
unmodified, and running on genuine apple devices"
2021 Mitigate fraud with App Attest and DeviceCheck
- Use DeviceCheck to prevent promotions from being reused when a user reinstalls the app
- 2:09 "The bits are shared by all apps of a developer". Well, that seems entirely less useful.
- 3:05 Motivating App Attest.
"When your service receives request, it can be hard to know [unintelligible]
from your app...App Attest lets your service verify a request came from a
legitimate instance of your app by satisfying three conditions: the request
came from a genuine Apple device, running your genuine application, and that
the payload has not been tampered with."
- I can't understand this presentation. Client implementation starts at 8:25
- See the 'Validating apps that connect to your server' documentation:
https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server
- It sounds like Apple can throttle some part of App Attestation, so it should
be rolled out to the userbase gradually.
2021 Analyze HTTP traffic in Instruments
- 9 min: in Xcode go to Product > Profile. "This will build my app in the release configuration to ensure I'm profiling my app the way it would run for users with all optimizations turned on"
- Choose the Network template when Instruments opens
- The bottom track is the existing networking connections instrument. The top track is the new HTTP traffic instrument, tap on that.
- Use option-click to zoom in
- Tap the chevron disclosure indicator in the sidebar to show URLSession information
- Tap the chevron in the submenu to see http traffic by connection
- Example of "head of line blocking" at 14:20
- "Head of line blocking is one of the main limitations of http/1 and one
of the main improvements of http/2 is to avoid that effect by
multiplexing several requests to the same server on a single connection"
Questions:
- Guidance on singletons as data source?
- What happens if a property is a reference type in a swiftUI view? Any ownership/retention problems?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment