当前位置: 代码迷 >> 综合 >> iOS多线程开发(五)---GCD(二)block Grand Central Dispatch
  详细解决方案

iOS多线程开发(五)---GCD(二)block Grand Central Dispatch

热度:55   发布时间:2023-12-11 14:35:32.0

转载http://blog.csdn.net/kay_sprint/article/details/7483748

Being Objective-C objects, block objects can be treated like any other object: you can retain them, release them, and so forth. Block objects can also be called closures.

block对象就和obj-c的其他对象一样,有时也称之为闭包。


  • - (void) doTheConversion{  
  •   NSString *result =  
  •   [self convertIntToString:123  
  •           usingBlockObject:^NSString *(NSUInteger paramInteger) {  
  •     NSString *result = [NSString stringWithFormat:@"%lu",  
  •                         (unsigned long)paramInteger];  
  •     return result;  
  •   }];  
  •   NSLog(@"result = %@", result);  
  • }  

一般常用的方式是像上面这样,直接把block作为一个参数,并将实现直接写在里面,称为inline的写法。

You cannot  refer  to  self  in  independent  block  objects  implemented  in  an Objective-C class. If you need to access self, you must pass that object to the block object as a parameter.

独立定义block的函数体时不可以直接引用self,要用的话只能将self作为一个参数传递进去。


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 object has read-write access to variables defined inside the block object’s body.

对于Inlined的block对象,对内部定义的变量具有读写的操作权限。换句话说,block对象对定义在block对象体内的变量有读写操作权限



For inline block objects, variables local to the Objective-C method that implements that block can only be read from, not written to. There is an exception, though: a block object can write to such variables if they are defined with the __block storage type. 

在inline的block中,对于block外的变量,只具有读的权限,也有例外,例如:如果变量被定义为__block类型,在block对象内能够写操作这样的变量。



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 independent block object.

在独立定义的block对象体中,你能够使用你的类的声明属性,仅仅如果你使用这些属性的setter/getter方法。在一个独立block对象内,你不能用“对象圆点”(类似self.)的声明属性(在独立定义的block函数体中,对于类所定义的属性,不可以用类似self.properties的语法去操作,只可以用类似[self setProperties]的语法进行操作)。


    • typedef void (^BlockWithNoParams)(void);  
    • - (void) scopeTest{  
    •   NSUInteger integerValue = 10;  
    •   /*************** Definition of internal block object ***************/  
    •   BlockWithNoParams myBlock = ^{  
    •     NSLog(@"Integer value inside the block = %lu",  
    •           (unsigned long)integerValue);  
    •   };  
    •   /*************** End definition of internal block object ***************/  
    •   integerValue = 20;  
    •   /* Call the block here after changing the 
    •    value of the integerValue variable */  
    •   myBlock();  
    •   NSLog(@"Integer value outside the block = %lu",  
    •         (unsigned long)integerValue);  
    • }  

上面这段代码,输出会是下面这样:



  • Integer value inside the block = 10  
  • Integer value outside the block = 20  

为什么在修改了变量的值之后再调用block,出现的值还是修改之前的呢?



What’s happening here is that the block object is keeping a read-only copy of the integerValue variable for itself right where the block is implemented.

这里发生的是,为了自己的权限在block被执行的地方,这个block对象总是保持这个整型变量的只读拷贝(副本)

如果要让block可以显示修改后的值,那么就在变量定义的时候,前面加上 _block咯


  • - (void) scopeTest{  
  •   __block NSUInteger integerValue = 10;  
  •   /*************** Definition of internal block object ***************/  
  •   BlockWithNoParams myBlock = ^{  
  •     NSLog(@"Integer value inside the block = %lu",  
  •           (unsigned long)integerValue);  
  •   };  
  •   /*************** End definition of internal block object ***************/  
  •   integerValue = 20;  
  •   /* Call the block here after changing the 
  •    value of the integerValue variable */  
  •   myBlock();  
  •   NSLog(@"Integer value outside the block = %lu",  
  •         (unsigned long)integerValue);  
  • }  


这样就会显示都是20了


关于block的基本的知识差不多就是这些,更多的详细内容,大家自己找资料看咯 下面就来讲真正的powerful thing——GCD


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, asynchronously, after a certain delay, etc.

GCD使程序员可以不直接和线程打交道,而是通过把任务分配给dispatch queues,然后让这些queues去执行task.



Main Queue 
This queue performs all its tasks on the main thread, which is where Cocoa and Cocoa  Touch  require  programmers  to  call  all  UI-related  methods.  Use  the dispatch_get_main_queue function to retrieve the handle to the main queue.

主队列

这个队列在主线程上执行所有他的任务,这些任务就是Cocoa和Cocoa Touch要求程序员调用所有和UI关联的方法,通过dispatch_get_main_queue函数关联到主队列的处理。

主队列也就是对应程序的主线程,通过dispatch_get_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! Use the dispatch_get_global_queue function to retrieve the handle to a concurrent queue.

并发队列,可以异步或者同步执行任务,多个并发队列可以并发执行任务,dispatch_get_global_queue函数来获取并发队列。



Serial Queues 

These are queues that, no matter whether you submit synchronous or asynchronous 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, 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 the dispatch_queue_create function to create a serial queue. Once you are done with the queue, you must release it using the dispatch_release function.

连续队列,不管是异步还是同步,都将是按照先进先出的顺序执行队列中的任务,用dispatch_queue_create函数来创建一个连续队列,在执行完这个队列后需要用dispatch_release 函数来释放。

用两种将任务提交给GCD的方法,一种是利用前面讲的block,另一种是用C函数,后面我只讲利用block的,C函数的有兴趣的朋友自己找资料看咯



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 submitted to GCD return a value.

GCD中的block有多种形式,有些有参数,有些没有,但是他们都不可以有返回值,也就是说在GCD中的block,返回值类型都是void



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  the dispatch_get_main_queue function to get the handle to the main dispatch queue.

和UI有关的任务都必须在主线程中运行。



The dispatch_sync method cannot be called on the main queue because it will block the thread indefinitely and cause your application to deadlock. All tasks submitted to the main queue through GCD must be submitted asynchronously.

主线程中要用异步请求的方式分配task。


  • dispatch_queue_t mainQueue = dispatch_get_main_queue();  
  • dispatch_async(mainQueue, ^(void) {  
  •   [[[[UIAlertView alloc]  
  •      initWithTitle:NSLocalizedString(@"GCD", nil)  
  •      message:NSLocalizedString(@"GCD is amazing!", nil)  
  •      delegate:nil  
  •      cancelButtonTitle:NSLocalizedString(@"OK", nil)  
  •      otherButtonTitles:nil, nil] autorelease] show];  
  • });  







上面的代码显示一个alertView


when you are getting the maximum performance from GCD to do some heavy calculation on concurrent 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 UI-related work. The functions shown in this section are the only way to get out of a serial or a concurrent queue while still utilizing GCD to update your UI, so you can imagine how important it is.

这里讲到了主线程的重要性。在更新UI的时候,跳出子线程。



For any task that doesn’t involve the UI, you can use global concurrent queues in GCD.

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. 

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.

这段说到当使用同步分配运行两个block在两个不同的并发队列时,这两个任务是异步执行的,所以如果要保证任务B在任务A完成之后才开始,那么就要用同步分配的方式安排他们在同一个队列中。




看下面这段代码:


  • void (^printFrom1To1000)(void) = ^{  
  •   NSUInteger counter = 0;  
  •   for (counter = 1;  
  •        counter <= 1000;  
  •        counter++){  
  •     NSLog(@"Counter = %lu - Thread = %@",  
  •           (unsigned long)counter,  
  •           [NSThread currentThread]);  
  •   }  
  • };  



  • dispatch_queue_t concurrentQueue =   
  •         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);   
  •   
  •      dispatch_sync(concurrentQueue, printFrom1To1000);   
  •      dispatch_sync(concurrentQueue, printFrom1To1000);   



If you run this code, you might notice the counting taking place on the main thread, even though you’ve asked a concurrent queue to execute the task. It turns out this is

an optimization by GCD. The dispatch_sync function will use the current thread—the thread you’re using when you dispatch the task—whenever possible, as a part of an

optimization that has been programmed into GCD.

上面的代码中,我们已经获取了一个并发队列,并用同步分配方法执行了两次,但是我们会发现,这两个任务会被放在主线程中运行。为什么?明明我们已经创建了一个并发队列并将block放在这个队列中运行了,但却到了主线程中去运行。



关于这个:下面是苹果的官方解释

As an optimization, this function invokes the block on the current thread when possible.
—Grand Central Dispatch (GCD) Reference

上面的this function指的就是就是dispatch_sync这个方法,所以我们要知道这个原则,就是当你在方法中使用同步分配时,GCD就会把这个task放到你声明的这个方法所属的线程中去,所以上面的任务就会被放到主线程中运行,那我们要怎么来达到我们原先期望的那样,让他们在一个并发队列中执行呢?可以像下面这样来修改你的代码:


  • dispatch_queue_t concurrentQueue =   
  •         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  • dispatch_async(concurrentQueue,^{  
  •     dispatch_sync(concurrentQueue, printFrom1To1000);   
  •     dispatch_sync(concurrentQueue, printFrom1To1000);  
  • });  

先异步,然后里面再嵌同步,这样就可以保证你的同步分配的任务不会在主线程中运行了。




he first parameter of the dispatch_get_global_queue function specifies the priority of the concurrent queue that GCD has to retrieve for the programmer. The higher the
priority, the more CPU timeslices will be provided to the code getting executed on that queue.  You  can  use  any  of  these  values  for  the  first  parameter  to  the
dispatch_get_global_queue function:
DISPATCH_QUEUE_PRIORITY_LOW
Fewer timeslices will be applied to your task than normal tasks.
DISPATCH_QUEUE_PRIORITY_DEFAULT
The default system priority for code execution will be applied to your task.
DISPATCH_QUEUE_PRIORITY_HIGH
More timeslices will be applied to your task than normal tasks.

上面说的是dispatch_get_global_queue函数第一个参数的含义,至于第二个参数,被保留了,所以一直是0就对了。



This is where GCD can show its true power: executing blocks of code asynchronously on the main, serial, or concurrent queues.

下面就以 我们要从网上来下载图片并更新到我们的UI上的例子来说明应该怎么利用GCD的异步分配

代码的框架就是下面这样子


  • dispatch_queue_t concurrentQueue =  
  •   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  • dispatch_async(concurrentQueue, ^{  
  •   __block UIImage *image = nil;  
  •   dispatch_sync(concurrentQueue, ^{  
  •     /* Download the image here */  
  •   });  
  •   dispatch_sync(dispatch_get_main_queue(), ^{  
  •     /* Show the image to the user here on the main queue*/  
  •   });  
  • });  






上面的框架中,我们用个两个同步分配的方法,一个是下载图片,一个是更新图片到UI上,之所以都要用同步的,是因为这样可以保证第二个同步的会在第一个同步的运行完之后才被调用,这个顺序也是我们希望的。



GCD lets us create groups, which allow you to place your tasks in one place, run all of them, and get a notification at the end from GCD. 

可以创建一个group来运行几个任务。

下面是一个例子,假设现在我们需要跟新三个UI,一个是UITableView,一个是UIScrollView还有一个是UIImageView,那么我们可以把下面三个reload函数放在一个group里面


  • - (void) reloadTableView{  
  •   /* Reload the table view here */  
  •   NSLog(@"%s", __FUNCTION__);  
  • }  
  • - (void) reloadScrollView{  
  •   /* Do the work here */  
  •   NSLog(@"%s", __FUNCTION__);  
  • }  
  • - (void) reloadImageView{  
  •   /* Reload the image view here */  
  •   NSLog(@"%s", __FUNCTION__);  
  • }  


You should know about four functions when working with groups in GCD:


dispatch_group_create
Creates a group handle. Once you are done with this group handle, you should dispose of it using the dispatch_release function.

dispatch_group_async
Submits a block of code for execution on a group. You must specify the dispatch queue on which the block of code has to be executed as well as the group to which this block of code belongs.

dispatch_group_notify

Allows you to submit a block object that should be executed once all tasks added to the group for execution have finished their work. This function also allows you to specify the dispatch queue on which that block object has to be executed.

dispatch_release
Use this function to dispose of any dispatch groups that you create using the dispatch_group_create function.


  • dispatch_group_t taskGroup =  dispatch_group_create();  
  • dispatch_queue_t mainQueue =  dispatch_get_main_queue();  
  • /* Reload the table view on the main queue */  
  • dispatch_group_async(taskGroup, mainQueue, ^{  
  •   [self reloadTableView];  
  • });  
  • /* Reload the scroll view on the main queue */  
  • dispatch_group_async(taskGroup, mainQueue, ^{  
  •   [self reloadScrollView];  
  • });  
  • /* Reload the image view on the main queue */  
  • dispatch_group_async(taskGroup, mainQueue, ^{  
  •   [self reloadImageView];  
  • });  
  • /* At the end when we are done, dispatch the following block */  
  • dispatch_group_notify(taskGroup, mainQueue, ^{  
  •   /* Do some processing here */  
  •   [[[[UIAlertView alloc] initWithTitle:@"Finished"  
  •                                message:@"All tasks are finished"  
  •                               delegate:nil  
  •                      cancelButtonTitle:@"OK"  
  •                      otherButtonTitles:nil, nil] autorelease] show];  
  • });  
  • /* We are done with the group */  
  • dispatch_release(taskGroup);  



上面就是一个运用group的例子啦



All synchronous tasks submitted to a serial queue will be executed on the current thread being used by the code that is submitting the task, whenever possible. But asynchronous tasks submitted to a serial queue will always be executed on a thread other than the main thread.

上面这段话就说明了采用异步分配方法的话可以保证我们的task不会在主线程中运行



We’ll use the dispatch_queue_create function to create serial queues. The first parameter in this function is a C string (char *) that will uniquely identify that serial queue in the system. The reason I am emphasizing system is because this identifier is a system- wide identifier, meaning that if your app creates a new serial queue with the identifier of serialQueue1 and somebody else’s app does the same, the results of creating a new serial queue with the same name are undefined by GCD. Because of this, Apple strongly recommends that you use a reverse DNS format for identifiers. Reverse DNS identifiers are usually constructed in this way: com.COMPANY.PRODUCT.IDENTIFIER. For instance, I could create two serial queues and assign these names to them:
com.pixolity.GCD.serialQueue1
com.pixolity.GCD.serialQueue2

上面这段话说明了在创建一个连续队列时,需要注意的第一个参数的命名问题,要保证这个参数是独一无二的,不仅仅是对于本文件来说,而是要对于整个程序来说,所以Apple官方就推荐使用反DNS的格式来命名,也就像上面两个例子那样



Once you are done with the serial  dispatch  queue  that  you’ve  just  created,  you must  dispose  of  it  using  the dispatch_release function.

在用完连续队列之后,必须调用dispatch_release来释放。


写完咯,只是对GCD的一些基本知识的总结,GCD的强大之处,需要实践去体会咯

  相关解决方案