Scope & Context Explained In Swift

Written by Reinder de Vries on December 6 2019 in App Development, Swift

Scope & Context Explained In Swift

The concept of scope states that, if you’ve declared a variable or function in one region of your code, you cannot use it elsewhere. It’s a subtle, implicit rule in programming and it can be challenging to grasp at first.

In this article, we’ll focus on exploring scope, and what it means for practical iOS development.

Ready? Let’s go.

  1. What Is Scope?
  2. Global, Local, Function & Class Scope
  3. Scope In Practical iOS Development
  4. Further Reading

What Is Scope?

We’re going to start with an example. Check out the following Swift code:

func getAge() -> Int
{
var age = 42
age += 1

return age
}

var age = 99
var anotherAge = getAge()
anotherAge += 1

print(age)
print(anotherAge)

Take a moment to read through the code. Without cheating, can you guess what the values of age and anotherAge are?

In the above code, we’re working with two kinds of scope, the global and local scope.

  • The local scope is present in the function getAge(), which is why it’s often called function scope. Variables, like age, that are declared inside the function, cannot be accessed outside of it.
  • The global scope is present everywhere – that’s why its called global. Variables defined at the highest level of the code, i.e. outside of functions, classes, etc., can be used anywhere. (There are exceptions, though.)

Now, let’s look at that code again. The variable age is defined inside the getAge() function. We cannot access that same variable outside the function.

We also can’t redeclare a variable with the same name, in the same scope, because variable names must be unique within their scope. What we’ve done though, is define another variable with the same name in a different scope. See which one?

  1. A variable age is defined inside the getAge() function, with var age = 42, on the first line of the function.
  2. A variable age is defined outside (below) the getAge() function, with var age = 99.

These two variables have the same name, but they’re declared in different scopes. Their regions of code – their scope – don’t conflict with each other. That’s why we can use them both, with the same name, separately!

When the code runs, a variable age is initialized with value 99. We then initialize a variable called anotherAge with the value that’s returned from the getAge() function, which is 43. That value is then incremented by one.

Finally, we’re printing out the values of these variables. The value of age is 99, because it hasn’t changed. The value of anotherAge is 44. It’s initialized as 42, incremented inside the function, and incremented outside of it. Despite 2 of these 3 variables having the same name, their don’t conflict each other, because they’re declared in different scopes.

Starting to get the hang of scope? It’s nothing more than the region in which you have access to certain variables. It’s simplest to think of scope as an actual scope, you know, the one you find on top of a rifle – or a viewfinder in your photocamera, or in binoculars. When you look through the scope, you can’t see what’s outside of your view!

Local scope is the scope you’re currently in, i.e. typing in, that block of code between squiggly brackets { }. If you want to get the hang of scope, just keep track of what your current, local scope is, and to which variables, types, etc. you have access.

In Swift, functions are at the deepest level of scope, which is why a local scope is often the same as the function scope. Closures are at a level deeper than functions, but closures can “close over”, which makes them kinda special. More about that later!

Learn how to build iOS apps

Get started with iOS 14 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.

Global, Local, Function & Class Scope

But wait, there’s more!

So far, we’ve only looked at the global and local scopes. There’s also something called a function scope, and classes have a scope too. In fact, frameworks, modules and Swift files themselves, have a scope. Scopes have levels, they’re hierarchical, and types, in fact, are scoped too. Pfew! Where do we even begin!?

Let’s start with a simple class. Like this one:

class Product
{

}

As far as we can see, this class is defined in the global scope. We’re now going to add an enumeration to this class, as a nested type. Like this:

class Product
{
    var kind:Kind = .thing

    enum Kind {
        case food
        case thing
    }
}

In the above code, we’ve defined an enum called Kind. It has two cases – for the sake of this example, we’re considering any product either a food (can eat it) or a thing (cannot eat it). We’ve also added an instance property kind of type Kind, which is initialized with value .thing by default.

Let’s discuss this example in terms of scope. What’s the scope of all that stuff? Here’s what:

  1. The scope of the Product class is global. It’s globally defined, so we can create Product objects anywhere in the code.
  2. The scope of the Kind enum is limited to inside the class. We can use the Kind type inside the class, and not outside of it.
  3. The scope of the kind property is also limited to inside the class. We can use that property inside the class.

However, something else is going on. We can use Product in the global scope, and because the Kind enum has internal access control by default, we can use it as the Product.Kind type anywhere in the code.

The code below can be used anywhere in the code. We can reach the Kind nested type via the Product class:

let banana = Product()

if banana.kind == Product.Kind.food {
    // Itsafood!
}

And, likewise, the kind property is defined in the class scope, but because it’s also publicly accessible, we can access that property on any object of type Product.

Let’s get back to those different types of scope. Here, check out the Swift code below. We’re adding a canEat() function to the Product class:

class Product
{
    var kind:Kind = .thing

    enum Kind {
        case food
        case thing
    }

    func canEat() -> Bool {
        return kind == .food
    }
}

We’re dealing with 3 levels of scope here:

  1. The global scope, in which the Product class is defined
  2. The class scope, in which the kind property, Kind enum and canEat() function are defined
  3. The function scope, inside the canEat() function

The Product class is defined in the global scope, so we can use that anywhere in the app’s module. The kind property is defined in the class scope, so we can use that within the Product class. Same goes for the Kind enum, and the canEat() function.

We’re using the kind property inside the canEat() function. That means scopes have a hierarchy, because we can access a property from the class scope within the function scope. However, if we defined a local variable inside canEat(), we can’t use that variable in another function in the same class, because they have different scopes.

func canEat() -> Bool {
    let hungry = ...
}

func isThing() -> Bool {
    // Cannot use `hungry` in here...
}

So, to summarize:

  • Every region in your code, the stuff between square brackets, has a scope: global scope, class scope, function scope, and so on
  • We generally distinguish between local scope and global scope, in order to express whether we have access to a certain variabe, property, or type
  • Scopes are hierarchical, which means we can access Kind via Product.Kind if we’re in the global scope
  • Scopes are hierarchical, which means we can access a class property inside a class function, because the function has access to the class scope

The visibility of a type, variable, property, etc. determines whether it can be accessed. This is called access control. We’ve got 5 different levels of access: open, public, internal, fileprivate, and private. In general, we shorten that to “is it public?” or “is it private?”, because that’s quicker. You can learn more about access control in Swift here: Access Control Explained In Swift

Scope In Practical iOS Development

Scope is everywhere in practical, day-to-day iOS development. When you’re moving values around in your app, tracking to which variables, types etc. you have access, is a constant activity.

Chances are that, if you’re relatively new to iOS development, you’ve already incorporated scope in your reasoning about your code, without knowing it! “Which variable can I access where?”

An interesting case of scope is that of closures. As you may know, a closure is a block of code that can be passed around your code. It’s similar to a function, except that the code itself is the value. You can assign a closure to a variable, pass it into a function, after which it ends up in a different part of your program.

Closures are often used as so-called completion handlers. Say we’re downloading an image asynchronously from the internet. When the downloading completes, at a future point in time, we want to execute some code to show the image. We define this code in a closure, pass it to the function that downloads the image. This function then executes the closure when the download has completed.

Here, check this out:

class DetailViewController: UIViewController
{
    @IBOutlet weak var imageView:UIImageView?

    func viewDidLoad()
    {
        network.downloadImage(url, completionHandler: { image in
            imageView?.image = image
        })
    }
}

In the above code, we’ve created a view controller class with an imageView property. Inside the function, we’re calling a hypothetical downloadImage(_:completionHandler:) function. It’s second parameter, the completion handler, between the innermost squiggly brackets, is a closure. When the image download has completed, we’re assigning the downloaded image value to the image property of the imageView, which will show the image.

A closure is called a closure because it “closes over” any values that are referenced in the closure. Because closures can be passed as values through your code, a closure needs a way to reference values that are used inside the closure. This principle is called closing over, or capturing.

In the above example code, the closure holds onto a reference to the imageView property. It needs that property later, to set the image. When the closure finishes executing, this reference is released.

This capturing only works if the closure has access to the same level of scope as the function it’s defined in. Even though the closure is a separate entity, it can access the imageView property, because the function viewDidLoad() has access to that scope. A closure has the same scope as the entity it’s defined in, and in this case, that’s the function scope.

Interesting, isn’t it? Understanding capturing is the ultimate test of your understanding of scope. You’ll have to figure out why the closure, which is executed in the future, as seen from the context of the viewDidLoad() function, could possibly have access to the imageView property. It’s because the closure has access to the scope it’s defined in!

Scope and context are often confused. Scope is fixed, because the code you write is fixed. Context is fluid, and it depends on the execution of your code. So, you may have access to a property because it’s in scope, but because of the context of your code, i.e. its point in execution, that property is empty. You can see scope as how your code is defined (fixed), and context as what your code does at any given point in time (fluid).

Learn how to build iOS apps

Get started with iOS 14 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.

Further Reading

Scope is a concept that’s hard to put into rules and words, but once you get the hang of it, it becomes second nature. You’ve been working with scope all along, maybe even without knowing it. Scope is the answer to the question: “Can I use this variable here?” Neat!

Want to learn more? Check out these resources:

Reinder de Vries

Hi, I'm Reinder.
I help developers play with code.

Get the Weekly

Get iOS/Swift tutorials and insights in your inbox, every Monday.
  • This field is for validation purposes and should be left unchanged.

Most Popular

Browse Topics

Swift Sandbox

Code Swift right in your browser!
Go to the Swift Sandbox

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.

×

Build great iOS apps
Learn how in my free 7-day course

  • This field is for validation purposes and should be left unchanged.

No spam, ever. Unsubscribe anytime. Privacy Policy