您的位置:首页 > 移动开发 > Objective-C

Effective Objective-C 2.0: Item 38: Create typedefs for Common Block Types

2013-12-11 22:20 429 查看


Item 38: Create typedefs for Common Block Types

Blocks have an inherent type; that is, they can be assigned to an appropriately typed variable. The type is made up of the parameters the block takes along and with the return
type of the block. For example, consider the following block:

^(BOOL flag, int value){

if (flag) {

return value * 5;

} else {

return value * 10;

}

}

This block takes two parameters of type
BOOL
and
int
and returns a value of
type
int
. If it were to be assigned to a variable, this block would need to be appropriately typed. The type and assignment to the variable would look like this:

Click here to view code image

int (^variableName)(BOOL flag, int value)
=

^(BOOL flag, int value){

// Implementation

return someInt;

}

This looks quite different from a normal type but should look familiar if you’re used to function pointers. The
layout of the type is as follows:

return_type (^block_name)(parameters)

A block-variable definition is different from other types in that the variable name is in the middle of the type rather than on the right. This makes it quite difficult to remember
the syntax and to read. For these reasons, it is a good idea to make type definitions for commonly used block types, especially if you are shipping an API that you expect others to use. However, it
is possible to hide block types like this behind a name that is easy to read and indicates what the block is meant to do.

To hide the complicated block type, you use a language feature from C called type definitions. The keyword
typedef
allows
you to define an easy-to-read name that becomes an alias for another type. For example, to define a new type for the block that returns an
int
and takes two parameters—a
BOOL
and
an
int
—you would use the following type definition:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

Just as the variable name of a block is in the
middle prefixed by a caret, so too is the new type name. This adds a new type, called
EOCSomeBlock
, to the type system. So instead of creating a
variable with a complicated type, you can simply use this new type:

Click here to view code image

EOCSomeBlock block = ^(BOOL flag, int value){

// Implementation

};

This code is now much easier to read, as the variable definition is back to the type on the left and the variable name on the right, just like you’re used to seeing with
other variables.

Using this feature can make APIs that use blocks much easier to use. Any class that takes a block as a parameter to a method—for example, a completion handler for an asynchronous
task—can make use of this feature to make it a lot easier to read. Consider, for example, a class that has a
start
method that takes a block as a handler
to be run when the task finishes. Without making use of a type definition, the method signature may look like this:

Click here to view code image

- (void)startWithCompletionHandler:

(void(^)(NSData *data, NSError *error))completion;

Note that the way the syntax for a block type is laid out is different again from that for defining a variable. It’s much easier to read if the type in the method signature is
a single word. So you can define a type definition and then use that instead:

Click here to view code image

typedef void(^EOCCompletionHandler)

(NSData *data, NSError *error);

- (void)startWithCompletionHandler:

(EOCCompletionHandler)completion;

This is now much simpler to read and understand what the parameter is. Any good, modern Integrated Development Environment (IDE) will automatically expand the type definition,
making this easy to use.

Using a type definition is also useful if you ever need to refactor to change the block’s type signature. For
example, if you decide that the completion handler block now needs to take an additional parameter to pass the time the task took, you can simply change the type definition:

Click here to view code image

typedef void(^EOCCompletionHandler)

(NSData *data, NSTimeInterval duration, NSError *error);

Anywhere the type definition is used, such as method signatures, will now fail to compile in the same way, and you can go
through and fix things. Without the type definition, you would find that you needed to change many types throughout your code. It would potentially be easy to miss one or two of these, leading to hard-to-find bugs.

It is usually best to define these type definitions along with the class that uses them. It is also
prudent to prefix the new type’s name with the class that uses the type definitions. This makes the block’s use clear. If multiple type definitions end up being created for the same block signature type, fine. It’s better to have more types than
fewer.

An example of this point can be seen in the Accounts framework from Mac OS X and iOS. This framework defines, among others, the following block type definitions:

Click here to view code image

typedef void(^ACAccountStoreSaveCompletionHandler)

(BOOL success, NSError *error);

typedef void(^ACAccountStoreRequestAccessCompletionHandler)

(BOOL granted, NSError *error);

These block type definitions have the same signature but are used in distinct places. The name of the type and the name of the parameters in the signature make it easy
for the developer to understand how the type is to be used. The developer could have defined a single type definition, perhaps called
ACAccountStoreBooleanCompletionHandler
,
used in place of both of these. However, that would lose the clarity of how the block and parameters are used.

Similarly, if you have a few classes that perform a similar but distinct asynchronous task but that don’t fit into a class hierarchy, each class should have its own completion
handler type. The signature may be exactly the same for each one, but it’s better to use a type definition for each class rather than a single one. On the other hand, if the classes fit into a hierarchy, you could define the type definition along with the
base class and have each subclass use it.


Things to Remember


Use
type definitions to make it easier to use block variables.


Always
follow the naming conventions when defining new types such that you do not clash with other types.


Don’t
be afraid to define multiple types for the same block signature. You may want to refactor one place that uses a certain block type by changing the block signature but not another.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐