[Download Xcode project and source code from GitHub to follow along.]
Let’s learn about, formally define, review some code for, and write some code for blocks in Objective-C, and write some code for closures in Swift. Blocks are one of the most important programming language constructs you’ll ever learn about. I depend on them to get notified when concurrent tasks complete (i.e., as callbacks), whether I submitted those tasks synchronously or asynchronously. I’ll bet that even if you have never heard of blocks or closures, you’ve already used them. Guess what? If you’ve been reading this blog, you’ve already used blocks!
Objective-C blocks and Swift closures
Blocks/closures allow you to create chunks of almost any type of code that can be called almost anywhere, anytime (like in the future). They are self-contained but know about local variables around them. They can be assigned to properties, variables, and/or constants — and passed as parameters to functions/methods. Blocks/closures can access variables and constants from the surrounding environment in which they were defined. Even if the variables and constants from their original surrounding environment go out of scope, blocks/closures can still access those variables and constants. Perhaps most importantly, blocks/closures are great for communicating between objects, especially as in the case of reporting completion of an operation (i.e., callbacks). You can define a block/closure one place, but not have it execute until (much) later. So you don’t have to incur the overhead of adopting protocols and encoding calls to delegate methods. Protocols/delegates have their place, but you don’t have to write all the code to implement and adopt them when you just need a simple callback. That’s my take. How are blocks and closures formally defined?
Blocks represent typically small, self-contained pieces of code. As such, they’re particularly useful as a means of encapsulating units of work that may be executed concurrently, or over items in a collection, or as a callback when another operation has finished.
Blocks are a useful alternative to traditional callback functions for two main reasons:
- They allow you to write code at the point of invocation that is executed later in the context of the method implementation.
Blocks are thus often parameters of framework methods.
- They allow access to local variables.
Rather than using callbacks requiring a data structure that embodies all the contextual information you need to perform an operation, you simply access local variables directly.
Apple’s “Blocks Programming Topics: Conceptual Overview – Usage”
Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.
Closures can capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables. Swift handles all of the memory management of capturing for you.
Apple’s “The Swift Programming Language (Swift 3.0.1): Closures”
Code where you might not recognize blocks/closures
Here’s my code from my post entitled “Concurrency in iOS: serial and concurrent queues in Grand Central Dispatch (GCD) with Swift 3”, where I submitted image file download tasks on a concurrent queue using Swift 3. See highlighted lines of code 13, 15, 16, 17, 18, 19, 20, 25, 27, 28, 29, 30, 32, 34. These are closures — actually, there’s one larger closure that contains a sub-closure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
@IBAction func startAsyncImageDownload(_ sender: Any) { // Download enough images to fill in the UIImageViews on my // UIViewController; I've set each UIImageView's "tag" property // so I can access each UIImageView programmatically for i in imageViewTags { // Start each image download task asynchronously by submitting // it to the default background queue; this task is submitted // and DispatchQueue.global returns immediately. DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { // Begin Closure // I'm PURPOSEFULLY downloading the image using a synchronous call // (NSData), but I'm doing so in the BACKGROUND. let imageView : UIImageView = self.view.viewWithTag(i) as! UIImageView let imageURL = URL(string: self.imageURLs[i-10]) let imageData = NSData(contentsOf: imageURL!) print("image tag: \(i)") // Once the image finishes downloading, I jump onto the MAIN // THREAD TO UPDATE THE UI. DispatchQueue.main.async { // Begin Closure imageView.image = UIImage(data: imageData as! Data) self.imageCounter += 1 self.progressView.progress = Float(self.imageCounter) / Float(self.imageURLs.count) self.view.setNeedsDisplay() } // End DispatchQueue.main.async Closure } // end DispatchQueue.global(_).async Closure } // end for i in imageViewTags } // end startAsyncImageDownload |
Look in Apple’s Grand Central Dispatch (GCD) Swift documentation at the official instance method declaration for “async” (line 4 highlighted):
1 2 3 4 |
func async(group: DispatchGroup? = default, qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, execute work: @escaping () -> Void) |
Apple calls line 4 an “escaping closure:”
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.
One way that a closure can escape is by being stored in a variable that is defined outside the function. As an example, many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn’t called until the operation is completed—the closure needs to escape, to be called later.
Here’s the Objective-C equivalent of my Swift code using a closure. See highlighted lines of code 7, 9, 10, 11, 14, 16, 17, 18, 20, 22. These are blocks — actually, there’s one larger block that contains a sub-block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
- (IBAction)startAsyncButtonTapped:(id)sender { for (NSString *stringURL in self.listOfURLs) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { // Begin Inner Block NSURL *url = [NSURL URLWithString:stringURL]; NSData *imageData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^ { // Begin Inner Block NSLog(@"URL: %@", [url path]); self.imageView.image = image; self.progressView.progress = (float)self.counter / (float)self.listOfURLs.count; }); // End Inner Block }); // End Outer Block } } |
Look in Apple’s Grand Central Dispatch (GCD) Objective-C documentation at the official function declaration for “dispatch_async” (line 2 highlighted):
1 2 |
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); |
Apple calls line 2 a “block:”
The block to submit to the target dispatch queue. This function performs Block_copy and Block_release on behalf of callers. This parameter cannot be NULL. …
This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. The target queue determines whether the block is invoked serially or concurrently with respect to other blocks submitted to that same queue. Independent serial queues are processed concurrently with respect to each other.
Writing Objective-C code for blocks
Let’s write some Objective-C code to define and use a block to serve as a callback when an asynchronous, concurrent task completes.
1) Let’s declare a block reference, or… I make extensive use of blocks and find that a block that returns success or failure and returns a string containing a possible error message is applicable to many different situations, I define a block type:
1 2 3 4 5 6 |
#pragma mark - Define blocks /*! Define a completion block TYPE for updating a UIImageView once an image downloads. */ typedef void (^ImageDownloadCompletionBlock) (BOOL success, NSString* message); |
2) Now we define the block, encoding what we want it to do, and pass it as an argument to a method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/*! Specify the completion handler/callback to be executed when finished downloading each image in a set of very large images. We do the downloading in the background (concurrently and asynchronously). */ - (void)prepareForAsyncImageDownloads { // Define a completion block INSTANCE for updating a UIImageView once an image downloads. ImageDownloadCompletionBlock completionBlock = ^(BOOL success, NSString* message) { if (success) { self.imageView.image = self.image; self.progressView.progress = (float)self.counter / (float)self.listOfURLs.count; NSLog(@"%@", message); } else { self.imageView.image = nil; NSLog(@"%@", message); } }; // start download process; each download task will call back/communicate // that it's finished by executing the completionBlock paramter [self performAsyncImageDownloadsWithCompletionBlock:completionBlock]; } |
3) When our background task for downloading an image completes, it calls our block (completion handler) to inform the caller of success or failure and updates the user interface (UI) on the main thread:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
/*! Download a set of images specified by a list of URLs to those images. Spin each image download task off asynchronously on a concurrent queue (i.e., in the background). When each image finishes downloading, call a block which updates the user interface to display the latest image. @param completionBlock A block to execute on the MAIN THREAD to update the UI with the latest image */ - (void)performAsyncImageDownloadsWithCompletionBlock:(ImageDownloadCompletionBlock)completionBlock { // go through the list of URLs specifiying addresses to images // we want to download for (NSString *stringURL in self.listOfURLs) { // start the download for the current URL asynchronously and // in the background dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { // Begin outer block NSURL *url = [NSURL URLWithString:stringURL]; NSData *imageData = [NSData dataWithContentsOfURL:url]; self.image = [UIImage imageWithData:imageData]; self.counter++; // jump back on the MAIN THREAD to update the UI dispatch_async(dispatch_get_main_queue(), ^ { // Begin inner block // if an image was successfully downloaded... if (self.image) { // this BLOCK updates the UI NSString *successString = [NSString stringWithFormat:@"SUCCESS: Image downloaded successfully - %@.", [url absoluteString]]; completionBlock(YES, successString); } else // image download failed { // this BLOCK updates the UI NSString *errorString = [NSString stringWithFormat:@"ERROR: Image was NOT downloaded - %@.", [url absoluteString]]; completionBlock(NO, errorString); } }); // End inner block }); // End outer block } // end for (NSString *stringURL in self.listOfURLs) } // end performAsyncImageDownloadsWithCompletionBlock |
Download the code and try it out. We’re going to write code for closures in Swift, same Bat-Time, same Bat-Channel, so check back soon. I’ll give you a hint about defining Swift closures:
1 2 3 |
{ (parameters) -> return type in statements } |
Go to the next post to see all the code I wrote for defining and executing a Swift 3 closure.
Note that I’m including coverage of Objective-C for a good reason. Remember that a lot of iOS apps today are being written, or originally were written, in Objective-C and are still being actively maintained and updated today. Swift has changed so many times that developers have had to rewrite the same code multiple times. Many Apple iOS frameworks were/are written in Objective-C. While Swift is a promising new language, you should still learn — at least be knowledgeable in — Objective-C. One great advantage with using Objective-C: As new Xcode versions keep coming out, you rarely have to rewrite much code.