Swift Context manager: Implementing Python Context manager in swift

Omar Abdelhafith
iOS App Development
4 min readJul 20, 2015

--

Whenever I work on python, I keep admiring all the nice construct that the language has. One construct that I always liked is the context manager and the with statement.

From python docs:

The with statement is used to wrap the execution of a block with methods defined by a context manager.

with is a python language statement that enables safe access to a resource. It ensures that the instance is setup before the execution, and cleaned up after it. with works in conjunction with a context manager. The context manager defines methods that with calls to enter and exit its wrapped resource.

In this article I will examine how to implement python’s with and context manager in swift.

Python’s with statement

Lets consider the classical example of opening a file in python:

f = open("the file")
try:
# do something with the file
finally:
f.close()

Since it is crucial to close the file handle, we wrap the file usage in a try/catch statement.

To understand the power of with lets examine the same example using with statement:

with open("the file") as opened_file:
# do something with the file

with statement has three parts: the with item (open(“the file”) in the example), the optional target instance passed to the block (opened_file above) and the suite or the block to be executed.

The with statement has two main responsibilities:

  • Provide a consistent way to enter and exit a wrapped resource.
  • Ensure that exit is called regardless of if an exception is thrown inside the with block.

Swift implementation

To begin, lets consider the with statement itself. Our implementation of with in swift will be a function that takes in a Context and a block.

with needs to perform three operations:

  • Enters the context: which it does by calling enter function on the passed context.
  • Execute the the passed block passing the context as the parameter.
  • Exits the context by calling exit on the context instance.

Our first implementation might look like this:

func with(context: Context, block:(context: Context) -> () ) {
context.enter()
block(context: context)
context.exit()
}

This is not a bad start, yet, in the python implementation, the block can throw an exception. Lets update our with function to address that:

func with(context: Context, block:(context: Context) throws -> () ) {
context.enter()
do {
try block(context: context)
context.exit(nil)
} catch {
context.exit(error)
}
}

Our with now looks much better and its ready to receive a context instance.

Lets proceed with defining the context manager. In order for a type to be considered as a context, it needs to contain an enter and an exit methods. Context is a good fit for a protocol:

protocol Context {
func enter()
func exit(error: ErrorType?)
}

Next, lets replicate the file access example done in python in swift. We will do that by wrapping an NSFileHandle instance in a context manager. First, lets see the original code for using NSFileHandle:

let f = NSFileHandle(forReadingAtPath: "the path")!
NSString(data: f.readDataToEndOfFile(), encoding: NSUTF8StringEncoding)
f.closeFile()

Our file handle Context creates an NSFileHandle on init and close it on exit. The following is the implementation:

struct FileHandleContext: Context {
var fileHandle: NSFileHandle!
init(filePath: String) {
fileHandle = NSFileHandle(forReadingAtPath: filePath)
}
func enter() {
//nothing
}
func exit(error: ErrorType?) {
fileHandle.closeFile()
}
}

As discussed, the only interesting detail is to close the filehandle in exit function.

To use FileHandleContext as a context manager, we will pass it to with function:

with(FileHandleContext(filePath: "the path")) { (context) -> () in
let s = NSString(data: context.fileHandle.readDataToEndOfFile(), encoding: NSUTF8StringEncoding)
}

Note on protocol and generics

When we try to execute the above example, we notice that it fails to compile.

The reason is in our version of with we use Context as a protocol type, which means the passed instance to the with block is of Context type, and since context does not define a fileHandle, then the compiler cannot allow us to use it.

Luckily, this is easily fixed by updating the with function signature to look like the following:

func with<C: Context>(context: C?, block:(context: C) throws -> () ) { }

Now, in the second take of with function, we use Context as a generic constraint, this tells the compiler to substitute C with the type that we pass as the first argument, In our example, that type will be FileHandleContext. Success!

Exceptions thrown inside the with block

Finally, if an exception is thrown inside the with block, with will capture this exception and sends it to Context’s’ exit method. This ensures that the Context has a saying on how to deal with the exception.

For example, the file handle context manager can choose to delete the file, or undo the the content written to the file if an exception was thrown.

It is really the responsibility of the context manager to react upon receiving an exception.

Recap

We saw how wrapping a resource in a context manager help making our code safer. It does that by ensuring that the resource wrapped is cleaned up after usage. We also saw how elegantly swift can implement a context manager.

I created a playground with the code used in this article, it contains all the examples used in this article. It also contains a bonus tip on how to wrap NSUserDefaults in a context manger. Check it here trust me you wont regret it.

--

--

Omar Abdelhafith
iOS App Development

Software developer @Facebook, previously @zendesk. In ❤ with λ, Swift, Elixir and Python. Check my Github https://github.com/nsomar.