• Understanding Objective-C Blocks


    The aim of this tutorial is to give a gentle introduction to Objective-C blocks while paying special emphasis to their syntax, as well as exploring the ideas and implementation patterns that blocks make possible.

    In my opinion, there are two main stumbling blocks (pun intended!) for beginners when attempting to truly understanding blocks in Objective-C:

    1. Blocks have a somewhat arcane and "funky" syntax. This syntax is inherited from function pointers as part of the C roots of Objective-C. If you haven't done a lot of programming in pure C, then it's likely you haven't had occasion to use function pointers, and the syntax of blocks might seem a bit intimidating.
    2. Blocks give rise to "programming idioms" based on the functional programming style that the typical developer with a largely imperative programming-style background is unfamiliar with.

    In simpler words, blocks look weird and are used in weird ways. My hope is that after reading this article, neither of these will remain true for you!

    Admittedly, it is possible to utilize blocks in the iOS SDK without an in-depth understanding of their syntax or semantics; blocks have made their way into the SDK since iOS 4, and the API of several important frameworks and classes expose methods that take blocks as parameters: Grand Central Dispatch (GCD), UIViewbased animations, and enumerating an NSArray, to name a few. All you need to do is mimic or adapt some example code and you're set.

    However, without properly understanding blocks, you'll be limited in your use of them to this method-takes-block-argument pattern, and you'll only be able to use them where Apple has incorporated them in the SDK. Conversely, a better understanding of blocks will let you harness their power and it will open the door to discovering new design patterns made possible by them that you can apply in your own code.

    Since in this tutorial we'll be discussing the core concepts of blocks, which apply to recent versions of both Mac OS X and iOS, most of the tutorial code can be run from a Mac OS X command-line project.

    To create a command line project, choose OS X > Application from the left-hand pane and choose the "Command Line Tool" option in the window that comes up when you create a new project.

    newproject

    Give your project any name. Ensure that ARC (Automatic Reference Counting) is enabled!

    ARC

    Most of the time code will be presented in fragments, but hopefully the context of the discussion will make it clear where the code fragment fits in, as you experiment with the ideas presented here.

    The exception to this is at the end of the tutorial, where we create an interesting UIView subclass, for which you obviously need to create an iOS project. You shouldn't have any problem writing a toy app to test out the class, but regardless, you'll be able to download sample code for this project if you'd like.

    Have you ever heard of a strange animal called the platypus? Wikipedia describes it as an "egg-laying, venomous, duck-billed, beaver-tailed, otter-footed mammal". Blocks are a bit like the platypus of the Objective-C world in that they share aspects of variables, objects, and functions. Let's see how:

    A "block literal" looks a lot like a function body, except for some minor differences. Here's what a block literal looks like:

    So, syntax-wise, what are the differences in comparison with function definitions?

    • The block literal is "anonymous" (i.e. nameless)
    • The caret (^) symbol 
    • We didn't have to specify the return type - the compiler can "infer" it. We could've explicitly mentioned it if we wanted to.

    There's more, but that's what should be obvious to us so far.

    Our block literal encapsulates a bunch of code. You might say this is what a function does too, and you'd be right, so in order to see what else blocks are capable of, read on!

    A block pointer lets us handle and store blocks, so that we can pass around blocks to functions or have functions return blocks - stuff that we normally do with variables and objects. If you're already adept with using function pointers, you'll immediately remark that these points apply to function pointers too, which is absolutely true. You'll soon discover that blocks are like function pointers "on steroids"! If you aren't familiar with function pointers, don't worry, I'm actually not assuming you know about them. I won't go into function pointers separately because everything that can be achieved with function pointers can be achieved with blocks - and more! - so a separate discussion of function pointers would only be repetitive and confusing.

    The above statement is key and deserves a thorough examination.

    The right-hand side is the block literal that we saw a moment ago.

    On the left side, we've created a block pointer called g. If we want to be pedantic, we'll say the '^' on the left signifies a block pointer, whereas the one on the right marks the block literal. The block pointer has to be given a "type", which is the same as the type of the block literal it points to. Let's represent this type as double (^) (double, double). Looking at the type this way, though, we should observe that the variable (f) is "ensconced" within its type, so the declaration needs be read inside out. I'll talk a bit more about the "type" of functions and blocks a bit later.

    The "pointing" is established through the assignment operator, "=".

    The above line is like a typical C statement - note the semicolon in the end! We've just defined a block literal, created a block pointer to identify it, and assigned it to point to the block. In that sense, it's similar to a statement of the following type:

    Of course, the above statement doesn't involve pointers, so beware of this when drawing semantic comparisons.

    I want to "milk" the previous comparison a bit more, just so you that you begin to see blocks in the same light as our humble char ch = 'a' statement:

    We could split the block pointer declaration and the assignment:

    We could reassign g to point to something else (although this time the analogy is with a pointer or reference to an object instance):

    Another key difference between ordinary functions and blocks is that functions need to be defined in the "global scope" of the program, meaning a function can't be defined inside the body of another function!

    A lot of the power of blocks comes from the fact that they can be defined anywhere a variable can! Compare what we just saw with functions to what we can do with blocks:

    Again, it helps to recall the char ch = 'a' analogy here; a variable assignment would ordinarily happen within the scope of a function. Except if we were defining a global variable, that is. Although we could do the same with blocks - but then there wouldn't be any practical difference between blocks and functions, so that's not very interesting!

    So far, we've only looked at how blocks are defined and how their pointers are assigned. We haven't actually used them yet. It is important that you realise this first! In fact, if you were to type in the above code into Xcode, it would complain that the variable sum is unused.

    The invocation of a block - actually using it - looks like a normal function call. We pass arguments to them through their pointers. So after the line of code we just saw, we could go:

    Actually, we can do even better than that: we could define and invoke our block all in one go.

    Observe the following:

    Perhaps the last one looked a bit like a "parlor trick" - flashy but not terribly useful - but in fact the ability to define blocks at the point of their use is one of the best things about them. If you've ever called block-accepting methods in the SDK, you've probably already encountered this use. We'll talk about this in more detail, shortly.

    Let's exercise our block syntax writing skills first. Can you declare a block pointer for a block that takes a pointer to an integer as a parameter and then returns a character?

    OK, now how about defining a block that takes no parameters, and returns no value, and encapsulates code to print "Hello, World!" to the console (upon invocation)? Assign it to a block pointer.

    Note that for a block literal that takes no parameters, the parantheses are optional.

    Question: Will the above line actually print anything to the console?

    Answer: No. We would need to call (or invoke) the block first, like this:

    Now, let's talk about functions that can take blocks as parameters or return blocks. Anything we say here applies to methods that take and return blocks, too.

    Can you write the prototype of an ordinary function that takes a block of the same type as the "Hello, world" block we defined above, and returns nothing? Recall that a prototype just informs the compiler about the signature of a function whose definition it should expect to see at some later point. For example, double sum(double, double); declares a function sum taking two double values and returning a double. However, it is sufficient to specify the type of the arguments and return value without giving them a name:

    Let's write a simple implementation (definition) for our function.

    At the risk of repeating myself too many times, func is an ordinary function so its definition must be outside the body of any other function (it can't be inside main(), for instance).

    Can you now write a small program that invokes this function in main(), passing it our "Hello, World" printing block?

    Did we have to create a block pointer first only so we could pass the block in?
    The answer is a resounding no! We could've done it inline as follows:

    Question: ff is a function whose prototype or definition you haven't seen, but it was invoked as follows. Assume that the right kind of arguments were passed in to the function. Can you guess the prototype of ff?

    This is an exercise in not getting intimidated by syntax! Assuming no warnings are generated, ff has a return type int (because its return value is being assigned to g, which is an int). So we have int f(/* mystery */) What about the parameters?

    Notice that the function is invoked with an inline block which is where the scariness comes from. Let's abstract this out and represent it by "blk". Now the statement looks like int g = ff(blk, t); Clearly, ff takes two parameters, the second one being an int (since t was an int). So we say tentatively, int ff(type_of_block, int) where we only have to work out the type of the block. To do that, recall that knowing the block type entails knowing the types of its parameters and its return type (and that's all). Clearly, block takes one parameter of type int * (pointer to int). What about the return type? Let's infer it, just like the compiler would: *x is dereferencing a pointer to an int, so that yields an int, adding one to which is also an int.

    Make sure you don't get mixed up between the meaning of * in int *x and the meaning of * in the expression *x. In the former, it means "x is a pointer to an int variable" in the context of a type declaration, while the latter retrieves the int value stored at the address x.

    So, our block returns an int. The type_of_block is thus int(^)(int *), meaning ff's prototype is:

    Question: Could we have passed in the "hello, world" printing block we created a while ago to ff?

    Answer: Of course not, its type was void(^)(void)), which is different from the type of the block that ff accepts.

    I want to digress briefly and talk a bit more about something we've been using implicitly: the "type" of a block. We defined the type of a block to be determined by the types and number of its arguments and the type of its return value. Why is this a sensible definition? First, keep in mind that a block is invoked just like a function, so let's talk in terms of functions.

    A C program is just a pool of functions that call each other: from the perspective of any function, all other functions in the programs are "black boxes". All that the calling function needs to concern itelf with (as far as syntactical correctness is concerned) is the number of arguments the "callee" takes, the types of these arguments, and the type of the value returned by the "callee". We could swap out one function body with another having the same type and the same number of arguments and the same return type, and then the calling function would be none the wiser. Conversely, if any of these were different, then we won't be able to substitute one function for another. This should convince you (if you needed convincing!) that our idea of what constitutes the type of a function or a block is the right one. Blocks reinforce this idea even more strongly. As we saw, we could pass an anonymous block to a function defined on the fly, as long as the types (as we defined them!) match.

    We could now talk about functions that return blocks! This is even more interesting. If you think about it, essentially you're writing a function that is returning code to the caller! Since the "returned code" will be in the form of a block pointer (which would be invoked exactly as a function) we'd effectively have a function that could return different functions (blocks, actually).

    Let's write a function that takes on options integer representing different types of binary operations (like addition, subtraction, multiplication, etc.) and returns a "calculator block" that you can apply to your operands in order to get the result of that operation.

    By a binary operation, we simply mean an operation that operates on two values, known as the "operands".

    Suppose we're dealing with double operands. Our calculations also return a double. What's the type of our binary operation block? Easy! It would be double(^) (double, double).

    Our function (let's called it "operation_creator") takes an int which encodes the type of operation we want it to return. So, for example, calling operation_creator(0) would return a block capable of performing addition, operation_creator(1) would give a subtraction block, etc. So operation_creator's declaration looks like return-type operation_creator(int). We just said that return-type is double (^)(double, double). How do we put these two together? The syntax gets a little hairy, but don't panic:

    Don't let this declaration get the better of you! Imagine you just came across this declaration in some code and wanted to decipher it. You could deconstruct it like this:

    1. The only identifier (name) is operation_creator. Start with that.
    2. operation_creator is a function. How do we know? It's immediately followed by an opening paranthesis (. The stuff between it and the closing paranthesis ) tells us about the number and types of parameters this function takes. There's only argument, of type int.
    3. What remains is the return type. Mentally remove operation_creator(int)from the picture, and you're left with double (^) (double, double). This is just the type of a block that takes two double values and returns a double. So, what our function operation_creator returns is a block of this type. Again, make a mental note that if the return type is a block, then the identifier is "ensconced" in the middle of it.

    Let's digress with another practice problem for you: Write the declaration of a function called blah that takes as its only parameter a block with no parameters and returns no value, and returns a block of the same type.

    If you had difficulty with this, let's break down the process: we want to define a function blah() that takes a block that takes no parameters and returns nothing (that is, void), giving us blah(void(^)(void)). The return value is also a block of type void(^)(void) so ensconce the previous bit, starting immediately after the ^, giving void(^blah(void(^)(void)))(void);.

    OK, now you can dissect or construct complex block and function declarations, but all these brackets and voids are probably making your eyes water! Directly writing out (and making sense of) these complex types and declarations is unwieldy, error prone, and involves more mental overhead than it should!

    It's time to talk about using the typedef statement, which (as you hopefully know), is a C construct that lets you hide complex types behind a name! For example:

    Gives the name IntPtrPtr to the type "pointer to pointer to int". Now you can substitute int ** anywhere in code (such as a declaration, a type cast, etc.) with IntPtrPtr.

    Let's define a type name for the block type int(^)(int, int). You can do it like this:

    This says that BlockTypeThatTakesTwoIntsAndReturnsInt is equivalent to int(^)(int, int), that is, a block of the type that takes two int values and returns an int value.

    Again, notice that the identifier (BlockTypeThatTakesTwoIntsAndReturnsInt) in the above statement, which represents the name we want to give the type we're defining, is wrapped up between the details of the type being typedef'd.

    How do we apply this idea to the blah function we just declared? The type for the parameter and the return type is the same: void(^)(void), so let's typedef this as follows:

    Now rewrite the declaration of blah simply as:

    There you go, much nicer! Right?

    At this point, it's very important that you are able to distinguish between the following two statements:

    They look the same, barring the typedef keyword, but they mean very different things.

    With (2), you've declared a block pointer variable that can point at blocks of type double(^)(double).

    With (1), you've defined a type by the name BlockType that can stand in as the type double (^)double. So, after (2), you could do something like: blkptr = ^(double x){ return 2 * x; };. And after (1), you could've actually written (2) as BlockType blkptr;(!)

    Do not proceed unless you've understood this distinction perfectly!

    Let's go back to our operation function. Can you write a definition for it? Let's typedef the block type out:

    For a cleaner implementation, you'd probably want to define an enumeration (enum type) to represent the options integer that our function accepts.

    Another example. Define a function that takes an array of integers, an integer representing the size of the array, and a block that takes an int and returns an int. The job of the function will be to apply the block (which we imagine represents a mathematical formula) on each value in the array.

    We want the type of our block to be int(^)(int). Let's typedef and then define our function:

    Let's use this in a program:

    Isn't that cool? We were able to express our mathematical formula right where we needed it!

    Add the following statements after the function call in the previous code:

    Did you see what we did there? We used the variable n which was in our block's lexical scope in the body of the block literal! This is another a tremendously useful feature of blocks (although in this trivial example it might not be obvious how, but let's defer that discussion).

    A block can "capture" variables that appear in the lexical scope of the statement calling the block. This is actually a read-only capture, so we couldn't modify n within the block's body. The value of the variable is actually copied by the block at the time of its creation. Effectively, this means if we were to change this variable at some point after the creation of the block literal but before the block's invocation, then the block would still use the "original" value of the variable, that is, the value held by the variable at the time of the block's creation. Here's a simple example of what I mean, based on the previous code:

    So, how is this ability of blocks useful? In high-level terms, it allows us to use "contextual information" in an inline block from the scope where the block is defined. It might not make sense to pass this information as a parameter to the block as it is only important in certain contexts, yet in those particular situations we do want to utilize this information in the implementation of our block.

    Let's make this idea concrete with an example. Suppose you're working on an educational app about the countries of the world, and as part of the app you want to be able to rank (i.e. sort) countries with respect to different metrics, such as population, natural resources, GDP, or any complex formula you come up with combining data you have about these countries. There are different sorting algorithms available, but most of them work on the principle of being able to compare two entities and decide which one is greater or smaller.
    Let's say you come up with the following helper method in one of your classes:

    Note that before this example we only talked about blocks taken or returned by functions, but it's pretty much the same with Objective-C methods as far as the block syntax and invocation is concerned.

    Again, you should appreciate that the ability to "plug in code" to our method in the form of a comparison block gives us the power to sort the countries according to some formula we can specify on-the-spot. All well and good. Now suppose you realise it would also be great if we could also rank the countries from lowest rank to highest. This is how you could achieve this with blocks.

    And that's it! We used the flag sortInAscendingOrder that carried contextual information about how we wanted the sort to be carried out. Because this variable was in the lexical scope of the block declaration, we were able to use its value within the block and not have to worry about the value changing before the block completes. We did not have to touch our -sortCountriesList: withComparisonBlock: to add a bool parameter to it, or touch its implementation at all! If we'd used ordinary functions, we would be writing and rewriting code all over the place!

    Let's end this tutorial by applying all we've learned here with a cool iOS application of blocks!

    If you've ever had to do any custom drawing in a UIView object, you know you have to subclass it and override its drawRect: method where you write the drawing code. You probably find it to be a chore, create an entire subclass even if all you're wanting to do is draw a simple line, plus having to look into the implementation of drawRect: whenever you need to be reminded of what the view draws. What a bore!

    Why can't we do something like this instead:

    Well, with blocks, you can! Here, view is an instance of our custom UIViewsubclass endowed with the power of drawing with blocks. Note that while we're still having to subclass UIView, we only have to do it once!

    Let's plan ahead first. We'll call our subclass BDView (BD for "block drawing"). Since drawing happens in drawRect:we want BDView's drawRect: to invoke our drawing block!. Two things to think of: (1) how does BDView hold on to the block? (2) What would be the block's type?

    Remember I mentioned way at the beginning that blocks are also like objects? Well, that means you can declare block properties!

    We haven't worked out what the block type should be, but after we do we'll typedef it as drawingblock_t!

    Why have we used copy for the storage semantics? Truthfully, in this introductory tutorial we haven't talked about what happens behind the scenes, memory-wise, when we create a block or return a block from a function. Luckily, ARC will do the right thing for us under most circumstances, saving us from having to worry about memory maangement, so for our purposes now it is enough to keep in mind that we want to use copy so that a block gets moved to the heap and doesn't disappear once the scope in which it was created ends.

    What about the type of the block? Recall that the drawRect: method takes a CGRect parameter that defines the area in which to draw, so our block should take that as a parameter. What else? Well, drawing requires the existence of a graphics context, and one is made available to us in drawRect: as the "current graphics context". If we draw with UIKit classes such as UIBezierPath, then those draw into the current graphics context implicitly. But if we choose to draw with the C-based Core Graphics API, then we need to pass in references to the graphics context to the drawing functions. Therefore, to be flexible and allow the caller to use Core Graphics functions, our block should also take a parameter of type CGContextRefwhich in BDView's drawRect: implementation we pass in the current graphics context.

    We're not interested in returning anything from our block. All it does is draw. Therefore, we can typedef our drawing block's type as:

    Now, in our -drawWithBlock: method we'll set our drawingBlock property to the passed in block and call the UIView method setNeedsDisplay which will trigger drawRect:. In drawRect:, we'll invoke our drawing block, passing it the current graphics context (returned by the UIGraphicsGetCurrentContext() function) and the drawing rectangle as parameters. Here's the complete implementation:

    So, if we had a view controller whose view property was an instance of BDView, we could call the following method from viewDidLoad (say) to draw a line that ran diagonally across the screen from the top-left corner to the bottom right corner:

    Brilliant!

    In this introductory tutorial on blocks, we've admittedly left out a few things. I'll mention a couple of these here, so you can look into them in more advanced articles and references:

    • We haven't talked much about memory semantics of blocks. Although automatic reference counting relieves us of a lot of burden in this regard, in certain scenarios more understanding is required.
    • The __block specifier that allows a block to modify a variable in its lexical scope.

    In this tutorial, we had a leisurely introduction to blocks where we paid special attention to the syntax of block declaration, assignment, and invocation. We tried to leverage what most developers already know from C regarding variables, pointers, objects, and functions. We ended with an interesting practical application of blocks that should get the wheels in your head turning and get you excited about using blocks effectively in your own code. Proper use of blocks can improve code understandability and sometimes offer very elegant solutions to problems.

    I also recommend you take a look at BlocksKit, which includes many interesting utilities of blocks that can make your life as an iOS developer easier. Happy coding!

  • 相关阅读:
    linux一些常用命令
    imageMagick
    nginx安装配置
    数据库操作(六)、Date函数
    数据库操作(五)、聚合函数,分组
    JQuery(一)
    数据库小结(二)
    数据库操作(四)、标量函数
    数据库操作(三)
    C#基础(六)
  • 原文地址:https://www.cnblogs.com/lisa090818/p/4208569.html
Copyright © 2020-2023  润新知