3

I am trying to load a set of buttons that will slide in one after the other when the menu is loaded. So I did this:

    buttonTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(MainMenu.enterButtons), userInfo: nil, repeats: true)
    buttonTimer.fire()

That would run the enterButtons() method, which will run the move action for buttons. This timer is created during the initializer of this menu because I want the buttons to load in on init. However, during my initializer I also initialize a class which is performance heavy, since it holds all my levels:

    DispatchQueue.main.asyncAfter(deadline: .now()) {
        self.initialLevel.levelHolder = LevelHolder(screenWidth: Float(self.initialLevel.size.width), scene: self.initialLevel, screenSize: self.initialLevel.frame, initialLevel: (1,1))
    }

So I did this in async because if I did not it would hold up all the other functions of the initializer. However, I think those two threads are actually just one. The order of the async tasks are:

  1. Initialize level holder
  2. Load buttons

I was hoping that these two task would run simultaneously. However, it seems that instead of the three threads I was hoping for (the main thread, the one for the level holder, and the load buttons) I believe there is only two: the main thread and one for the two async tasks I created. During my debug session, I noticed that the load buttons task waits for the level holder to be initialized. However, I want to create three separate async threads. How can I do this?

Edit

I an attempt to achieve concurrency for the level holder I tried this, suggested by @UpholderOfTruth:

    DispatchQueue.global(qos: .background) {
        self.initialLevel.levelHolder = LevelHolder(screenWidth: Float(self.initialLevel.size.width), scene: self.initialLevel, screenSize: self.initialLevel.frame, initialLevel: (1,1))
    }

But I get an error that says:

LevelHolder is not convertible to 'LevelHolder!'

because self.initialLevel.levelHolder is of type LevelHolder! but I am setting it equal to LevelHolder.

How can I avoid this error? I tried doing this:

self.initialLevel.levelHolder = LevelHolder(screenWidth: Float(self.initialLevel.size.width), scene: self.initialLevel, screenSize: self.initialLevel.frame, initialLevel: (1,1))!

But then Xcode suggests warns I

cannot force unwrap to a non-optional type 'LevelHolder'

initialLevel is initialized within the method that initializes level holder:

func initializeLevelHolder() {
    initialLevel = InitialLevel()
    initialLevel.size = self.size
    DispatchQueue.main.asyncAfter(deadline: .now()) {
        self.initialLevel.levelHolder = LevelHolder(screenWidth: Float(self.initialLevel.size.width), scene: self.initialLevel, screenSize: self.initialLevel.frame, initialLevel: (1,1))
    }
}

It is a instance variable of this menu defined as

var initialLevel : InitialLevel!
2
  • For the animation, why not use UIView.animate(withDuration:delay:options:animations:completion:)? Commented Jun 10, 2017 at 1:57
  • 1
    UI stuff always has to happen on the main thread. Of what you’ve shared with us, both the timer and the LevelHolder stuff is happing on the main thread. There is no concurrency happening here so far, so no, this isn’t going to happen simultaneously. In terms of the desire to run stuff simultaneously, we’d have to see what LevelHolder Is doing that is so resource intensive. Maybe there’s an opportunity to make some of that happen asynchronously. But simply dispatching something asynchronously to the main queue does not provide concurrency. Commented Jun 10, 2017 at 3:35

1 Answer 1

1

You are pushing your levelHolder code back onto the main thread immediately and setting the button time to fire after 0.1 seconds and then firing it manually which happens after the method finishes. So everything is happening on the main thread and your levelHolder processing holds up the button processing.

You can put the levelHolder processing onto a background thread like this:

DispatchQueue.global(qos: .background).async {
    self.initialLevel.levelHolder = LevelHolder(screenWidth: Float(self.initialLevel.size.width), scene: self.initialLevel, screenSize: self.initialLevel.frame, initialLevel: (1,1))
}

However don't forget any updates to the UI still need to go back on the main thread like this:

DispatchQueue.main.async {
    // Update the UI
}

Unfortunately there is nothing that can be done about that as all UI updates have to happen on the main thread so will block each other to some degree.

Sign up to request clarification or add additional context in comments.

6 Comments

I thought Dispatch DispatchQueue.main.asyncAfter created an entirely different thread, but I see what you are saying is that it just merges a task after a certain amount of time into the main thread right? I don't understand why you say I fire the button time it after the method finishes? Also could you please check my last edit
Sorry my mistake with the timer for some reason I read init instead of scheduledTimer. You are quite correct you create a timer that repeats every 0.1 seconds and fire the first one manually.
Now that timer is fine to run on the main thread. However, the one I need to run concurrently is the level holder. I tried doing the background thread but I ran into an issue described in my last edit. Initial level is defined as: var initialLevel : InitialLevel! @UpholderOfTruth
Could you edit the post to show how you have defined the initialLevel property in the first place.
I have added it
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.