Post

Replies

Boosts

Views

Activity

Can SwiftData Registered Models Be Equal Values and Different References?
Hi! I'm experimenting with SwiftData and looking for a situation where one persistentModelID might result in more than one registered model object reference delivered from a fetch from a single context. Here is an example of what I have to experiment: import Foundation import SwiftData @Model class Person { var name: String init(name: String) { self.name = name } } func main() { let configuration = ModelConfiguration( isStoredInMemoryOnly: true, allowsSave: true ) do { let container = try ModelContainer( for: Person.self, configurations: configuration ) let context = ModelContext(container) let person = Person(name: "John Appleseed") context.insert(person) let persistentModelID = person.persistentModelID if let left: Person = context.registeredModel(for: persistentModelID), let right: Person = context.registeredModel(for: persistentModelID) { print(left === right) // true } let descriptor = FetchDescriptor<Person>( predicate: #Predicate { person in person.persistentModelID == persistentModelID } ) if let left = try context.fetch(descriptor).last, let right = try context.fetch(descriptor).last { print(left === right) // true } } catch { print(error) } } main() This is a very simple command line app that attempts to fetch "two different" registered models… but both approaches (querying directly for persistentModelID and wrapping persistentModelID with FetchDescriptor) seem to consistently deliver objects equal by reference. Is there any situation where I could set this code up to deliver two registered models different by reference (but equal by value)? Is this anything I have to think about or manage at an "app" level? Is this behavior documented anywhere? Thanks!
0
0
840
Jan ’24
SwiftUI.View Compiler Errors when Property Wrapper is Annotated with MainActor
Hi! I'm seeing some confusing behavior with a propertyWrapper that tries to constrain its wrappedValue to MainActor. I'm using this in a SwiftUI.View… but I'm seeing some confusing behavior when I try to add that component to my graph. There seems to be some specific problem when body is defined in an extension. I start with a simple property wrapper: @propertyWrapper struct Wrapper<T> { @MainActor var wrappedValue: T } I then try a simple App with a View that uses a Wrapper: @main struct MainActorDemoApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } This code compiles with no problems for me. For style… I might choose to define the body property of my MainActorDemoApp with an extension: @main struct MainActorDemoApp: App { // var body: some Scene { // WindowGroup { // ContentView() // } // } } extension MainActorDemoApp { var body: some Scene { WindowGroup { ContentView() // Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } Explicitly marking my body as a MainActor fixes the compiler error: @main struct MainActorDemoApp: App { // var body: some Scene { // WindowGroup { // ContentView() // } // } } extension MainActorDemoApp { @MainActor var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } So I guess the question is… why? Why would code that breaks when my body is in an extension not break when my body is in my original struct definition? Is this intended behavior? I'm on Xcode Version 15.2 (15C500b) and Swift 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5). It's unclear to me what is "wrong" about the code that broke… any ideas?
3
0
877
Jan ’24
RandomAccessCollection Performance Problems when List is paired with NavigationStack
https://gist.github.com/vanvoorden/1c7c6ed08898de7f4b8619147537c0eb Hi! Has anyone here seen weird performance problems when passing values from a Collections.OrderedDictionary to a SwiftUI.List inside SwiftUI.NavigationStack? I see some performance problems that go away when switching back to SwiftUI.NavigationView. At this point, I'm not sure if it's a problem in the OrderedDictionary implementation or not. Anyone want to take a look and see if you can help track this down? For a simple backing store to drive a simple SwiftUI app displaying a List of data, the OrderedDictionary (from swift-collections) has the advantage of giving us the option to access (by an arbitrary key) in constant time. The OrderedDictionary exposes a values property that conforms to RandomAccessCollection. Since we can pass a RandomAccessCollection as data to generate a List, we should hope to be able to pass the OrderedDictionary.values the same way we would pass an Array. This is leading to some strange performance problems that seem to happen when our List is contained in a NavigationStack (and we push and pop a detail view on the stack). Here is a simple example: import Collections import SwiftUI let NavigationDemoRange = 0...999999 extension Int: Identifiable { public var id: Self { return self } } @main struct NavigationDemoApp: App { let dictionary = OrderedDictionary( uniqueKeys: NavigationDemoRange, values: NavigationDemoRange ) } extension NavigationDemoApp { var body: some Scene { WindowGroup { NavigationStack { List( self.dictionary.values, rowContent: { element in NavigationLink( String(element), value: element ) } ).navigationDestination( for: Int.self, destination: { element in Text( String(element) ) } ) } } } } In this example, we start by defining a NavigationDemoRange that we use to generate 1MM data elements. Our data element is a simple Int, which we mark as Identifiable to make it easier to pass to a List. We then construct a simple OrderedDictionary instance, where every key simply maps to itself (and the values property preserves the intended ordering). We then create (and present) a NavigationStack that wraps a List that displays every element from our OrderedDictionary.values. We add a simple NavigationLink (and pair with a navigationDestination) to display a "detail view" (which is just a Text element to display the Int). When we launch this app (on simulator or device), we see a significant hang when tapping an item from the list (pushing a detail view). Popping back appears not to hang, but then pushing another item hangs again. When we inspect instruments to troubleshoot this hang, we see that our app is spending a lot of time testing our OrderedDictionary.values for equality: static OrderedDictionary.Values<>.== infix(_:_:) Adding a breakpoint here and running our app shows that this equality check happens 7 times when the app is launched, 5 times when the detail is pushed, and 5 times when the detail is popped. That is 17 total equality checks to display a list, push a detail, and pop back all driven from data that we defined as immutable (a let constant). If we try and implement our app on the alternative NavigationLink API (before the introduction of navigationDestination), we see different results. Now, our app launches and we see a significant hang on both pushing and popping a detail view. Instruments confirms we are still spending a lot of the on equality checks. Adding a breakpoint on the check shows us this equality check happens 1 time when the app launches, 24 times when the detail view is pushed, and 12 times when the app is popped. That is 37 total equality checks against data that is (still) an immutable let constant. We set our NavigationDemoRange to 1MM elements to exaggerate performance problems, but for any amount of data we still see the equality checks occurring the same amount of times. The OrderedDictionary.values type exposes an elements property that wraps the values with an Array. When we pass OrderedDictionary.values.elements to our List, our performance problems go away: extension NavigationDemoApp { var body: some Scene { WindowGroup { NavigationStack { List( self.dictionary.values.elements, rowContent: { element in NavigationLink( String(element) ) { Text( String(element) ) } } ) } } } } FWIW, we see the exact same performance problems (number of equality checks) when passing in Collections.OrderedSet (and the problems go away when we pass the OrderedSet.elements property). That was NavigationStack. What happens if we try this same test with NavigationView? extension NavigationDemoApp { var body: some Scene { WindowGroup { NavigationView { List( self.dictionary.values, rowContent: { element in NavigationLink( String(element) ) { Text( String(element) ) } } ) } } } } This looks much faster with no noticeable performance problems. Setting a breakpoint confirms that equality is checked once on app launch. Pushing and popping perform no equality checks. Here is what we know (so far): OrderedDictionary.Values performs poorly when List is paired with NavigationStack. OrderedDictionary.Values performs well when List is paired with NavigationView. Array performs well when List is paired with NavigationStack or NavigationView. What could be causing these performance problems? It's not clear. Most of the sample code from Apple passes an Array when constructing a List, but the List constructor just takes an arbitrary RandomAccessCollection without telling us there are performance implications from rolling our own RandomAccessCollection outside the system Swift types. Is there some kind of bug in the implementation of OrderedDictionary.Values (and OrderedSet) that would lead to this performance problem? If that was true, why would that performance problem only seem to show up in NavigationStack (and not NavigationView)? If it's not safe to pass arbitrary RandomAccessCollection types to List, should we always wrap any collection in an Array before constructing a List? Any more thoughts about this? Any ideas where the performance problem could be caused from? These have all been tested on 17.1 iPhone simulator and device. The swift-collections version is 1.0.5 Thanks!
3
0
804
Nov ’23
Documentation or Resources to Measure and Optimize SwiftUI Performance?
Does anyone have some resources to recommend for learning more about measuring and optimizing SwiftUI performance and rendering? I haven't seen a ton from Apple that looks like it's going to help me more. I did find two sessions from WWDC 2023. The "Demystify SwiftUI performance" session[1] does tell us the _printChanges function can help tell us when component bodies are computed. I see how I can attach this to a component that I built… I'm not (yet) sure if I have the ability to run this on arbitrary system components in my component graph. The "Analyze hangs with Instruments" session[2] does walk us through using the SwiftUI Instruments to measure body computations. Does anyone have any more resources along these lines for learning more? SwiftUI is closed source… so we don't really have easy visibility inside to see for ourselves. Bummer. Are there any additional debug APIs (like _printChanges) that can help for local development? I'm also interested to learn more about view equality[3]… I found this essay but it's four years old and I still can't find more information directly from Apple about EquatableView… I would prefer to find any more documentation or resources directly from Apple… but I would be happy with free third-party resources as long as they have some legit data to back up their code. Thanks! [1] https://developer.apple.com/videos/play/wwdc2023/10160/ [2] https://developer.apple.com/videos/play/wwdc2023/10248/ [3] https://swiftui-lab.com/equatableview/
0
0
465
Nov ’23
CF_RETURNS_RETAINED on function pointer return values?
https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/working_with_core_foundation_types If we define a C function that returns a CFType, Swift can (sometimes) bridge the memory management semantics. If Swift cannot bridge the memory management semantics, we can end up with an Unmanaged type: CFStringRef StringByAddingTwoStrings(CFStringRef s1, CFStringRef s2); bridges to: public func StringByAddingTwoStrings(_ s1: CFString!, _ s2: CFString!) -> Unmanaged<CFString>! We can improve the bridging with the CF_RETURNS_RETAINED annotation: CFStringRef StringByAddingTwoStrings(CFStringRef s1, CFStringRef s2) CF_RETURNS_RETAINED; now bridges to: public func StringByAddingTwoStrings(_ s1: CFString!, _ s2: CFString!) -> CFString! What does not seem to be documented is how to improve bridging when function pointer return values should be annotated. For example: typedef CFStringRef (*StringByAddingTwoStrings)(CFStringRef, CFStringRef); bridges to: public typealias StringByAddingTwoStrings = @convention(c) (CFString?, CFString?) -> Unmanaged<CFString>? Is there any way to annotate the function pointer to properly communicate the memory management semantics? Adding the CF_RETURNS_RETAINED to the function pointer does not seem to help.
0
0
1.1k
Oct ’21
ARSCNView anchor(for:) and node(for:) performance
Does anyone know the runtime performance I can expect from the anchor(for:) and node(for:) methods on ARSCNView? I don't see this documented. Would it be safe for me to assume this is a constant-time lookup? Would I need to assume this is a linear-time operation that scales up with the number of nodes and anchors active? Is it my responsibility to do my own bookkeeping if I want to look these relationships up in constant-time?
0
0
693
May ’21