Swift / Apple Development Chat

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
What I noticed in playgrounds is that the first Task in the example will just run as early as possible, suggesting concurrent execution. While the second task waits for the isolated function to suspend so it can have access to the actor. I tested this by using print statements and was able to see how behavior become rigidly deterministic when the Task picked up the isolation implicitly via referencing an isolated property. But if the actor’s isolation isn’t required for the Task, it seems to not get it and will run concurrently, making it less deterministic on exactly when it will run as it runs on the global executor’s thread pool.
If you start the first Task with Task(qos: .background) and the second Task with Task(qos: .userInitiated), the second task will actually start first. And spraying some print(Thread.isMainThread) around the code seems to indicate that everything is running in the main thread at all times. If I understood the proposal, this shouldn't be guaranteed to happen, right? The first task may execute somewhere else, since it's not isolated.

You are better off buying an aftermarket CarPlay display and using that as a test harness in your work office.
I remember Overcast's dev on Twitter saying exactly that.
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
If you start the first Task with Task(qos: .background) and the second Task with Task(qos: .userInitiated), the second task will actually start first. And spraying some print(Thread.isMainThread) around the code seems to indicate that everything is running in the main thread at all times. If I understood the proposal, this shouldn't be guaranteed to happen, right? The first task may execute somewhere else, since it's not isolated.

I read a little more on this, and it looks like MainActor itself doesn’t always behave expectedly, and that Xcode 13 updates have included fixes here. Oof, that’s not good. And in my case, I was testing with an actor, rather than a global actor like MainActor. Double oof. That said, my Playground was able to demonstrate concurrent code executing not only non-deterministically, but also at a point where the caller was not suspended, but it’s likely my example should really be an actor example, not MainActor.

I will generally warn though that what I do know is that Swift attempts to be lazy about thread hops, and the compiler does seem to try to optimize context switches where it can, so we should try to ensure any tests we perform aren’t so simple that they can’t simply be inlined away.

I’ll take your point though. There’s an awful lot of things interacting here, and it’s not documented well enough to know exactly how it will behave without discussing a particular, very specific, example. It doesn’t help that the compiler is now a member of the party and trying to make sense of the dependencies on our behalf to make smarter decisions on what sort of code to emit.

I remember Overcast's dev on Twitter saying exactly that.

I assume you might be referring to this tweet? https://www.twitter.com/i/web/status/1433156056083992576/

Yeah, that’s pretty much what I’m doing now, just a different unit.
 

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
I will generally warn though that what I do know is that Swift attempts to be lazy about thread hops, and the compiler does seem to try to optimize context switches where it can, so we should try to ensure any tests we perform aren’t so simple that they can’t simply be inlined away.
That's an interesting point. I wonder if the compiler will simply inline very short tasks. My guess is that it won't, and that scheduling is always done dynamically if you manually call Task (even if it's executed serially after that). On the other hand, if the task is something like a one-liner variable change, the compiler should have enough information to know that it's better to just inline it. Hmm.

I assume you might be referring to this tweet?

Yeah, that’s pretty much what I’m doing now, just a different unit.
I can't remember if that was the tweet, but definitely something he tweeted around that time.
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
That's an interesting point. I wonder if the compiler will simply inline very short tasks. My guess is that it won't, and that scheduling is always done dynamically if you manually call Task (even if it's executed serially after that). On the other hand, if the task is something like a one-liner variable change, the compiler should have enough information to know that it's better to just inline it. Hmm.

This is what I get for testing one thing and assuming it is the other. So it looks like global actors are more aggressive than non-global actors, and so all child tasks of a global actor will also require that global actor implicitly, unless you detach the task. Yet my tests show that non-global actors will pick up on isolation based on if isolated properties are accessed or not. This kinda makes sense… a global actor doesn’t have any particular state to isolate, so it’s the task itself that is isolated.

I’ve attached some code you can run in a playground that demonstrates what I mean. For both the global actor and main actor, the child task cannot run until the current task suspends, which is when Task.sleep() is called. Since this is a true suspension point, you will see doAThing() continue to execute until it reaches that suspension point, which is where the child task gets a chance to run. For the non-global actor, the child task runs in parallel, and multiple runs will show different ordering of the print statements. But since you can see that in many cases, the ordering would make no sense if the code was running within the actor. But the moment you uncomment the “self.isActive = true” within the child task, it behaves the same as the global and main actors.

As for checking threads, I don’t do a ton of that unless there’s a specific issue I’m trying to track down, as Swift’s scheduler is inherently lazy. You’ll tend to stay on a thread until a suspension point is reached, but when you awake from suspend what thread you awaken on will depend on a few factors, such as if the function itself is isolated or not, and if it is isolated, is it a global actor or not. So from my experience it’s generally better to consider what actor you are on.

But as you‘ve shown me here, the implicit nature of child tasks taking on global actor isolation of the parent, and the fact that many SwiftUI and UIKit types are annotated with @MainActor means you can find yourself isolated on the MainActor without knowing it. That’s not a super-great for someone new to this, even if it makes some level of sense after thinking through it.

So ultimately, if you want to run something that needs to detach from the global actor it is a part of and run in a non-isolated context (and UI code will generally be isolated to the main actor), then you must use Task.detached manually. But this generally should be the case where you might do a lot of work before a suspend point can pass the global actor off to other work.

EDIT: Just want to add that I have really appreciated the back and forth here. It’s really helped me solidify my understanding around Swift concurrency and how to use it better.

Code:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

actor MyActor {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            // Uncomment the below to force actor isolation on this.
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

struct SomeStruct {
    static func doSomething(_ value: Int) async -> Int {
        print("hello \(value)")
        try! await Task.sleep(nanoseconds: 10000)
        return value
    }
}

@MainActor class MyMainActorClass {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

@globalActor
struct MyGlobalActor {
    actor ActorType { }
    static let shared: ActorType = ActorType()
}

@MyGlobalActor class MyGlobalActorClass {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

enum RunMode {
    case actor
    case mainActor
    case globalActor
}

let runMode: RunMode = .actor
if runMode == .actor {
    let actor = MyActor()
    Task { @MainActor in
        await actor.doAThing()
        print("Actor Task complete")
        PlaygroundPage.current.finishExecution()
    }
    print("Actor Task created")
} else if runMode == .globalActor {
    Task { @MyGlobalActor in
        let actorClass = MyGlobalActorClass()
        await actorClass.doAThing()
        print("GlobalActor Task Complete")
        PlaygroundPage.current.finishExecution()
    }
    print("GlobalActor Task Created")
} else {
    Task { @MainActor in
        let mainActorClass = MyMainActorClass()
        await mainActorClass.doAThing()
        print("MainActor Task Complete")
        PlaygroundPage.current.finishExecution()
    }
    print("MainActor Task Created")
}
 
Last edited:

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
This is what I get for testing one thing and assuming it is the other. So it looks like global actors are more aggressive than non-global actors, and so all child tasks of a global actor will also require that global actor implicitly, unless you detach the task. Yet my tests show that non-global actors will pick up on isolation based on if isolated properties are accessed or not. This kinda makes sense… a global actor doesn’t have any particular state to isolate, so it’s the task itself that is isolated.

I’ve attached some code you can run in a playground that demonstrates what I mean. For both the global actor and main actor, the child task cannot run until the current task suspends, which is when Task.sleep() is called. Since this is a true suspension point, you will see doAThing() continue to execute until it reaches that suspension point, which is where the child task gets a chance to run. For the non-global actor, the child task runs in parallel, and multiple runs will show different ordering of the print statements. But since you can see that in many cases, the ordering would make no sense if the code was running within the actor. But the moment you uncomment the “self.isActive = true” within the child task, it behaves the same as the global and main actors.
Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.

EDIT: Just want to add that I have really appreciated the back and forth here. It’s really helped me solidify my understanding around Swift concurrency and how to use it better.
Same! I learned a lot, writing about it really helps. :)
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.

Yeah, I just wish there was a little more flexibility bridging between synchronous and asynchronous contexts. For example, menus (and context menus) in SwiftUI only update when becoming visible, meaning if you have to check an asynchronous API to see if something should be present, or enabled, then it will tend to happen too late. I still have to do more testing, but I’m not even sure .task() can fetch state quickly enough for menus. And annotating @MainThread will force synchronous contexts to spin up a Task to call it, even from the main thread, ensuring that the call will not actually happen until the next iteration of the run loop. So that’s great.

I’ve got code interacting with CoreData that is growing increasingly reliant on concurrency to handle thread transitions. But because of how UI operates, and still isn’t really aware of concurrency, I have to follow a weird pattern. If it’s Core Data, and synchronous, assume it takes view context objects, and operates on the view context. If it’s asynchronous, it is safe to call with any context’s objects and it will either fetch the appropriate object for its internal context (editing/sync) or invoke into the object’s context to query details from it. But just to keep things clean, objects from other contexts than the view context don’t get passed around, so the only thing that should be flying around are the objects from the view context.
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
Some interesting stuff in SwiftUI with macOS 13 and iOS 16:

- NavigationView is dead. Long live NavigationStack.
- On this topic, it looks a bit like NavigationStack might actually provide a navigation stack for macOS outside of catalyst? Need to play with it once I can install the beta.
- NavigationPath makes it easier to serialize navigation state to do things like handle iPhone/iPad size class switching, handoff, and more. Heck, the entire navigation model is a lot more sensible, and you can create a navigation router for a stack that is surprisingly VIPER like. Still needs to be embedded in a view, but you could easily create a view modifier or wrapping view that defines your routes and compartmentalize that way. Overall, navigation looks so much better now than it did last year. Sidebars might be less jank under this approach.
- Context menus can provide preview views now, just to get back to my opening post in this thread.
- Sheets now support detents, finally. Can even specify custom detents.
- Custom grid layouts will make a couple things I’ve banged my head against easier (trying to multiple VStacks properly).
- ViewThatFits to pick between different layouts based on what fits the space available. Useful for portrait vs landscape on iPad, and possibly iPhone vs iPad/Mac/AppleTV.
- A full layout engine you can seemingly take control over that is declarative.
- You can now specify background tasks without dropping to UIKit, so background refresh, network tasks and the like can be done fully in SwiftUI.
- Custom windows can now be created for specific needs, such as supplementary windows (say a diagnostics tool, or a mini player window, camera window, etc).
- There’s some new ways to declare toolbars that looks like it feeds the new iPad toolbars, but should also help with Mac toolbars.
- PhotosPicker is now available without dropping to UIKit (just after I wrote a wrapper…). Somehow it seems like this is supported on macOS 13 too? Huh?
- Some Apple Pay and Wallet integration
- Menus can now have their order defined by the developer, no more being stuck with “first closest to button” ordering when UIKit doesn’t do this.
- Rename action/button. huh.
- A couple new gesture modifiers that give you access to the gesture location, including “continuous hover”.
- ImageRenderer, a way to generate images from SwiftUI views.

And as an aside, the new AppIntents framework looks a lot like they have been watching the SwiftUI team. A declarative intents API that doesn’t rely on those external model files. Seeing @resultsBuilder trickle out into other APIs as well.
 

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
I didn't really understand NavigationPath just by having a quick look at the docs. I'd rather wait until the videos about it drop. I think a lot of people were waiting for this. I didn't hear about any changes on the Scenes API though, I hope that has changes too.

Interestingly, it looks like Apple encourages SwiftUI over AppKit rather than SwiftUI over Catalyst/UIKit as the default for Mac apps now. Hmmm. Maybe I'll make the switch.
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
I didn't really understand NavigationPath just by having a quick look at the docs. I'd rather wait until the videos about it drop. I think a lot of people were waiting for this. I didn't hear about any changes on the Scenes API though, I hope that has changes too.

NavigationPath is an odd duck in SwiftUI. It’s just a container of state, and I think one of the few concrete state containers in SwiftUI. But it handles the fact that your navigation stack‘s steps aren’t going to be all the same type. So when using NavigationLink(label:value:), which is the new recommendation now that NavigationView is deprecated, the NavigationPath will have the value pushed onto its internal stack, and account for the fact the stack values might look like: enum > struct > NSManagedObject

I’m not seeing anything on scenes, unfortunately. There’s a couple annoyances with scenes on macOS especially I wish were better. I really want to be able to know when a window isn’t the active window, but haven’t found a good way to do it without dropping to AppKit.

Interestingly, it looks like Apple encourages SwiftUI over AppKit rather than SwiftUI over Catalyst/UIKit as the default for Mac apps now. Hmmm. Maybe I'll make the switch.

Based on our previous discussions, I don’t think there‘s huge reason to go do a bunch of work if you are already living in one world. SwiftUI on AppKit doesn’t behave the same way as SwiftUI on UIKit on the Mac and will need to be accounted for. And if you need to support versions earlier than Ventura, the SwiftUI 4 changes that seem to be making things more consistent between AppKit and UIKit won’t really help you much until you can ditch Monterrey.

NavigationView is the real big one. I had to create my own navigation behaviors for macOS because of AppKit’s NavigationView doesn’t support stack based navigation. It’s not fun, and has been a source of developer pain. I’m actually rushing to get Ventura installed on a partition so I can explore the new NavigationStack and see if it actually resolves the issues I’m facing. Although I’m well aware I’m going to have some real pain ahead of me doing the work to enable dual Monterrey/Ventura behavior if I want to leverage it. *sigh*
 

MEJHarrison

Site Champ
Posts
869
Reaction score
1,700
Location
Beaverton, OR
- On this topic, it looks a bit like NavigationStack might actually provide a navigation stack for macOS outside of catalyst? Need to play with it once I can install the beta.

I wanted to play with the new Regex Builder, but you need to be on Ventura. I might just have to install Ventura on my laptop. I'm looking forward to Regex Builder. I'm old enough to have used Regex tons in my career. So taking that and making it readable is super duper cool in my book. Can't wait to play with it.

Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.
 

Cmaier

Site Master
Staff Member
Site Donor
Posts
5,209
Reaction score
8,250
I wanted to play with the new Regex Builder, but you need to be on Ventura. I might just have to install Ventura on my laptop. I'm looking forward to Regex Builder. I'm old enough to have used Regex tons in my career. So taking that and making it readable is super duper cool in my book. Can't wait to play with it.

Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.

I saw some of that regex builder stuff (is there documentation for it somewhere yet?), and my initial thought was “regex strings were good enough for me in 1996, and get off my lawn!”

BTW, is regex builder something different than the swift stuff that lets you build up a regex? I am not following the news too closely because of work stuff.
 

MEJHarrison

Site Champ
Posts
869
Reaction score
1,700
Location
Beaverton, OR
I saw some of that regex builder stuff (is there documentation for it somewhere yet?), and my initial thought was “regex strings were good enough for me in 1996, and get off my lawn!”

BTW, is regex builder something different than the swift stuff that lets you build up a regex? I am not following the news too closely because of work stuff.

I just caught a small snippet in the State of the Union video yesterday. At one point, he very quickly showed Regex Builder. It turns Regex patterns into readable code. Let me see if I can find it...

Here's a screenshot from bought 11:45 in the State of the Union video. He took one of the new Regex Expression Literals (that's just old school Regex we've been doing for years) and converted it on the fly to this. I wanted to try it out myself, but will need to be running on Ventura for that to happen.

Screen Shot 2022-06-07 at 10.07.01 AM.png
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
I wanted to play with the new Regex Builder, but you need to be on Ventura. I might just have to install Ventura on my laptop. I'm looking forward to Regex Builder. I'm old enough to have used Regex tons in my career. So taking that and making it readable is super duper cool in my book. Can't wait to play with it.

Result Builders are starting to pop up everywhere. It looks like the new App Intents API also pulls a little from this as well, but it was pretty late when I watched the platform state of the union. Not surprising though. The syntax has been growing on me and I like it compared to say, Java's approach to builders.

Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.

I'm taking some time off to try to reset a little after spending 2 years working from home and spend a chunk of that working on my own project. The start of it just lined up perfectly with WWDC, but it also means I'm going to be somewhat distracted for the first week of being able to do my own thing during working hours. Go figure. But at least it means I can do some mindless refactoring and test writing while I go through the videos.

Got a chance to play with the new navigation behaviors in SwiftUI. It's a definite improvement, but not perfect. If I use safeAreaInset on the NavigationStack itself, it still gets attached to the root view rather than the NavigationStack view. But it does mean instead of dropping down all the way to UISplitViewController, I can instead use NavigationSplitView from SwiftUI and just drop down to UIKit on the detail view to attach my accessory view.

It also does provide a working stack navigation scheme for AppKit-based apps (unfortunately without any nice animations like on iOS). Good. Unfortunately, with Beta 1, AppKit-based apps will crash when you hit the back button. Fun.
 

Cmaier

Site Master
Staff Member
Site Donor
Posts
5,209
Reaction score
8,250
I just caught a small snippet in the State of the Union video yesterday. At one point, he very quickly showed Regex Builder. It turns Regex patterns into readable code. Let me see if I can find it...

Here's a screenshot from bought 11:45 in the State of the Union video. He took one of the new Regex Expression Literals (that's just old school Regex we've been doing for years) and converted it on the fly to this. I wanted to try it out myself, but will need to be running on Ventura for that to happen.

View attachment 14728
Yeah, this is what i saw too. It seems fine, though un programming my brain from decades of \s+[_A-Za-z]\s+ etc, etc. would be tough.

I assume there are functions that let you build a Regex programmatically on-the-fly (e.g. something like Regex.append or whatever)? When I use regexes in my code, it’s *usually* because I am giving the user a search interface. Out of habit, I try to avoid using regexes to parse and things like that, for historical performance reasons (though I break that rule on occasion when I am in a hurry).
 

MEJHarrison

Site Champ
Posts
869
Reaction score
1,700
Location
Beaverton, OR
Yeah, this is what i saw too. It seems fine, though un programming my brain from decades of \s+[_A-Za-z]\s+ etc, etc. would be tough.

I assume there are functions that let you build a Regex programmatically on-the-fly (e.g. something like Regex.append or whatever)? When I use regexes in my code, it’s *usually* because I am giving the user a search interface. Out of habit, I try to avoid using regexes to parse and things like that, for historical performance reasons (though I break that rule on occasion when I am in a hurry).

We still use them for things like "did the user enter a valid email/phone/SSN/etc". I still use them for little things here and there. My problem is I use them so little, I usually need a few minutes for all that knowledge to return every time I need to use them. A builder would suit me just fine. I'd not really come across that idea before, so I found it really cool.
 

Cmaier

Site Master
Staff Member
Site Donor
Posts
5,209
Reaction score
8,250
We still use them for things like "did the user enter a valid email/phone/SSN/etc". I still use them for little things here and there. My problem is I use them so little, I usually need a few minutes for all that knowledge to return every time I need to use them. A builder would suit me just fine. I'd not really come across that idea before, so I found it really cool.
Having written a regex parser once, I always sort of think of it in the tree-like way this regex builder portrays them. I just am so used to the standard perl-like syntax that to me its just a nicer, more compact, representation at the moment. That may change as I get used to the new way. I also am quite used to using string operations to dynamically create the regular expression, and I can see where more verbose and clearer syntax could be very helpful in code maintainability.
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.

On this topic, there’s one related to new Instruments tools for investigating what’s going on with Swift concurrency. Neat stuff. I’ll get some use out of that.
 

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
On this topic, there’s one related to new Instruments tools for investigating what’s going on with Swift concurrency. Neat stuff. I’ll get some use out of that.
Hadn't had time to see that yet! I've been busy with the Metal talks. I had that one bookmarked though, I hope I manage to watch it tomorrow.

I got a slot for one of the Metal Labs with an Apple engineer tonight, so I've been busy preparing for that.
 

Andropov

Site Champ
Posts
602
Reaction score
754
Location
Spain
Saw the WWDC talk on "Bring multiple windows to your SwiftUI app" hoping they'd improved the Scenes API to be able to check for the foreground window... no luck :(
 

Nycturne

Elite Member
Posts
1,108
Reaction score
1,417
Saw the WWDC talk on "Bring multiple windows to your SwiftUI app" hoping they'd improved the Scenes API to be able to check for the foreground window... no luck :(

On Mac? Yeah, I’ve been trying to do something similar. I think the only real way is to drop down to UIKit/AppKit and inject the information as an environment value of some kind.
 
Top Bottom
1 2