Thứ Sáu, 11 tháng 4, 2014

Grand Central Dispatch for iPhone


In the previous two blog posts, we’ve looked at implementing threaded code using NSThread and NSOperation. In this post, we’ll take a look at threading using Grand Central Dispatch. The source code for this post may be downloaded here: GCD_code. There are two source examples used in this post, each is in a separate zip file inside the single download.


Grand Central Dispatch (or GCD) is a C API that allows us to dispatch either a block or C function to a dispatch queue. (In this post, we will focus on blocks as tasks.) Each queue may contain one or more such blocks, which are executed either sequentially or concurrently depending on the type of queue used. In GCD, each block or function sent to a queue is called a task. The great thing about GCD is that all details of how the threading of tasks is managed is taken care of by the system.


If we want a series of tasks to execute sequentially, we create a serial queue. This type of queue is used in GCD_Demo1. Serial dispatch queues are created using the dispatch_queue_create function to return a value of type dispatch_queue_t:

























Objective-C

















1


2




dispatch_queue_t
serialQueue

=


        
dispatch_queue_create
(
"com.gcddemo1.serialqueue"
,

NULL
)
;





The first argument to this function is an identifier for the queue, which should be in the form of a reverse – DNS lookup. Use the same string used when setting up the Bundle ID in Xcode, appending a name for the queue. The second argument is reserved for later use, just pass NULL here.


Now that we have a serial queue, we can schedule tasks to be run. In GCD_Demo1, we use a very simple interface to collect input from the user in a UITextField, then display it in a UILabel. As the user enters new strings, they are saved into an NSArray, then a series of tasks are scheduled on a serial queue to display the strings from the array one by one at two second intervals. Here is the entryEnded method from ViewController.m, which responds to the DidEndOnExit event from the text field:

























Objective-C

















1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24




-

(
IBAction
)
entryEnded
:
(
UITextField

*
)
sender


{


    
//add the new string to the entry array:


    
NSMutableArray

*entryCopy

=

[
self
.
entryArray
mutableCopy
]
;


    
[
entryCopy
addObject
:sender
.
text
]
;


    
self
.
entryArray

=

entryCopy
;


 


    
//clear the text field:


    
sender
.
text

=

@""
;


 


    
//display all the entries in the label, one by one, starting


    
//over if a new entry is recieved.


    
dispatch_queue_t
serialQueue

=


        
dispatch_queue_create
(
"com.gcddemo1.serialqueue"
,

NULL
)
;


    
for

(
NSString

*entry

in

self
.
entryArray
)

{


        
dispatch_async
(
serialQueue
,

^
{


            
dispatch_queue_t
theMainQueue

=

dispatch_get_main_queue
(
)
;


            
dispatch_async
(
theMainQueue
,

^
{


                
self
.
lbText
.
text

=

entry
;


            
}
)
;


            
sleep
(
2
)
;


        
}
)
;


    
}


}





Since serialQueue is a local variable to this method, a new queue will be created each time the method is run. Grand Central Dispatch takes care of memory management as well: when the queue no longer has tasks to be run, it is removed from memory automatically.


To dispatch blocks as tasks to a queue, we use the dispatch_async method. (There is also a dispatch_sync method that dispatches tasks synchronously, but in most cases, asynchronous dispatch is preferred, especially in serial queues.) We give this method the name of the queue, and the block to be dispatched. In this case, the block merely dispatches back the output operation to the main queue.


The main queue is a special serial queue that also runs the UI thread. If we need to do something in the user interface (like set the value of a label, for example), we must do so on the main queue. Since the main queue is a serial queue (one task executing at a time), we must take care that the tasks we dispatch to the main queue run in a short amount of time, otherwise the UI will be blocked. We obtain the main queue by setting the return value of the dispatch_get_main_queue function to a dispatch_queue_t variable. On the main queue, we set the value of the label’s text property to the current string in the array.


The last thing each task on serialQueue does is sleep for two seconds, which gives the user time to read each string in the array. Here is the full listing of ViewController.h and ViewController.m, along with the user interface:


ViewController.h:

























Objective-C

















1


2


3


4


5


6


7


8


9


10


11




#import <UIKit/UIKit.h>


 


@interface
ViewController
: UIViewController


 


@property

(
nonatomic
,

weak
)

IBOutlet

UILabel

*lbText
;


@property

(
nonatomic
,

weak
)

IBOutlet

UITextField

*tfEntry
;


@property

(
nonatomic
,

strong
)

NSArray

*entryArray
;


 


-

(
IBAction
)
entryEnded
:
(
UITextField

*
)
sender
;


 


@end





ViewController.m:

























Objective-C

















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


52


53


54


55


56




#import "ViewController.h"


 


@interface

ViewController

(
)


 


@end


 


@implementation

ViewController


 


-

(
NSArray

*
)
entryArray


{


    
if

(
!
_entryArray
)

{


        
_entryArray

=

@
[
]
;


    
}


    
return

_entryArray
;


}


 


-

(
IBAction
)
entryEnded
:
(
UITextField

*
)
sender


{


    
//add the new string to the entry array:


    
NSMutableArray

*entryCopy

=

[
self
.
entryArray
mutableCopy
]
;


    
[
entryCopy
addObject
:sender
.
text
]
;


    
self
.
entryArray

=

entryCopy
;


 


    
//clear the text field:


    
sender
.
text

=

@""
;


 


    
//display all the entries in the label, one by one, starting


    
//over if a new entry is recieved.


    
dispatch_queue_t
serialQueue

=


        
dispatch_queue_create
(
"com.gcddemo1.serialqueue"
,

NULL
)
;


    
for

(
NSString

*entry

in

self
.
entryArray
)

{


        
dispatch_async
(
serialQueue
,

^
{


            
dispatch_queue_t
theMainQueue

=

dispatch_get_main_queue
(
)
;


            
dispatch_async
(
theMainQueue
,

^
{


                
self
.
lbText
.
text

=

entry
;


            
}
)
;


            
sleep
(
2
)
;


        
}
)
;


    
}


}


 


-

(
void
)
viewDidLoad


{


    
[
super

viewDidLoad
]
;


 


    
//set up a single queue for all serial tasks to be dispached to:


 


}


 


-

(
void
)
didReceiveMemoryWarning


{


    
[
super

didReceiveMemoryWarning
]
;


    
// Dispose of any resources that can be recreated.


}


 


@end





Main.storyboard:


image001


In the second example (GCD_Demo2), we use a concurrent queue to change the value of two slider controls simultaneously. The procedure is similar, but concurrent queues are set up a bit differently.


Unlike serial queues, we don’t need to create a new concurrent queue, we just get one of the four globally available concurrent queues the systen provides for us. This is done by creating a new dispatch_queue_t variable and setting it to the return value of the dispatch_get_global_queue method, as shown:

























Objective-C

















1


2


3




//get a global concurrent queue:


    
dispatch_queue_t
conQueue

=


        
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,

0
)
;





The dispatch_get_global_queue method takes two parameters. The first is the priority of the queue. Tasks on queues with higher priorities will run before those on queues with lower priorities, but this is not a way to control execution order! If we need to control the order of execution of tasks, we should use a serial queue. (Note that we can also use other functions of GCD to control execution order in concurrent queues. See the Apple Grand Central Dispatch documentation for more information.)


The second parameter is for flags, but as no flags are currently used in this method, we pass 0.


Here is the entire goPressed method from ViewController.m, which responds to the TouchUpInside event on a UI button:

























Objective-C

















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




-

(
IBAction
)
goPressed
:
(
UIButton

*
)
sender


{


    
//get a global concurrent queue:


    
dispatch_queue_t
conQueue

=


        
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,

0
)
;


    
//get the main queue:


    
dispatch_queue_t
theMainQueue

=


        
dispatch_get_main_queue
(
)
;


 


    
//dispatch tasks to update each slider


    
//the the conQueue:


 


    
dispatch_async
(
conQueue
,

^
{


        
for

(
float

i

=

0.0
;

i

< 1.1; i += 0.1) {


            dispatch_async(theMainQueue, ^{


                self.slOne.value = i;


            });


            sleep(1);


        }


    });


 


    dispatch_async(conQueue, ^{


        for (float i = 1.0; i >

-

0.1
;

i

-
=

0.1
)

{


            
dispatch_async
(
theMainQueue
,

^
{


                
self
.
slTwo
.
value

=

i
;


            
}
)
;


            
sleep
(
1
)
;


        
}


    
}
)
;


}





The two sliders will be moved in opposite directions. The first task dispatched to the queue counts from 0 to 1, the second from 1 to 0. This is done in for-loops in each task. In each case, the main queue is used to set the value of the slider to the current value of the loop. Note that the two tasks are dispatched separately, but run at the same time. (To see the difference for yourself, try changing the concurrent queue to a sequential queue and running the app again.)


Here is the full listing of ViewController.h, ViewController.m, and the Main.storyboard:


ViewController.h:

























Objective-C

















1


2


3


4


5


6


7


8


9


10




#import <UIKit/UIKit.h>


 


@interface
ViewController
: UIViewController


 


@property

(
nonatomic
,

weak
)

IBOutlet

UISlider

*slOne
;


@property

(
nonatomic
,

weak
)

IBOutlet

UISlider

*slTwo
;


 


-

(
IBAction
)
goPressed
:
(
UIButton

*
)
sender
;


 


@end





ViewController.m:

























Objective-C

















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


52




#import "ViewController.h"


 


@interface

ViewController

(
)


 


@end


 


@implementation

ViewController


 


-

(
IBAction
)
goPressed
:
(
UIButton

*
)
sender


{


    
//get a global concurrent queue:


    
dispatch_queue_t
conQueue

=


        
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,

0
)
;


    
//get the main queue:


    
dispatch_queue_t
theMainQueue

=


        
dispatch_get_main_queue
(
)
;


 


    
//dispatch tasks to update each slider


    
//the the conQueue:


 


    
dispatch_async
(
conQueue
,

^
{


        
for

(
float

i

=

0.0
;

i

< 1.1; i += 0.1) {


            dispatch_async(theMainQueue, ^{


                self.slOne.value = i;


            });


            sleep(1);


        }


    });


 


    dispatch_async(conQueue, ^{


        for (float i = 1.0; i >

-

0.1
;

i

-
=

0.1
)

{


            
dispatch_async
(
theMainQueue
,

^
{


                
self
.
slTwo
.
value

=

i
;


            
}
)
;


            
sleep
(
1
)
;


        
}


    
}
)
;


}


 


-

(
void
)
viewDidLoad


{


    
[
super

viewDidLoad
]
;


    
// Do any additional setup after loading the view, typically from a nib.


}


 


-

(
void
)
didReceiveMemoryWarning


{


    
[
super

didReceiveMemoryWarning
]
;


    
// Dispose of any resources that can be recreated.


}


 


@end





Main.storyboard:


image002


Try dispatching tasks of your own to both serial and concurrent queues to gain further understanding of Grand Central Dispatch. This is a very powerful API: we have merely scratched the surface of its capabilities. Be sure to read the documentation from Apple!











Did you enjoy this article?








Share


the


Love







Get Free Updates









































Source : edumobile[dot]org

Không có nhận xét nào:

Đăng nhận xét