There are two pieces of code I’d like to review before you continue, because both are hugely unappreciated.
assert() function. If you remember, you place calls to
assert() in your code whenever you want to say, “I believe X must be the case.” What X is depends on you: “this array will contain 10 items,” or “the user must be logged in,” or “the in-app purchase content was unlocked” are all good examples.
assert() line hits, iOS validates that your statement is true. If the array doesn’t contain 10 items, or the in-app purchase wasn’t unlocked, the assertion will fail and your app will crash immediately. Xcode will print the location of the crash – e.g. ViewController.swift line 492 – along with any custom message you attached, such as “The in-app purchase failed to unlock content.”
Obviously crashing your app sounds bad on the surface, but remember: assertions are automatically removed when you build your app in release mode – i.e., for the App Store. This means not only will your app not crash because an assertion failed, but also that those assertions aren’t even checked in the first place – Xcode won’t even run them. This means you can – and should! – add assertions liberally throughout your code, because they have zero performance impact on your finished, shipping app.
Think of assertions as “hard-core mode” for your app: if your app runs perfectly even with assertions in every method, it will definitely work when users get hold of it. And if your assertions do make your app crash while in development, that’s perfect: it means your app’s state wasn’t what you thought, so either your logic is wrong or your assertion was.
The second piece of code to review is the
Timer class, which we used like this:
gameTimer = Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
Timers are rudimentary things, but perfectly fit many games – that code will cause the
createEnemy() method to be called approximately every 0.35 seconds until it’s cancelled.
In this case, the
gameTimer value was actually a property that belong to the game scene. Cunningly,
Timer maintains a strong reference to its target – i.e., it stores it and won’t let it be destroyed while the timer is still active – which means this forms a strong reference cycle. That is, the game scene owns the timer, and the timer owns the game scene, which means neither of them will be ever destroyed unless you manually call
In project 17 this isn’t a problem, but we’ll be returning to this theme in project 30 when it is a problem – watch out for it!
By the way: the
Timer class really does offer only approximate accuracy, meaning that the
createEnemy() method will be called roughly every 0.35 seconds, but it might take 0.4 seconds one time or 0.5 seconds another. In the context of Space Race this isn’t a problem, but remember: iOS wants you to draw at 60 or 120 frames per second, which gives you between 8 and 16 milliseconds to do all your calculation and rendering – a small delay from
Timer might cause problems in more advanced games.
The iOS solution to this is called
CADisplayLink, and it causes a method of yours to be called every time drawing has just finished, ensuring you always get the maximum allotment of time for your calculations. This isn’t covered in Hacking with Swift, but you’ll find an explanation and code example in this article in my Swift Knowledge Base: How to synchronize code to drawing using CADisplayLink.
SPONSORED Only until this Sunday, December 12th, you can join a free crash course to learn advanced techniques for testing new and legacy Swift code — it's the fast track to becoming a complete Senior iOS Developer!
Link copied to your pasteboard.