Category Archives: Uncategorized

@MainActor – not guaranteed

tldr marking methods with @MainActor does not guarantee that they run on the main thread.


Swift has a magical new structured concurrency model.

‘using Swift’s language-level support for concurrency in code that needs to be concurrent means Swift can help you catch problems at compile time’

-the swift programming language

One of the new features is @MainActor. You can use this to annotate functions, classes, properties, etc.


this proposal introduces MainActor as a global actor describing the main thread. It can be used to require that certain functions only execute on the main thread

link

So – what does @MainActor do?
I hoped to find some documentation, but other than the evolution proposal, I haven’t found any.

There are some tutorials out there which say things like:


all that we really need to know is that this new, built-in actor implementation ensures that all work that’s being run on it is always performed on the main queue.

https://www.swiftbysundell.com/articles/the-main-actor-attribute/

This is what I thought @MainActor did. It is not true.


I created an example to demonstrate.

I have a ViewController with a method and property marked with @MainActor

    @MainActor var mainDate:Date {
        print("@MainActor var - main: \(Thread.isMainThread)")
        return Date()
    }
    
    @MainActor func printDate(_ label:String) {
        print("@MainActor func - \(label) - main: \(Thread.isMainThread)")
    }

My expectation is that calling the function printDate or the property mainDate will always run the code on the main thread. As such, they’ll always print “main: true” in their debug statements.

I created an async function which calls these

    func doWork() async  {
        let _ = await Background().go()

        print("returned from Background - now running off main thread")

        print("calling mainDate in doWork")
        self.storedDate = self.mainDate //sometimes not main thread
        printDate("in doWork") //sometimes not main thread
    }

The first call in the async function is to a background actor.

//Actor that isn't main - it does not run on the main thread
actor Background {
    func go() -> Date {
        print("Background go - main: \(Thread.isMainThread)")
        return Date()
    }
}

The effect of the background actor is simply to return from the await on a background (not main) thread.

This ensures that I’m not simply calling mainDate and printDate on the main thread by default.

I call doWork from a task responding to a button click

    @IBAction func doWorkInAsyncFunction(_ sender: Any) {

        Task { @MainActor in
            await doWork()
        }
    }

For good measure – I mark the task as @MainActor.
My expectation was the following

  1. in doWork(), it would return off the main thread after Background().go()
  2. @MainActor annotation would ensure that calling self.mainDate would happen on the main thread – or there would be a compiler error
  3. @MainActor annotation would ensure that calling self.storedDate would happen on the main thread – or there would be a compiler error

#2 and #3 are false. I get the following output when I run the code

returned from Background - now running off main thread
calling mainDate in doWork
@MainActor var - main: false
@MainActor func - in doWork - main: false

Note the last two lines print main:false – @MainActor isn’t keeping me on the main thread here…

Bizarrely – If include a print statement in my task, then everything _does_ run on the main thread!!!

    @IBAction func doWorkInAsyncFunctionWithPrint(_ sender: Any) {

        Task { @MainActor in
            //Adding this print statement, magically makes everything in doWork run on the main thread!!!
            print("Srsly?")
            await doWork()
        }
    }

gives the following “correct” output

Srsly?
Background go - main: false
returned from Background - now running off main thread
calling mainDate in doWork
@MainActor var - main: true
@MainActor func - in doWork - main: true

Similarly – if I call identical functions, but within a Task (and not even one annotated as @MainActor), then I get the “correct” results

    @IBAction func doInTask(_ sender: Any) {
        Task {
            let _ = await Background().go()

            print("returned from Background - now running off main thread")

            print("calling mainDate doInTask")
            self.storedDate = self.mainDate //main thread
            printDate("in doInTask") // main thread
        }
    }

gives the following…

Background go - main: false
returned from Background - now running off main thread
calling mainDate doInTask
@MainActor var - main: true
@MainActor func - in doInTask - main: true

Is this even wrong?

Swift isn’t behaving as I would expect it to. That’s hardly damning!

However:

  • I can’t find any formal documentation to compare against.
  • I can’t look up what the guarantee (if any) is that Swift provides when you annotate something with @MainActor

I have mostly learned from the brilliant resources provided by sites like swiftbysundell

all that we really need to know is that this new, built-in actor implementation ensures that all work that’s being run on it is always performed on the main queue.

-swiftbysundell

and Hacking with Swift

The magic of @MainActor is that it automatically forces methods or whole types to run on the main actor, a lot of the time without any further work from us. 

-hacking with swift


It seems that I’m not alone in what I expected @MainActor to do. Perhaps this is a bug. Perhaps it is expected behaviour. In the absence of clear documentation showing what @MainActor should to, I can’t tell.


Either way – marking methods with @MainActor does not ensure that they run on the main thread.

Update: Warnings Available…

returned from Background - now running off main thread
calling mainDate in doWork
warning: data race detected: @MainActor function at MainActorExample/ViewController.swift:26 was not called on the main thread
2022-01-17 14:43:40.786070+0000 MainActorExample[4407:7439502] warning: data race detected: @MainActor function at MainActorExample/ViewController.swift:26 was not called on the main thread
@MainActor var - main: false
warning: data race detected: @MainActor function at MainActorExample/ViewController.swift:31 was not called on the main thread
2022-01-17 14:43:40.811866+0000 MainActorExample[4407:7439502] warning: data race detected: @MainActor function at MainActorExample/ViewController.swift:31 was not called on the main thread
@MainActor func - in doWork - main: false

This does show runtime a bunch of warnings.

‘cannot use parameter ‘self’ with a non-sendable type ‘ViewController’ from concurrently-executed code’
and

‘cannot call function returning non-sendable type ‘Date’ across actors’

I’ll experiment some more…

Update 2: Models get errors!

Moving the exact same code out of the NSViewController and into a separate class (that inherits from nothing) causes the expected compiler warnings to kick in.

It seems that my model is ‘Sendable’ – so now the compiler can do magic.
This feels like an important limitation that should be in BIG BOLD LETTERS in the documentation…

Interestingly, when this code is moved to a separate model, calling self.mainDate without async causes an error.
However in the original ViewController version – adding an async (async self.mainDate) generates a warning

‘no ‘async’ operations occur within ‘await’ expression’

Update 3

This bug looks like the same setup

Update 4 – May 2022

Running the example code in Xcode 13.3.1 on OSX 12.3.1 and the code behaves as expected.

I don’t know if this is a compiler fix (so newly compiled apps are fine), or an OS fix (so your app will only behave unexpectedly on older OS’s.)


Example code available at https://github.com/ConfusedVorlon/MainActorExample

Dolby Codecs, Legal Threats: Coda

Back in 2014, I wrote about how Dolby were requiring me to remove ac3 support from VLC Streamer for somewhat questionable legal reasons

I included the email I received from their representative about the price I would have to pay

You should also know that there are fees associated with becoming licensed.  Specifically, there is a $25,000 one-time initial fee and a recurring $5,000 annual maintenance fee.  There is also a per-unit royalty that has a tiered structure, due quarterly, based on annual total usage, as follows:

0-100,000 downloads at $0.99 per download

100,001-1,000,000 downloads at $0.67 per download

1,000,001+ downloads at $0.45 per download

I was interested to get a call from a lawyer working for Adobe recently. They were being sued by Dolby for something related to these patents.

Bizarrely, Dolby were arguing that their pricing levels were so super secret that they couldn’t even disclose them in court.

Adobe found my old post and argued that if they were published on the web – they couldn’t be that secret.

I don’t have any details about the case, but I know they settled and it went away. Hopefully my pricing info made the difference…

Overreaction vs Sanity. Google Play vs Apple Store.

In the last week or so, both Apple and Google informed me that one of my apps was in a small way breaching store guidelines.

Photo by Jorge Fernandez on Unsplash

I’ll paraphrase both messages here:

One of your apps hasn’t been updated in years. It doesn’t meet current guidelines. If you don’t update it – we’ll remove it in 30 days.

Apple

and

One of your apps breaks the metadata guidelines. It has a quote in the app description.

We have removed it from the store.

Google

Both complaints are probably valid. The iOS app hasn’t been updated since 2015 and hasn’t been optimised for the latest devices. I’ll update it this month.

The Android app did have the following quote in the store description

VLC Remote is the latest Android app to earn my favor, and it’s a beauty

Androidnica

The quote has been there for at least 5 years. I’m not entirely sure whether it should count as a ‘user testimonial’ (banned) or a ‘third party review’ (I think those are ok). But either way – I’m happy to remove it.

Removing my app from the store just seems like a Massive Overreaction when they could simply have emailed me to request a change.

Easier iTunes Connect Sandbox Users with GSuite Routing

Use GSuite Routing to dynamically handle different iTunes Connect test emails.

iTunes connect testing is a pain. One of the pain points is that you have to create a bunch of iTunes Connect accounts when you want to test your purchase flows. Each of those needs a separate email address.

You used to be able to do this with the magic

  • rob+test1@hobbyistsoftware.com
  • rob+test2@hobbyistsoftware.com

And all your emails would be routed through to rob@hobbyistsoftware.com (this works for gmail, and many other email providers)

Sadly Apple disabled this capability some time in 2018 (?) – so now you need a new valid email address for every iTunes Connect sandbox user

GSuite Routing provides a neat way to restore this functionality

  1. Open your GSuite Management Console
  2. Click through to Apps > G Suite > Gmail
  3. Click on ‘Default Routing’
  4. Click ‘Add Setting’ and add something like the following

(note – the regexp is rob_.*@hobbyistsoftware.com)

This redirects all email of the format rob_something@hobbyistsoftware.com to rob@hobbyistsoftware.com

New App for Mac: AV Rules

When I connect my projector, I want my mac to switch to 1080p.

When I connect my TV, I want it to run at 720p

When hdmi audio output is available, I want my mac to use that (and to use all the channels too!)

Previously, my Mac mini didn’t do a great job of switching, so I wrote AV Rules to take charge.

rules


Buy Now

Windows Store is still rubbish

Back in 2105, the Windows store decided to unpublish VLC Remote after I submitted an update.

I ran a competition to try to figure out why they had banned it. This attracted some media attention which ultimately resulted in a  Windows Developer Platform manager contacting me. He quickly got some people on the case and the app was restored.

About three weeks later, I needed to update the description. The same thing happened again. Again, the same chap kicked some butt in the store and got the update approved.

A year or so later, and I have had a request from Microsoft to fill in their age rating questionnaire.

Of course – this requires submitting a new update, and of course the exact same problem is hitting. (Note – the app, and its description and screenshots are completely unchanged)

App Policies: 10.1 Inaccurate Representation, Title

Your app and its associated metadata must accurately and clearly reflect the source, functionality, and features of your app.

  • <snip – list of policies>

Notes To Developer

The app name does not accurately represent the app.
For more information see Windows Store Policy 10.1 at http://go.microsoft.com/fwlink/?LinkId=620446.

This time, I haven’t had a response from the program manager (he has probably moved on in disgust)

I have resubmitted with a link to the original story, but have just had a somewhat meaningless response

Hello Rob,

Thank you for the follow up and I am showing that app under that account. Now I also reviewed over the listing and I would suggest contacting report app from the email below as they will be bale to assist you directly on the certification.

Report App: reportapp@microsoft.com

If you have any questions or concerns, please respond to this e-mail and we will work to resolve them as quickly as possible.

I don’t think this will help – clearly the issue is nothing to do with me reporting an infringement, or anyone else reporting an infringement. The issue is that every time I submit an update, Microsoft do the same brain-dead thing.

I’m going to try one more submission with the ‘do not fail… escalate to…’ note…

I guess Windows Store is dying, and Microsoft just want to clear out the few remaining apps.