Categories
iOS Swift

Swift 6 suitable notification observers in iOS

I have a couple of side projects going on, although it is always a challenge to find time of them. One of them, SignalPath, is what I created back in 2015. Currently, I have been spending some time to bump the Swift version to 6 which brought a quite a list of errors. In many places I had code what dealt with observing multiple notifications, but of course Swift 6 was not happy about it.

let handler: (Notification) -> Void = { [weak self] notification in
self?.keyboardInfo = Info(notification: notification)
}
let names: [Notification.Name] = [
UIResponder.keyboardWillShowNotification,
UIResponder.keyboardWillHideNotification,
UIResponder.keyboardWillChangeFrameNotification
]
observers = names.map({ name -> NSObjectProtocol in
return NotificationCenter.default.addObserver(forName: name,
object: nil,
queue: .main,
using: handler)
// Converting non-sendable function value to '@Sendable (Notification) -> Void' may introduce data races
})
view raw Observer.swift hosted with ❤ by GitHub

After moving all of the notification observing to publishers instead, I can ignore the whole sendable closure problem all together.

Publishers.Merge3(
NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
)
.map(Info.init)
.assignWeakly(to: \.keyboardInfo, on: self)
.store(in: &notificationCancellables)
view raw Observer.swift hosted with ❤ by GitHub

Great, compiler is happy again although this code could cause trouble if any of the notifications are posted from a background thread. But since this is not a case here, I went for skipping .receive(on: DispatchQueue.main). Assign weakly is a custom operator and the implementation looks like this:

public extension Publisher where Self.Failure == Never {
func assignWeakly<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable where Root: AnyObject {
return sink { [weak object] value in
object?[keyPath: keyPath] = value
}
}
}

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

AnyClass protocol and Objective-C methods

AnyClass is a protocol all classes conform to and it comes with a feature I was not aware of. But first, how to I ended up with using AnyClass. While working on code using CoreData, I needed a way to enumerate all the CoreData entities and call a static function on them. If that function is defined, it runs an entity specific update. Let’s call the function static func resetState().

It is easy to get the list of entity names of the model and then turn them into AnyClass instances using the NSClassFromString() function.

let entityClasses = managedObjectModel.entities
.compactMap(\.name)
.compactMap { NSClassFromString($0) }
view raw AnyClass.swift hosted with ❤ by GitHub

At this point I had an array of AnyClass instances where some of them implemented the resetState function, some didn’t. While browsing the AnyClass documentation, I saw this:

You can use the AnyClass protocol as the concrete type for an instance of any class. When you do, all known @objcclass methods and properties are available as implicitly unwrapped optional methods and properties, respectively.

Never heard about it, probably because I have never really needed to interact with AnyClass in such way. Therefore, If I create an @objc static function then I can call it by unwrapping it with ?. Without unwrapping it safely, it would crash because Department type does not implement the function.

class Department: NSManagedObject {
}
class Employee: NSManagedObject {
@objc static func resetState() {
print("Resetting Employee")
}
}
// This triggers Employee.resetState and prints the message to the console
for entityClass in entityClasses {
entityClass.resetState?()
}
view raw AnyClass.swift hosted with ❤ by GitHub

It has been awhile since I wrote any Objective-C code, but its features leaking into Swift helped me out here. Reminds me of days filled with respondsToSelector and performSelector.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Xcode

AnyView is everywhere in Xcode 16

Loved to see this entry in Xcode 16’s release notes:

Xcode 16 brings a new execution engine for Previews that supports a larger range of projects and configurations. Now with shared build products between Build and Run and Previews, switching between the two is instant. Performance between edits in the source code is also improved for many projects, with increases up to 30%.

It has been difficult at times to use SwiftUI previews when they sometimes just stop working with error messages leaving scratch head. Turns out, it comes with a hidden cost of Xcode 16 wrapping views with AnyView in debug builds which takes away performance. If you don’t know it only affects debug builds, one could end up on journey of trying to improve the performance for debug builds and making things worse for release builds. Not sure if this was ever mentioned in any of the WWDC videos, but feels like this kind of change should have been highlighted.

As of Xcode 16, every SwiftUI view is wrapped in an AnyView _in debug builds only_. This speeds switching between previews, simulator, and device, but subverts some List optimizations.

Add this custom build setting to the project to override the new behavior:

`SWIFT_ENABLE_OPAQUE_TYPE_ERASURE=NO`

Wrapping in Equatable is likely to make performance worse as it introduces an extra view in the hierarchy for every row.

Curt Clifton on Mastodon

Fortunately, one can turn off this if this becomes an issue in debug builds.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
Foundation iOS Swift

Sorting arrays in Swift: multi-criteria

Swift’s foundation library provides a sorted(by:) function for sorting arrays. The areInIncreasingOrder closure needs to return true if the closure’s arguments are increasing, false otherwise. How to use the closure for sorting by multiple criteria? Let’s take a look at an example of sorting an array of Player structs.

  1. Sort by score in descending order
  2. Sort by name in ascending order
  3. Sort by id in ascending order
struct Player {
let id: Int
let name: String
let score: Int
}
extension Player: CustomDebugStringConvertible {
var debugDescription: String {
"id=\(id) name=\(name) score=\(score)"
}
}
let players: [Player] = [
Player(id: 0, name: "April", score: 7),
Player(id: 1, name: "Nora", score: 8),
Player(id: 2, name: "Joe", score: 5),
Player(id: 3, name: "Lisa", score: 4),
Player(id: 4, name: "Michelle", score: 6),
Player(id: 5, name: "Joe", score: 5),
Player(id: 6, name: "John", score: 7)
]
view raw Sort.swift hosted with ❤ by GitHub

As said before, the closure should return true if the left element should be ordered before the right element. If they happen to be equal, we should use the next sorting criteria. For comparing strings, we’ll go for case-insensitive sorting using Foundation’s built-in localizedCaseInsensitiveCompare.

let sorted = players.sorted { lhs, rhs in
if lhs.score == rhs.score {
let nameOrdering = lhs.name.localizedCaseInsensitiveCompare(rhs.name)
if nameOrdering == .orderedSame {
return lhs.id < rhs.id
} else {
return nameOrdering == .orderedAscending
}
} else {
return lhs.score > rhs.score
}
}
print(sorted.map(\.debugDescription).joined(separator: "\n"))
// id=1 name=Nora score=8
// id=0 name=April score=7
// id=6 name=John score=7
// id=4 name=Michelle score=6
// id=2 name=Joe score=5
// id=5 name=Joe score=5
// id=3 name=Lisa score=4
view raw Sort.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
Foundation iOS Swift

How to keep Date’s microseconds precision in Swift

DateFormatter is used for converting string representation of date and time to a Date type and visa-versa. Something to be aware of is that the conversion loses microseconds precision. This is extremely important if we use these Date values for sorting and therefore ending up with incorrect order. Let’s consider an iOS app which uses API for fetching a list of items and each of the item contains a timestamp used for sorting the list. Often, these timestamps have the ISO8601 format like 2024-09-21T10:32:32.113123Z. Foundation framework has a dedicated formatter for parsing these strings: ISO8601DateFormatter. It is simple to use:

let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let date = formatter.date(from: "2024-09-21T10:32:32.113123Z")
print(date?.timeIntervalSince1970) // 1726914752.113
view raw ISO8601.swift hosted with ❤ by GitHub

Great, but there is on caveat, it ignores microseconds. Fortunately this can be fixed by manually parsing microseconds and adding the missing precision to the converted Date value. Here is an example, how to do this using an extension.

extension ISO8601DateFormatter {
func microsecondsDate(from dateString: String) -> Date? {
guard let millisecondsDate = date(from: dateString) else { return nil }
guard let fractionIndex = dateString.lastIndex(of: ".") else { return millisecondsDate }
guard let tzIndex = dateString.lastIndex(of: "Z") else { return millisecondsDate }
guard let startIndex = dateString.index(fractionIndex, offsetBy: 4, limitedBy: tzIndex) else { return millisecondsDate }
// Pad the missing zeros at the end and cut off nanoseconds
let microsecondsString = dateString[startIndex..<tzIndex].padding(toLength: 3, withPad: "0", startingAt: 0)
guard let microseconds = TimeInterval(microsecondsString) else { return millisecondsDate }
return Date(timeIntervalSince1970: millisecondsDate.timeIntervalSince1970 + microseconds / 1_000_000.0)
}
}
view raw ISO8601.swift hosted with ❤ by GitHub

That this code does is first converting the string using the original date(from:) method, followed by manually extracting digits for microseconds by handling cases where there are less than 3 digits or event there are nanoseconds present. Lastly a new Date value is created with the microseconds precision. Here are examples of the output (note that float’s precision comes into play).

let dateStrings = [
"2024-09-21T10:32:32.113Z",
"2024-09-21T10:32:32.1131Z",
"2024-09-21T10:32:32.11312Z",
"2024-09-21T10:32:32.113123Z",
"2024-09-21T10:32:32.1131234Z",
"2024-09-21T10:32:32.11312345Z",
"2024-09-21T10:32:32.113123456Z"
]
let dates = dateStrings.compactMap(formatter.microsecondsDate(from:))
for (string, date) in zip(dateStrings, dates) {
print(string, "->", date.timeIntervalSince1970)
}
/*
2024-09-21T10:32:32.113Z -> 1726914752.113
2024-09-21T10:32:32.1131Z -> 1726914752.1130998
2024-09-21T10:32:32.11312Z -> 1726914752.1131198
2024-09-21T10:32:32.113123Z -> 1726914752.113123
2024-09-21T10:32:32.1131234Z -> 1726914752.113123
2024-09-21T10:32:32.11312345Z -> 1726914752.113123
2024-09-21T10:32:32.113123456Z -> 1726914752.113123
*/
view raw ISO8601.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift SwiftUI

Cancellable withObservationTracking in Swift

Observation framework came out along with iOS 17 in 2023. Using this framework, we can make objects observable very easily. Please refer to @Observable macro in SwiftUI for quick recap if needed. It also has a function withObservationTracking(_:onChange:) what can be used for cases where we would want to manually get a callback when a tracked property is about to change. This function works as a one shot function and the onChange closure is called only once. Note that it is called before the value has actually changed. If we want to get the changed value, we would need to read the value on the next run loop cycle. It would be much more useful if we could use this function in a way where we could have an observation token and as long as it is set, the observation is active. Here is the function with cancellation support.

func withObservationTracking(
_ apply: @escaping () -> Void,
token: @escaping () -> String?,
willChange: (@Sendable () -> Void)? = nil,
didChange: @escaping @Sendable () -> Void
) {
withObservationTracking(apply) {
guard token() != nil else { return }
willChange?()
RunLoop.current.perform {
didChange()
withObservationTracking(
apply,
token: token,
willChange: willChange,
didChange: didChange
)
}
}
}

The apply closure drives which values are being tracked, and this is passed into the existing withObservationTracking(_:onChange:) function. The token closure controls if the change should be handled and if we need to continue tracking. Will and did change are closures called before and after the value has changed.

Here is a simple example where we have a view which controls if the observation should be active or not. Changing the value in the view model only triggers the print lines when observation token is set.

struct ContentView: View {
@State private var viewModel = ViewModel()
@State private var observationToken: String?
var body: some View {
VStack {
Text(viewModel.title)
Button("Add") {
viewModel.add()
}
Button("Start Observing") {
guard observationToken == nil else { return }
observationToken = UUID().uuidString
observeAndPrint()
}
Button("Stop Observing") {
observationToken = nil
}
}
.padding()
}
func observeAndPrint() {
withObservationTracking({
_ = viewModel.title
}, token: {
observationToken
}, willChange: { [weak viewModel] in
guard let viewModel else { return }
print("will change \(viewModel.title)")
}, didChange: { [weak viewModel] in
guard let viewModel else { return }
print("did change \(viewModel.title)")
})
}
}
@Observable final class ViewModel {
var counter = 0
func add() {
counter += 1
}
var title: String {
"Number of items: \(counter)"
}
}

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
Foundation iOS Swift

Referencing itself in a struct in Swift

It took a long time, I mean years, but it finally happened. I stumbled on a struct which had a property of the same type.

struct Message {
let id: Int
// This is OK:
let replies: [Message]
// This is not OK
// Value type 'Message' cannot have a stored property that recursively contains it
let parent: Message?
}
view raw Struct.swift hosted with ❤ by GitHub

At first, it is kind of interesting that the replies property compiles fine, although it is a collection of the same type. I guess it is so because array’s storage type is a reference type.

The simplest workaround is to use a closure for capturing the actual value.

struct Message {
let id: Int
let replies: [Message]
private let parentClosure: () -> Message?
var parent: Message? { parentClosure() }
}
view raw Struct2.swift hosted with ❤ by GitHub

Or we could go for using a boxed wrapper type.

struct Message {
let id: Int
let replies: [Message]
private let parentBoxed: Boxed<Message>?
var parent: Message? { parentBoxed?.value}
}
class Boxed<T> {
let value: T
init(value: T) {
self.value = value
}
}
view raw Struct3.swift hosted with ❤ by GitHub

Or if we prefer property wrappers, using that instead.

struct Message {
let id: Int
let replies: [Message]
@Boxed var parent: Message?
}
@propertyWrapper
class Boxed<Value> {
var value: Value
init(wrappedValue: Value) {
value = wrappedValue
}
var wrappedValue: Value {
get { value }
set { value = newValue }
}
}
view raw Struct4.swift hosted with ❤ by GitHub

Then there are also options like changing the struct into class instead, but that is something to consider. Or finally, creating a

All in all, it is fascinating how something simple like this actually has a pretty complex background.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift SwiftUI

Zoom navigation transition in SwiftUI

WWDC’24 brought a lot of new and SwiftUI received many updates. One of which, is the new NavigationTransition protocol and the zoom transition. And that is pretty much what it contains at the moment. A built-in way to add a zoom transition to the view. Easy to get going, but does not provide customization, at least for now.

Here is an example of a grid view which opens a DetailView with zoom transition. Here we can see that we need to add navigationTransition view modifier to the destination view and matchedTransitionSource view modifier to the view it starts the transition from.

struct ContentView: View {
let colors: [[Color]] = [
[.red, .blue, .green],
[.yellow, .purple, .brown],
[.cyan, .gray]
]
@Namespace() var namespace
var body: some View {
NavigationStack {
Grid(horizontalSpacing: 50, verticalSpacing: 50) {
ForEach(colors, id: \.hashValue) { rowColors in
GridRow {
ForEach(rowColors, id: \.self) { color in
NavigationLink {
DetailView()
.navigationTransition(
.zoom(
sourceID: color,
in: namespace
)
)
} label: {
RoundedRectangle(cornerRadius: 5)
.foregroundStyle(color)
.frame(width: 48, height: 48)
}
.matchedTransitionSource(id: color, in: namespace)
}
}
}
}
}
}
}
view raw Zoom.swift hosted with ❤ by GitHub

Just a few lines of code and we have a nice zoom transition in place.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

Task with @MainActor gotcha in Swift

While working on a codebase with a mix of structured concurrency and good old DispatchQueues I stumbled on something. Most of the time this would not be a problem, but in my case, it was important that events would happen in order. Let’s jump to an example for illustrating what I am talking about.

Firstly, let’s add an extension to Task which adds a static function for running operations on the main actor. Similar to the existing Task.detached. This made sense since I needed to push the Task to main actor in multiple places.

extension Task {
@discardableResult static func main(
operation: @escaping () async throws -> Success
) -> Task<Success, Failure> where Failure == any Error {
Task { @MainActor in
print(Thread.isMainThread) // logging purposes
return try await operation()
}
}
}

This extension was used in a class which observes state using non-async await code. If the state changed, it would trigger a closure.

final class Observer {
private var didChange: ((Contact) async throws -> Void)?
func start(didChange: @escaping (Contact) async -> Void) {
self.didChange = didChange
}
func simulateChange() {
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { [self] in
let changed = Contact(name: "Toomas \((1…5).randomElement()!)")
Task.main {
print(Thread.isMainThread) // logging purposes
try await self.didChange?(changed)
}
}
}
}
view raw Observer.swift hosted with ❤ by GitHub

The idea is that when the state changes, we jump to the main actor from the beginning, since it will always end up updating state in a @MainActor annotated class. In this example, a view model.

@MainActor final class ViewModel: ObservableObject {
@Published private(set) var contact: Contact?
let observer: Observer
init() {
observer = Observer()
observer.start(didChange: { [weak self] contact in
print(Thread.isMainThread) // logging purposes
self?.contact = contact
})
}
func refresh() {
observer.simulateChange()
}
}
view raw ViewModel.swift hosted with ❤ by GitHub

Looks great, we use Task.main which calls the operation on the main actor which in turn sets the updated state to a main actor guarded view model. If I run the code and observe isMainThread print statements, then it prints: true, false, true. Here is where the gotcha is. I expected it to be true, true, true since I made sure Task uses @MainActor and also the view model is @MainActor. Here is the change I needed to do for getting the expected true, true, true in console log.

extension Task {
@discardableResult static func main(
// note the @MainActor
operation: @escaping @MainActor () async throws -> Success
) -> Task<Success, Failure> where Failure == any Error {
Task { @MainActor in
print(Thread.isMainThread) // logging purposes
return try await operation()
}
}
}

I was missing the @MainActor from the closure’s definition. What happened was that task was forced to main actor, but the operation closure did not specify any isolation, and Swift happily switched to the global executor instead.

If I rethink about what happened, then most of the time this would not be a problem, but in my specific case the timing was important. It was important that when we jump to the main actor, then no other code path should not change the state in the view model.

All in all, this is just something to keep in mind when mixing structured concurrency with DispatchQueues.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Categories
iOS Swift

Initializing @MainActor type from a non-isolated context in Swift

Recently I was in the middle of working on code where I wanted a type to require @MainActor since the type was an ObservaleObject and makes sense if it always publishes changes on the MainActor. The MainActor type needed to be created by another type which is not a MainActor. How to do it?

This does not work by default, since we are creating the MainActor type from a non-isolated context.

final class ViewPresenter {
init(dataObserver: DataObserver) {
// Call to main actor-isolated initializer 'init(dataObserver:)' in a synchronous nonisolated context
self.viewState = ViewState(dataObserver: dataObserver)
}
let viewState: ViewState
}
@MainActor final class ViewState: ObservableObject {
let dataObserver: DataObserver
init(dataObserver: DataObserver) {
self.dataObserver = dataObserver
}
@Published private(set) var status: Status = .loading
@Published private(set) var contacts: [Contact] = []
}
view raw ViewState.swift hosted with ❤ by GitHub

OK, this does not work. But since the ViewState has a simple init then why not slap nonisolated on the init and therefore not requiring the init to be called on a MainActor. This leads to a warning: “Main actor-isolated property ‘dataObserver’ can not be mutated from a non-isolated context; this is an error in Swift 6”. After digging in Swift forums to understand the error, I learned that as soon as init assigns the dataObserver instance to the MainActor guarded property, then compiler considers that the type is owned by the MainActor now. Since init is nonisolated, compiler can’t ensure that the assigned instance is not mutated by the non-isolated context.

final class ViewPresenter {
init(dataObserver: DataObserver) {
self.viewState = ViewState(dataObserver: dataObserver)
}
let viewState: ViewState
}
@MainActor final class ViewState: ObservableObject {
let dataObserver: DataObserver
nonisolated init(dataObserver: DataObserver) {
// Main actor-isolated property 'dataObserver' can not be mutated
// from a non-isolated context; this is an error in Swift 6
self.dataObserver = dataObserver
}
@Published private(set) var status: Status = .loading
@Published private(set) var contacts: [Contact] = []
}
view raw ViewState.swift hosted with ❤ by GitHub

This warning can be fixed by making the DataObserver type to conform to Sendable protocol which tells the compiler that it is OK, if the instance is mutated from different contexts (of course we need to ensure that the type really is thread-safe before adding the conformance). In this particular case, making the type Sendable was not possible, and I really did not want to go to the land of @unchecked Sendable, so I continued my research. Moreover, having nonisolated init looked like something what does not look right anyway.

Finally, I realized that since the ViewState is @MainActor, then I could make the viewState property @MainActor as well and delay creating the instance until the property is accessed. Makes sense since if I want to access the ViewState and interact with it then I need to be on the MainActor anyway. If the property is lazy var and created using a closure, then we achieve what we want: force the instance creation to MainActor. Probably, code speaks itself more clearly.

final class ViewPresenter {
private let viewStateBuilder: @MainActor () -> ViewState
init(dataObserver: DataObserver) {
self.viewStateBuilder = { ViewState(dataObserver: dataObserver) }
}
@MainActor lazy var viewState: ViewState = viewStateBuilder()
}
@MainActor final class ViewState: ObservableObject {
let dataObserver: DataObserver
init(dataObserver: DataObserver) {
self.dataObserver = dataObserver
}
@Published private(set) var status: Status = .loading
@Published private(set) var contacts: [Contact] = []
}
view raw ViewState.swift hosted with ❤ by GitHub

What I like is that I can keep one of the types fully @MainActor and still manage the creation from a non-isolated context. The downside is having lazy var and handling the closure.

If you want to try my apps, then grab one of the free offer codes for Silky Brew.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.