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.