iosswiftasync-awaitwhile-loopcpu-usage

using while in swift for checking condition makes iOS phone over heated


I'm trying to implement a simple game, in iOS using swift. game is turn bases; first player moves, then second player, then first, ... I'm using a protocol for this:

class PersonAgent: AgentProtocol {
    let agentType: AgentType = .person
    let color: UIColor
    var points: Int = 0
    private var tappedLine: Line?

    init(color: UIColor) {
        self.color = color
    }

    func action() async -> Line {
        while self.tappedLine == nil {}
        guard let tappedLine = self.tappedLine else { fatalError() }
        self.tappedLine = nil
        return tappedLine
    }

    func tapped(line: Line) {
        self.tappedLine = line
    }
}

when a user taps on a button, view controller calls to tapped function and after that, action method can return something. the problem is phone is getting over heated for a simple game like this. can I reach same functionality without using while? any suggestion to make things more efficient cpu and memory vise?

for making things more clear, I have this code in my view controller:

extension BoardViewController {
    private func game() {
        print(#function)
        Task {
            print("inside task")
            while self.shouldResumeGame() {
                print("Turn Iteration")
                var shouldChangeTurn = true
                let currentAgent: AgentProtocol
                switch self.turn {
                case .first:
                    currentAgent = self.firstAgent
                case .second:
                    currentAgent = self.secondAgent
                }

                let actionResult = await currentAgent.action()
                // some logic here
            }

            let message: String
            if self.firstAgent.points > self.secondAgent.points {
                message = "First Player Won"
            } else if self.firstAgent.points < self.secondAgent.points {
                message = "Second Player Won"
            } else {
                message = "Draw"
            }

            // MARK: Game finished

            let alert = UIAlertController(title: "Game Finished", message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { [weak self] _ in
                self?.navigationController?.popViewController(animated: true)
            }))
            self.present(alert, animated: true)
        }
    }

    private func shouldResumeGame() -> Bool {
        return self.firstAgent.points + self.secondAgent.points != 60
    }
}

Solution

  • Don't use a while loop. This is polling

    Modern apps use an event-driven approach where events such as user-interaction or a timer firing trigger actions.

    For example, while self.tappedLine == nil {} will execute millions of times per second.

    You haven't given enough information for me to give you a complete answer, but one way of refactoring your PersonAgent is to use a CheckedContinuation:

    class PersonAgent:AgentProtocol {
        let agentType: AgentType = .person
        let color: UIColor
        var points: Int = 0
        var continuation: CheckedContinuation<Line,Never>?
    
        init(color: UIColor) {
            self.color = color
        }
        
        func action() async -> Line {
            guard self.continuation == nil else {
                fatalError("Action called while already awaiting")
            }
            let tappedLine = await withCheckedContinuation { continuation in
                self.continuation=continuation
            }
            self.continuation = nil
            return tappedLine
        }
        
        func tapped(line: Line) {
            self.continuation?.resume(returning:line)
        }
    }
    

    Now there is no polling for the line. The action function creates a continuation and awaits for it to be resumed. The tapped function resumes the continuation with the tapped line.