Xavier Lowmiller's Blog

software developer • vegetarian • pub quiz organizer • star wars • bob dylan • member of gryffindor

Just Do Blocks

Swift’s error handling system relies on do blocks to wrap throwing statements and handle those errors:

do {
    try FileManager.default.write(file, to: url)
} catch {
    print("An error has occurred trying to write the file")
}

While this is obviously the most common use case, there’s nothing stopping you from writing a do block without any try statements

do {
    print("I won't throw an error")
}

Why would you do this, you might ask?

Variable Names

Well, do blocks have their own variable scope. Consider the following code:

func persist(image: UIImage, named name: String) {
    // Save full size image
    let fullSizeData = image.jpegData(compressionQuality: 1)
    let fullSizeUrl = fullImageUrl(for: name)
    persist(fullSizeData, at: fullSizeUrl)

    // Save thumbnail
    let thumbnailData = image.resizedToThumbnailSize.jpegData(compressionQuality: 1)
    let thumbnailUrl = thumbnailUrl(for: name)
    persist(thumbnailData, at: thumbnailUrl)
}

All that awkward naming can be avoided using do blocks:

func persist(image: UIImage, named name: String) {
    // Save full size image
    do {
        let data = image.jpegData(compressionQuality: 1)
        let url = fullImageUrl(for: name)
        persist(data, at: url)
    }

    // Save Thumbnail
    do {
        let data = image.resizedToThumbnailSize.jpegData(compressionQuality: 1)
        let url = thumbnailUrl(for: name)
        persist(data, at: url)
    }
}

It’s not a problem to use the same name in different do blocks.

I find this particularly useful with XCTestExpectations in unit tests:

do {
    let exp = XCTestExpectation(description: "Wait for async dispatch")
    loadUserData() { _ in exp.fulfill() }
    wait(for: [exp], timeout: 1)
}

do {
    let exp = XCTestExpectation(description: "Wait for async dispatch")
    loadPhotosMetadata() { _ in exp.fulfill() }
    wait(for: [exp], timeout: 1)
}

do {
    let exp = XCTestExpectation(description: "Wait for async dispatch")
    loadPhotos() { _ in exp.fulfill() }
    wait(for: [exp], timeout: 1)
}

XCTAssert(...)

No more exp1, exp2, exp3, …!

Memory Management

Furthermore, as variables go out of scope their memory can be reclaimed. You can use this like the using statement in C#.

func postProcessFrameOfMovie(at url: URL, at scrubbingPosition: TimeInterval) -> UIImage {
    let frame: UIImage
    do {
        // Load the movie, which fills up a lot of memory
        let movie = Movie(at: url)
        frame = moview.frame(at: scrubbingPosition)
    }
    // The movie is out of scope now, its memory can be reclaimed

    frame.postProcess()

    return frame
}

This is also useful for nasty stuff like locks, which should be kept around as short as possible.

Posted 24 Oct 2018