In this chapter, you will see how to use operations and operation queues, aswell as basic threads and timers, to synchronously and asynchronously execute tasks An operation object can ha
Trang 1Figure 4-13 Selecting the navigation controller in Interface Builder
Figure 4-14 Selecting a navigation controller as the initial view controller of a storyboard
As you can see, now your navigation controller has a border around it Now if you runyour application, you will notice that the initial view controller has a navigation bar on
Trang 2top, indicating that this view controller now has a navigation controller In the nextrecipes we will see how we can make use of the navigation controller to display newscenes on the screen.
Figure 4-15 The navigation bar on a view controller created with a storyboard
We now have a navigation controller with a view controller inside it but our objectivenow is to trigger an action and then move from one view controller to another Alrightthen; let's place a button on our view controller and push a view controller into thestack once the user presses the button Sounds good? Alright, let's get started:
4.2 Adding a Navigation Controller to a Storyboard | 341
Trang 31 Go back to your storyboard file.
2 In the Object Library, find the View Controller object (see Figure 4-16) and dragand drop it onto the storyboard, on the right side of our existing view controller,
as shown in Figure 4-17
Figure 4-16 A view controller object in the Object Library
Trang 4Figure 4-17 Adding a new view controller to a storyboard
3 In Object Library, find the Button object (see Figure 4-18) and drag and drop itinto the first view controller (see Figure 4-19) Note that if you are zoomed out,
Interface Builder will not allow you to drop a button onto a view controller Youneed to double click on an empty space on your storyboard to zoom into it beforeInterface Builder allows you to drop UI components onto your view controllers
Figure 4-18 Selecting the Button object in the Object Library
4.2 Adding a Navigation Controller to a Storyboard | 343
Trang 5Figure 4-19 Dropping a button on the first view controller in our storyboard
4 Now select the button, hold down the Control key on your keyboard and then holddown the left mouse button and on the button and drag all the way to the secondview controller (see Figure 4-20
Trang 6Figure 4-20 Connecting a button to another view controller in a storyboard
5 Now lift your fingers off the mouse button and the Control key on your keyboard.You will now be presented with a dialog similar to that shown in Figure 4-21.Simply click on the performSegueWithIdentifier:sender: item
Figure 4-21 Making a button perform a segue
Now if you have a look at your storyboard, you will see that the first view controller isconnected to the second view controller, as shown in Figure 4-22:
4.2 Adding a Navigation Controller to a Storyboard | 345
Trang 7Figure 4-22 The first view controller is connected to the second view controller through a segue
Now if you run your app and tap on the button on the first view controller, you'll seethat the second view controller will automatically get pushed onto the stack of viewcontrollers Once the second view controller is presented, you will see a back button
on the navigation bar If you press that button, you will be sent back to the first viewcontroller
Trang 8A segue is an object, just like any other object in Objective-C When a trasition ishappening from one scene to another, the storyboard runtime creates a segue objectfor that transition A segue is an instance of class UIStoryboardSegue When a transitionhappens, the current view controller (that will get pushed out of the screen after thesegue) will receive the prepareForSegue:sender: message where the prepareForSegueparameter will be an object of type UIStoryboardSegue If you want to pass any datafrom the current view controller to view controller which is about to appear on thescreen, you need to do that in the prepareForSegue:sender: method
For this recipe to make sense, you need to have followed the instructions
in Recipe 4.2 and created two view controllers inside a navigation
con-troller on your storyboard.
Let's implement the prepareForSegue:sender: method in the first view controller:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSLog(@"Source Controller = %@", [segue sourceViewController]);
NSLog(@"Destination Controller = %@", [segue destinationViewController]);
NSLog(@"Segue Identifier = %@", [segue identifier]);
}
If you run this app now, you will see the results in the console window However, ifyou noted, the identifier is nil Each segue has an identifier which can uniquely identifythat segue Since one scene can have more than one segue associated with it, it is good
to give your segues identifiers that you can then detect in your view controllers andtake action accordingly
A segue object in Interface Builder is the connection between two scenes Here is thesegue between my first view controller and the second:
4.3 Passing Data From One Screen to Another | 347
Trang 9Figure 4-23 Selecting a segue object in Interface Builder
Follow these steps to give your segue an identifier:
1 Select your segue object in Interface Builder by clicking on it
2 From the View menu, select Utilities→Show Attributes Inspector
3 In the Attributes Inspector, in the Identifier text field, simply write the identifierthat you would like this segue to carry with itself
You can see that when the storyboard runtime calls the prepareForSegue:sender:
meth-od in the current view controller to prepare it for the segue, the destination view troller has already been initialized in the segue object Now this is your chance to passany required data to the destination view controller You can either set the data directlyinto a property of the destination view controller or pass your data by calling a method
con-on that view ccon-ontroller; it is really up to you In this code, my seccon-ond view ccon-ontroller
is of clas SecondViewController and I've given my segue the identifier of SimpleSegueTo SecondViewController:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSLog(@"Source Controller = %@", [segue sourceViewController]);
NSLog(@"Destination Controller = %@", [segue destinationViewController]);
NSLog(@"Segue Identifier = %@", [segue identifier]);
Trang 101 From the File menu, choose New→New File
2 In the New File dialog, make sure you have selected the Resource sub-category ofthe iOS category on the left and then choose Storyboard item on the right and pressNext (see Figure 4-24
4.4 Adding a Storyboard to an Existing Project | 349
Trang 11Figure 4-24 Adding a storyboard to an existing application
3 In this screen, pick the Device Family for which you want to create your storyboard
If your app is an iPhone or iPad only app, then pick the appropriate device family
If your app is a universal app, you will need to select one device family now, createthe storyboard file and then come back and create another one for your other devicefamilt Once you are done, press the Next button
4 Now select where to save your storyboard
I had a universal app based on the Single View Application template so I have now
created two storyboard files under the names StoryboardiPhone.storyboard and boardiPad.storyboard.
Story-5 In your project structure, find the Info.plist file Note that this plist file might have
been stored under a different name For instance, I have named my project Adding
a Storyboard to an Existing Project and my Info.plist is stored under the name Adding a Storyboard to an Existing Project-Info.plist Once you click on this file,
the property list editor will open automatically
6 If you have any of these keys, delete them from the plist file:
• NSMainNibFile (might appear as Main nib file base name).
• NSMainNibFile~ipad (might appear as Main nib file base name (iPad)).
Trang 127 If you have an iPhone or iPad only app, then create a new key called UIMainStory boardFile for iPhone or UIMainStoryboardFile~ipad for iPad If you have a universalapp, create both these keys.
8 For the value of these keys, provide the filename of the storyboards that you created
without the storyboard extension.
9 Make sure to save the plist file.
10 Last but perhaps the most important part is to remove the application:didFinish LaunchingWithOptions: method from your app delegate's implementation De-pending on how your app is set up, this method might be setting up different viewcontrollers and windows and etc With storyboards, you will no longer need this
Discussion
When creating apps on older versions of Xcode or using the new versions but not usingstoryboards, the structure of our apps will be different from apps that use storyboards.For once, the storyboard-based apps no longer use a main nib file for their windows
This needs to be removed from the plist of our app The other thing to do will be, as
we saw in the Solution section, to make our app understand what our storyboard files
are by setting them in the plist.
Once all that is done, we need to make sure that our app delegate is not messing withhow we are intending to load our storyboards Every project is different and you just
need to make sure that the app delegate is not assigning any object to the rootViewCon troller property of the window If it does, your storyboards will not be displayed andyou will spend hours and hours trying to find out what the issue is The easiest solutionwould be to simply comment out the entire application:didFinishLaunchingWithOp tions: method and consider putting your initialization (for instance, initializing anydata models) in other places in your app An alternative is to simply leave this method
as it is but comment out any lines that might be changing the window's root viewcontroller object
See Also
4.4 Adding a Storyboard to an Existing Project | 351
Trang 14CHAPTER 5 Concurrency
5.0 Introduction
Concurrency is achieved when two or more tasks are executed at the same time Modernoperating systems have the ability to run tasks concurrently even on one CPU Theyachieve this by giving every task a certain time slice from the CPU For instance, if thereare 10 tasks to be executed in one second all with the same priority, the operating systemwill divide 1000 miliseconds (there are 1000 miliseconds per second) by 10 (tasks) andwill give each task 100 miliseconds of the CPU time That means all these tasks willthen be executed in the same second and they will appear to have been executed con-currently
However, with advances in technology, now we have CPUs that have more than onecore This means that the CPU is truly capable of executing tasks at the same time Theoperating system will dispatch the tasks to the CPU and will wait until they are done.That simple!
Grand Central Dispatch, or GCD for short, is a low-level C API that works with blockobjects The real use for GCD is to dispatch tasks to multiple cores without makingyou, the programmer, worry about which core is executing which task On Mac OS X,multicore devices, including laptops, have been available to users for quite some time.With the introduction of multicore devices such as the iPad 2, programmers can writeamazing multicore-aware multithreaded apps for iOS
At the heart of GCD are dispatch queues Dispatch queues, as we will see soon, arepools of threads managed by GCD on the host operating system, whether it is iOS orMac OS X You will not be working with these threads directly You will just work with
dispatch queues, dispatching tasks to these queues and asking the queues to invoke
your tasks GCD offers several options for running tasks: synchronously, nously, after a certain delay, etc
asynchro-To start using GCD in your apps, you don’t have to import any special library into yourproject Apple has already incorporated GCD into various frameworks, including CoreFoundation and Cocoa/Cocoa Touch All methods and data types available in GCD
353
Trang 15start with a dispatch_ keyword For instance, dispatch_async allows you to dispatch atask on a queue for asynchronous execution, whereas dispatch_after allows you to run
a block of code after a given delay
Traditionally, programmers had to create their own threads to perform tasks in parallel.For instance, an iOS developer would create a thread similar to this to perform anoperation 1000 times:
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t numberOfIterations = 1000;
dispatch_async(queue, ^(void) {
dispatch_apply(numberOfIterations, queue, ^(size_t iteration){
/* Perform the operation here */
Trang 16});
});
In this chapter, you will learn all there is to know about GCD and how to use it to writemodern multithreaded apps for iOS and Mac OS X that will achieve blazing perform-ance on multicore devices such as the iPad 2
We will be working with dispatch queues a lot, so please make sure that you fullyunderstand the concept behind them There are three types of dispatch queues:
Main Queue
This queue performs all its tasks on the main thread, which is where Cocoa andCocoa Touch require programmers to call all UI-related methods Use thedispatch_get_main_queue function to retrieve the handle to the main queue
Concurrent Queues
These are queues that you can retrieve from GCD in order to execute asynchronous
or synchronous tasks Multiple concurrent queues can be executing multiple tasks
in parallel, without breaking a sweat No more thread management, yippee! Usethe dispatch_get_global_queue function to retrieve the handle to a concurrentqueue
Serial Queues
These are queues that, no matter whether you submit synchronous or nous tasks to them, will always execute their tasks in a first-in-first-out (FIFO)fashion, meaning that they can only execute one block object at a time However,
asynchro-they do not run on the main thread and therefore are perfect for a series of tasks
that have to be executed in strict order without blocking the main thread Use thedispatch_queue_create function to create a serial queue Once you are done withthe queue, you must release it using the dispatch_release function
At any moment during the lifetime of your application, you can use multiple dispatchqueues at the same time Your system has only one main queue, but you can create asmany serial dispatch queues as you want, within reason, of course, for whatever func-tionality you require for your app You can also retrieve multiple concurrent queuesand dispatch your tasks to them Tasks can be handed to dispatch queues in two forms:block objects or C functions, as we will see in Recipe 5.4
Block objects are packages of code that usually appear in the form of methods in
Objective-C Block objects, together with Grand Central Dispatch (GCD), create aharmonious environment in which you can deliver high-performance multithreadedapps in iOS and Mac OS X What’s so special about block objects and GCD, you mightask? It’s simple: no more threads! All you have to do is to put your code in block objectsand ask GCD to take care of the execution of that code for you
Block objects in Objective-C are what the programming field calls first-class objects.
This means you can build code dynamically, pass a block object to a method as aparameter, and return a block object from a method All of these things make it easier
to choose what you want to do at runtime and change the activity of a program In
5.0 Introduction | 355
Trang 17particular, block objects can be run in individual threads by GCD Being Objective-Cobjects, block objects can be treated like any other object.
Block objects are sometimes referred to as closures.
Constructing block objects is similar to constructing traditional C functions, as we willsee in Recipe 5.1 Block objects can have return values and can accept parameters Blockobjects can be defined inline or treated as a separate block of code, similar to a Cfunction When created inline, the scope of variables accessible to block objects isconsiderably different from when a block object is implemented as a separate block ofcode
GCD works with block objects When performing tasks with GCD, you can pass ablock object whose code can get executed synchronously or asynchronously, depend-ing on which methods you use in GCD Thus, you can create a block object that isresponsible for downloading a URL passed to it as a parameter That single block objectcan then be used in various places in your app synchronously or asynchronously, de-pending on how you would like to run it You don’t have to make the block objectsynchronous or asynchronous per se; you will simply call it with synchronous or asyn-
chronous GCD methods and the block object will just work.
Block objects are quite new to programmers writing iOS and OS X apps In fact, blockobjects are not as popular as threads yet, perhaps because their syntax is a bit differentfrom pure Objective-C methods and more complicated Nonetheless, block objects areenormously powerful and Apple is making a big push toward incorporating them intoApple libraries You can already see these additions in classes such as NSMutableArray,where programmers can sort the array using a block object
This chapter is dedicated entirely to constructing and using block objects in iOS andMac OS X apps, using GCD for dispatching tasks to the operating system, threads andtimers I would like to stress that the only way to get used to block objects’ syntax is
to write a few of them for yourself Have a look at the sample code in this chapter andtry implementing your own block objects
Here, you will learn the basics of block objects, followed by some more advanced jects such as Grand Central Dispatch, Threads, Timers, Operations and OperationQueues You will understand everything you need to know about block objects beforemoving to the Grand Central Dispatch material From my experience, the best way tolearn block objects is through examples, so you will see a lot of them in this chapter
sub-Make sure you try the examples for yourself in Xcode to really get the syntax of block
objects
Trang 18Operations can be configured to run a block of code synchronously or asynchronously.
You can manage operations manually or place them on operation queues, which
facil-itate concurrency so that you do not need to think about the underlying thread agement In this chapter, you will see how to use operations and operation queues, aswell as basic threads and timers, to synchronously and asynchronously execute tasks
An operation object can have dependencies on other operation objects and be ted to wait for the completion of one or more operations before executing the taskassociated with it Unless you add a dependency, you have no control over the order
instruc-in which operations run For instruc-instance, addinstruc-ing them to a queue instruc-in a certainstruc-in order does
not guarantee that they will execute in that order, despite the use of the term queue.
There are a few important things to bear in mind while working with operation queuesand operations:
• Operations, by default, run on the thread that starts them, using their start stance method If you want the operations to work asynchronously, you will have
in-to use either an operation queue or a subclass NSOperation and detach a new thread
on the main instance method of the operation
• An operation can wait for the execution of another operation to finish before itstarts its execution Be careful not to create interdependent operations, a common
mistake known as a race condition In other words, do not tell operation A to
de-pend on operation B if B already dede-pends on A; this will cause both to wait forever,taking up memory and possibly hanging your application
• Operations can be cancelled So, if you have subclassed NSOperation to create tom operation objects, you have to make sure to use the isCancelled instancemethod to check whether the operation has been cancelled before executing thetask associated with the operation For instance, if your operation’s task is to checkfor the availability of an Internet connection every 20 seconds, it must call theisCancelled instance method at the beginning of each run to make sure it has not
cus-5.0 Introduction | 357
Trang 19been cancelled before attempting to check for an Internet connection again If theoperation takes more than a few seconds (such as when you download a file), youshould also check isCancelled periodically while running the task.
• Operation objects are key-value observing (KVO) compliant on various key pathssuch as isFinished, isReady, and isExecuting We will be discussing Key ValueCoding and Key Value Observing in a later chapter
• If you plan to subclass NSOperation and provide a custom implementation for theoperation, you must create your own autorelease pool in the main method ofthe operation, which gets called from the start method We will discuss this indetail later in this chapter
• Always keep a reference to the operation objects you create The concurrent nature
of operation queues might make it impossible for you to retrieve a reference to anoperation after it has been added to the queue
Threads and timers are objects, subclassing NSObject Threads are more low-level thantimers When an application runs under iOS, the operating system creates at least onethread for that application, called the main thread Every thread and timer must beadded to a run loop A run loop, as its name implies, is a loop during which differentevents can occur, such as a timer firing or a thread running Discussion of run loops isbeyond the scope of this chapter, but we will refer to them here and there in recipes.Think of a run loop as a kind of loop that has a starting point, a condition for finishing,and a series of events to process during its lifetime A thread or timer is attached to arun loop and in fact requires a run loop to function
The main thread of an application is the thread that handles the UI events If youperform a long-running task on the main thread, you will notice that the UI of yourapplication will become unresponsive or slow to respond To avoid this, you can createseparate threads and/or timers, each of which performs its own task (even if it is a long-running task) but will not block the main thread
5.1 Constructing Block Objects
Trang 20Block objects can either be inline or coded as independent blocks of code Let’s startwith the latter type Suppose you have a method in Objective-C that accepts two integervalues of type NSInteger and returns the difference of the two values, by subtractingone from the other, as an NSInteger:
NSInteger subtract(NSInteger paramValue, NSInteger paramFrom){
return paramFrom - paramValue;
}
You can see that the C function is quite different in syntax from its Objective-C terpart Now let’s have a look at how we could code the same function as a block object:
coun-NSInteger (^subtract)(coun-NSInteger, coun-NSInteger) =
^(NSInteger paramValue, NSInteger paramFrom){
return paramFrom - paramValue;
};
Before I go into details about the syntax of block objects, let me show you a few moreexamples Suppose we have a function in C that takes a parameter of type NSUInteger(an unsigned integer) and returns it as a string of type NSString Here is how we im-plement this in C:
NSString* intToString (NSUInteger paramInteger){
return [NSString stringWithFormat:@"%lu",
(unsigned long)paramInteger];
}
To learn about formatting strings with system-independent format
specifiers in Objective-C, please refer to String Programming Guide, iOS
Developer Library on Apple’s website.
The block object equivalent of this C function is shown in Example 5-1
5.1 Constructing Block Objects | 359
Trang 21Example 5-1 Example block object defined as function
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
Now that we know how to write block objects as independent blocks of code, let’s have
a look at passing block objects as parameters to Objective-C methods We will have tothink a bit abstractly to understand the goal of the following example
Suppose we have an Objective-C method that accepts an integer and performs somekind of transformation on it, which may change depending on what else is happening
in our program We know that we’ll have an integer as input and a string as output,but we’ll leave the exact transformation up to a block object that can be different eachtime our method runs This method, therefore, will accept as parameters both the in-teger to be transformed and the block that will transform it
For our block object, we’ll use the same intToString block object that we implementedearlier in Example 5-1 Now we need an Objective-C method that will accept an un-signed integer parameter and a block object as its parameter The unsigned integerparameter is easy, but how do we tell our method that it has to accept a block object
Trang 22of the same type as the intToString block object? First we typedef the signature of theintToString block object, which tells the compiler what parameters our block objectshould accept:
typedef NSString* (^IntToStringConverter)(NSUInteger paramInteger);
This typedef just tells the compiler that block objects that accept an integer parameterand return a string can simply be represented by an identifier named IntToString Converter Now let’s go ahead and write our Objective-C method that accepts both aninteger and a block object of type IntToStringConverter:
of the doTheConversion method (Example 5-3)
Example 5-3 Example block object defined as function
- (void) doTheConversion{
IntToStringConverter inlineConverter = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
Trang 23NSLog(@"result = %@", result);
}
Compare Example 5-3 to the earlier Example 5-1 I have removed the initial code thatprovided the block object’s signature, which consisted of a name and argument,(^intToString)(NSUInteger) I left all the rest of the block object intact It is now ananonymous object But this doesn’t mean I have no way to refer to the block object Iassign it using an equal sign to a type and a name: IntToStringConverter inline Converter Now I can use the data type to enforce proper use in methods, and use thename to actually pass the block object
In addition to constructing block objects inline as just shown, we can construct a block
object while passing it as a parameter:
- (void) doTheConversion{
NSString *result =
[self convertIntToString:123
usingBlockObject:^NSString *(NSUInteger paramInteger) {
NSString *result = [NSString stringWithFormat:@"%lu",
Trang 24Here is a brief summary of what you must know about variables in block objects:
• Local variables in block objects work exactly the same as in Objective-C methods
• For inline block objects, local variables constitute not only variables defined withinthe block, but also the variables that have been defined in the method that imple-ments that block object (Examples will come shortly.)
• You cannot refer to self in independent block objects implemented in anObjective-C class If you need to access self, you must pass that object to the blockobject as a parameter We will see an example of this soon
• You can refer to self in an inline block object only if self is present in the lexicalscope inside which the block object is created
• For inline block objects, local variables that are defined inside the block object’s
implementation can be read from and written to In other words, the block objecthas read-write access to variables defined inside the block object’s body
• For inline block objects, variables local to the Objective-C method that implementsthat block can only be read from, not written to There is an exception, though: ablock object can write to such variables if they are defined with the block storagetype We will see an example of this as well
• Suppose you have an object of type NSObject and inside that object’s tation you are using a block object in conjunction with GCD Inside this blockobject, you will have read-write access to declared properties of that NSObject insidewhich your block is implemented
implemen-• You can access declared properties of your NSObject inside independent block
ob-jects only if you use the setter and getter methods of these properties You cannot
access declared properties of an object using dot notation inside an independentblock object
Trang 25Invoking this block object, the values we assigned are printed to the console window:
NSLog(@"Outside variable = %lu", (unsigned long)outsideVariable);
NSLog(@"Inside variable = %lu", (unsigned long)insideVariable);
/* Return value for our block object */
return NSOrderedSame;
}];
}
The sortUsingComparator: instance method of NSMutableArray attempts
to sort a mutable array The goal of this example code is just to
dem-onstrate the use of local variables, so you don’t have to know what this
method actually does.
The block object can read and write its own insideVariable local variable However,the block object has read-only access to the outsideVariable variable by default Inorder to allow the block object to write to outsideVariable, we must prefix outside Variable with the block storage type:
- (void) simpleMethod{
block NSUInteger outsideVariable = 10;
NSMutableArray *array = [[NSMutableArray alloc]
Trang 26NSLog(@"Outside variable = %lu", (unsigned long)outsideVariable);
NSLog(@"Inside variable = %lu", (unsigned long)insideVariable);
/* Return value for our block object */
You cannot, without a change in your block object’s implementation, access self in
an independent block object Attempting to compile this code will give you a time error:
Trang 27You don’t have to assign the name self to this parameter You can
sim-ply call this parameter anything else However, if you call this parameter
self , you can simply grab your block object’s code later and place it in
an Objective-C method’s implementation without having to change
ev-ery instance of your variable’s name to self for it to be understood by
the compiler.
Let’s have a look at declared properties and how block objects can access them Forinline block objects, you can use dot notation to read from or write to declared prop-erties of self For instance, let’s say we have a declared property of type NSString calledstringProperty in our class:
#import <UIKit/UIKit.h>
@interface GCDAppDelegate : NSObject <UIApplicationDelegate>
@property (nonatomic, strong) NSString *stringProperty;
self.stringProperty = @"Block Objects";
NSLog(@"String property = %@", self.stringProperty);
/* Return value for our block object */
Trang 28void (^correctBlockObject)(id) = ^(id self){
NSLog(@"self = %@", self);
/* Should use setter method instead of this */
self.stringProperty = @"Block Objects"; /* Compile-time Error */
/* Should use getter method instead of this */
/* This will work fine */
[self setStringProperty:@"Block Objects"];
/* This will work fine as well */
NSLog(@"self.stringProperty = %@",
[self stringProperty]);
};
When it comes to inline block objects, there is one very important rule that you have
to remember: inline block objects copy the value for the variables in their lexical scope
If you don’t understand what that means, don’t worry Let’s have a look at an example:
typedef void (^BlockWithNoParams)(void);
/* Call the block here after changing the
value of the integerValue variable */
Trang 29We are declaring an integer local variable and initially assigning the value of 10 to it.
We then implement our block object but don’t call the block object yet After the block object is implemented, we simply change the value of the local variable that the block
object will later try to read when we call it Right after changing the local variable’svalue to 20, we call the block object You would expect the block object to print thevalue 20 for the variable, but it won’t It will print 10, as you can see here:
Integer value inside the block = 10
Integer value outside the block = 20
What’s happening here is that the block object is keeping a read-only copy of theintegerValue variable for itself right where the block is implemented You might be
thinking: why is the block object capturing a read-only value of the local variable
integerValue? The answer is simple, and we’ve already learned it in this section Unlessprefixed with storage type block, local variables in the lexical scope of a block objectare just passed to the block object as read-only variables Therefore, to change thisbehavior, we could change the implementation of our scopeTest method to prefix theintegerValue variable with block storage type, like so:
- (void) scopeTest{
block NSUInteger integerValue = 10;
/*************** Definition of internal block object ***************/
/* Call the block here after changing the
value of the integerValue variable */
myBlock();
NSLog(@"Integer value outside the block = %lu",
(unsigned long)integerValue);
}
Now if we get the results from the console window after the scopeTest method is called,
we will see this:
Integer value inside the block = 20
Integer value outside the block = 20
This section should have given you sufficient information about using variables withblock objects I suggest that you write a few block objects and use variables inside them,assigning to them and reading from them, to get a better understanding of how blockobjects use variables Keep coming back to this section if you forget the rules that governvariable access in block objects
Trang 30void (^simpleBlock)(NSString *) = ^(NSString *paramString){
/* Implement the block object here and use the
/*************** Definition of first block object ***************/
NSString *(^trimString)(NSString *) = ^(NSString *inputString){
NSString *result = [inputString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];
return result;
};
/*************** End definition of first block object ***************/
/*************** Definition of second block object ***************/
NSString *(^trimWithOtherBlock)(NSString *) = ^(NSString *inputString){
return trimString(inputString);
5.3 Invoking Block Objects | 369
Trang 31/*************** End definition of second block object ***************/
- (void) callTrimBlock{
NSString *trimmedString = trimWithOtherBlock(@" O'Reilly ");
NSLog(@"Trimmed string = %@", trimmedString);
There are two ways to submit tasks to dispatch queues:
• Block Objects (see Recipe 5.1)
• C functions
Discussion
Block objects are the best way of utilizing GCD and its enormous power Some GCDfunctions have been extended to allow programmers to use C functions instead of blockobjects However, the truth is that only a limited set of GCD functions allow program-mers to use C functions, so please do read the recipe about block objects (Recipe 5.1)before proceeding any further
C functions that have to be supplied to various GCD functions should be of typedispatch_function_t, which is defined as follows in the Apple libraries:
typedef void (*dispatch_function_t)(void *);
So if we want to create a function named, for instance, myGCDFunction, we would have
to implement it in this way:
Trang 32void myGCDFunction(void * paraContext){
/* Do the work here */
}
The paraContext parameter refers to the context that GCD allows
pro-grammers to pass to their C functions when they dispatch tasks to them.
We will learn about this shortly.
Block objects that get passed to GCD functions don’t always follow the same structure.Some must accept parameters and some shouldn’t, but none of the block objects sub-mitted to GCD return a value
In the next three sections you will learn how to submit tasks to GCD for executionwhether they are in the form of block objects or C functions
UI-related tasks have to be performed on the main thread, so the main queue
is the only candidate for UI task execution in GCD We can use thedispatch_get_main_queue function to get the handle to the main dispatch queue.There are two ways of dispatching tasks to the main queue Both are asynchronous,letting your program continue even when the task is not yet executed:
dispatch_async function
Executes a block object on a dispatch queue
dispatch_async_f function
Executes a C function on a dispatch queue
5.5 Performing UI-Related Tasks with GCD | 371
Trang 33The dispatch_sync method cannot be called on the main queue because
it will block the thread indefinitely and cause your application to
dead-lock All tasks submitted to the main queue through GCD must be
sub-mitted asynchronously.
Let’s have a look at using the dispatch_async function It accepts two parameters:
Dispatch queue handle
The dispatch queue on which the task has to be executed
Block object
The block object to be sent to the dispatch queue for asynchronous execution.Here is an example This code will display an alert, in iOS, to the user, using the mainqueue:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
As you’ve noticed, the dispatch_async GCD function has no parameters
or return value The block object that is submitted to this function must
gather its own data in order to complete its task In the code snippet
that we just saw, the alert view has all the values that it needs to finish
its task However, this might not always be the case In such instances,
you must make sure the block object submitted to GCD has access in
its scope to all the values that it requires.
Running this app in iOS Simulator, the user will get results similar to those shown in
Figure 5-1
Trang 34Figure 5-1 An alert displayed using asynchronous GCD calls
This might not be that impressive In fact, it is not impressive at all if you think about
it So what makes the main queue truly interesting? The answer is simple: when youare getting the maximum performance from GCD to do some heavy calculation onconcurrent or serial threads, you might want to display the results to your user or move
a component on the screen For that, you must use the main queue, because it is related work The functions shown in this section are the only way to get out of a serial
UI-or a concurrent queue while still utilizing GCD to update your UI, so you can imaginehow important it is
5.5 Performing UI-Related Tasks with GCD | 373
Trang 35Instead of submitting a block object for execution on the main queue, you can submit
a C function object Submit all UI-related C functions for execution in GCD to thedispatch_async_f function We can get the same results as we got in Figure 5-1, using
C functions instead of block objects, with a few adjustments to our code
As mentioned before, with the dispatch_async_f function, we can submit a pointer to
an application-defined context, which can then be used by the C function that getscalled So here is the plan: let’s create a structure that holds values such as an alertview’s title, message, and cancel-button’s title When our app starts, we will put all thevalues in this structure and pass it to our C function to display Here is how we aredefining our structure:
void displayAlertView(void *paramContext){
The reason we are free ing the context passed to us in here instead of in
the caller is that the caller is going to execute this C function
asynchro-nously and cannot know when our C function will finish executing.
Therefore, the caller has to malloc enough space for the AlertViewData
context and our displayAlertView C function has to free that space.
Trang 36And now let’s call the displayAlertView function on the main queue and pass the text (the structure that holds the alert view’s data) to it:
con (BOOL) application:(UIApplication *)application
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
- (BOOL) application:(UIApplication *)application
NSLog(@"Current thread = %@", [NSThread currentThread]);
NSLog(@"Main thread = %@", [NSThread mainThread]);
});
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
The output of this code would be similar to that shown here:
Current thread = <NSThread: 0x4b0e4e0>{name = (null), num = 1}
Main thread = <NSThread: 0x4b0e4e0>{name = (null), num = 1}
5.5 Performing UI-Related Tasks with GCD | 375
Trang 37Now that you know how to perform UI-related tasks using GCD, it is time we moved
to other subjects, such as performing tasks in parallel using concurrent queues (see
Recipe 5.6 and Recipe 5.7) and mixing our code with UI-related code if need be
There are times when you want to perform tasks that have nothing to do with the UI
or interact with the UI as well as doing other tasks that take up a lot of time For instance,you might want to download an image and display it to the user after it is downloaded.The downloading process has absolutely nothing to do with the UI
For any task that doesn’t involve the UI, you can use global concurrent queues in GCD.These allow either synchronous or asynchronous execution But synchronous
execution does not mean your program waits for the code to finish before continuing.
It simply means that the concurrent queue will wait until your task has finished before
it continues to the next block of code on the queue When you put a block object on a
concurrent queue, your own program always continues right away without waiting for
the queue to execute the code This is because concurrent queues, as their name implies,run their code on threads other than the main thread (There is one exception to this:when a task is submitted to a concurrent or a serial queue using the dispatch_sync
function, iOS will, if possible, run the task on the current thread, which might be the
main thread, depending on where the code path is at the moment This is an zation that has been programmed on GCD, as we shall soon see.)
optimi-If you submit a task to a concurrent queue synchronously, and at the same time submit
another synchronous task to another concurrent queue, these two synchronous tasks will run asynchronously in relation to each other because they are running two different concurrent queues It’s important to understand this because sometimes, as we’ll see,
you want to make sure task A finishes before task B starts To ensure that, submit them
synchronously to the same queue.
Trang 38You can perform synchronous tasks on a dispatch queue using the dispatch_sync tion All you have to do is to provide it with the handle of the queue that has to run thetask and a block of code to execute on that queue.
func-Let’s look at an example It prints the integers 1 to 1000 twice, one complete sequenceafter the other, without blocking the main thread We can create a block object thatdoes the counting for us and synchronously call the same block object twice:
As an optimization, this function invokes the block on the current thread when possible.
—Grand Central Dispatch (GCD) Reference
To execute a C function instead of a block object, synchronously, on a dispatch queue,use the dispatch_sync_f function Let’s simply translate the code we’ve written for theprintFrom1To1000 block object to its equivalent C function, like so:
void printFrom1To1000(void *paramContext){
Trang 39More timeslices will be applied to your task than normal tasks.
The second parameter of the dispatch_get_global_queue function is
re-served and you should always pass the value 0 to it.
In this section you saw how you can dispatch tasks to concurrent queues for nous execution The next section shows asynchronous execution on concurrentqueues, while Recipe 5.11 will show how to execute tasks synchronously and asyn-chronously on serial queues that you create for your applications
synchro-See Also
XXX
Trang 405.7 Performing Non-UI Related Tasks Asynchronously with GCD
Problem
You want to be able to execute non-UI related tasks asynchronously, with the help ofGCD
Solution
This is where GCD can show its true power: executing blocks of code asynchronously
on the main, serial, or concurrent queues I promise that, by the end of this section,you will be completely convinced GCD is the future of multithread applications, com-pletely replacing threads in modern apps
In order to execute asynchronous tasks on a dispatch queue, you must use one of thesefunctions:
so far about GCD in order to accomplish it:
1 We are going to launch a block object asynchronously on a concurrent queue
2 Once in this block, we will launch another block object synchronously, using the
dispatch_sync function, to download the image from a URL Synchronously loading a URL from an asynchronous code block holds up just the queue runningthe synchronous function, not the main thread The whole operation still is asyn-chronous when we look at it from the main thread’s perspective All we care about
down-is that we are not blocking the main thread while downloading our image
3 Right after the image is downloaded, we will synchronously execute a block object
on the main queue (see Recipe 5.5) in order to display the image to the user on theUI
The skeleton for our plan is as simple as this:
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
5.7 Performing Non-UI Related Tasks Asynchronously with GCD | 379