BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

Key points

There are two things I’d like to review for this milestone.

First, the weak keyword. We used it in project 29 to add a property to our game scene:

weak var viewController: GameViewController!

We also added the opposite property to the game view controller:

var currentGame: GameScene!

This approach allowed the game scene to call methods on the view controller, and vice versa. At the time I explained why one was weak and the other was not – do you remember? I hope so, because it’s important!

There are four possibilities:

  1. Game scene holds strong view controller and view controller holds strong game scene.
  2. Game scene holds strong view controller and view controller holds weak game scene.
  3. Game scene holds weak view controller and view controller holds weak game scene.
  4. Game scene holds weak view controller and view controller holds strong game scene.

Remember, “strong” means “I want to own this; don’t let this memory be destroyed until I’m done with it,” and weak means “I want to use this but I don’t want to own it; I’m OK if it gets destroyed, so don’t keep it around on my account.”

Now, the view controller has an SKView, which is what renders SpriteKit content. That’s owned strongly, because obviously the view controller can’t really work without something to draw to. And that SKView has a scene property, which is the current SKScene visible on the screen. That’s also strongly owned. As a result, the view controller already - albeit indirectly – has strong ownership of the game scene.

As a result, both options 1 and 2 will cause a strong reference cycle, because they would cause the game scene to have a strong reference to something that has a strong reference back to the game scene. This isn’t necessarily bad as long as you remember to break the strong reference cycle, but let’s face it: why take the risk?

That leaves options 3 and 4: both have the game scene using a weak reference to the view controller, but one has a weak reference going back the other way and the other has a strong one. Which is better? Honestly, I’m not sure it matters: using a strong reference wouldn’t result in anything new because there’s already the indirect strong reference in place. So, use whichever you prefer!

The second thing I’d like to cover is much easier: it’s the UIImage(contentsOfFile:) initializer for UIImage. Like I said in project 30, the UIImage(named:) initializer has a built-in cache system to help load and store commonly used images – anything you load with that initializer will automatically be cached, and so load instantly if you request it again.

Of course, if you don’t want something to be cached, that’s the wrong solution, which is where UIImage(contentsOfFile:) comes in: give it a path and it will load the image, with no magic caching ever happening.

The downside is that UIImage(named:) automatically finds images inside your app bundle, whereas UIImage(contentsOfFile:) does not. So, you need to write code like this:

let path = Bundle.main.path(forResource: someImage, ofType: nil)!
imageView.image = UIImage(contentsOfFile: path)

That’s hardly a lot of code, but it’s never nice writing even simple repetitive code – and look at that force unwrap after path()! Wouldn’t it be great to get rid of it? I’m going to show you how to do just that, and, as a bonus, I’m also going to teach you something new: convenience initializers.

You’ve already seen initializers – we’ve used dozens of them. They are special methods that create things, like UILabel() or UIImage(named:). Swift has complex rules about its initializers, all designed to stop you trying to access something that hasn’t been created yet.

Fortunately, there’s one easy part, which is convenience initializers, which are effectively wrappers around basic initializers that are designed to make coding a bit more pleasant. A convenience initializer is able to do some work before calling a regular initializer, which in our case means we can add a wrapper around UIImage(contentsOfFile:) so that it’s nicer to call.

To make things even better, we’re also going to get rid of the force unwrap. Remember, path(forResource:) can return nil because the file you requested might not exist. Force unwrapping it works on occasion if you know something definitely exists, but it’s usually a better idea to use an alternative such as failable initializers.

You’ve already used failable initializers several times – both UIImage(named:) and UIImage(contentsOfFile:) are failable, for example. A failable initializer is one that might return a valid created object, or it might fail and return nil. We’re going to use this so that we can return nil if the image name can’t be found in the app bundle.

So, leveraging the power of Swift extensions that you learned in project 24, here’s a UIImage extension that creates a new, failable, convenience initializer called UIImage(uncached:). It works like UIImage(named:) in that you don’t need to provide the full bundle path, but it doesn’t have the downside of caching images you don’t intend to use more than once.

Here’s the code:

extension UIImage {
    convenience init?(uncached name: String) {
        if let path = Bundle.main.path(forResource: name, ofType: nil) {
            self.init(contentsOfFile: path)
        } else {
            return nil
        }
    }
}

Note the init? syntax that marks this as an initializer that returns an optional.

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.