zoukankan      html  css  js  c++  java
  • Objective-C异步编程

    1. 不要阻塞主线程

    不管在进行iOS还是OS X开发中,主线程都只应该处理用户交互和界面布局,好的程序通常能够随时快速响应用户的操作,所以CPU密集型或者会阻塞线程的代码应该在其他位置去执行,我指的是其他线程。

    2. 在后台线程中执行

    为了不阻塞主线程,我们应该把更多的操作放到后台中去执行,只有在不得不在主线程中执行时(更新UI等)才回到主线程,GCD是最适合这种线程之间切换的:

    //Main Thread
    dispatch_queue_t queue;
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self renderThumbnails];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.thumbnailView setNeedsDisplay:YES];
        });
    });
    

    3. 不要阻塞太多后台线程

    如果我们要在后台线程中请求一系列的数据,然后将它们显示到界面上,你可能写出下面的代码:

    //Main Thread
    dispatch_queue_t queue;
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSURL *url in [self.imageStore URLs]) {
        dispatch_async(queue, ^{
            NSData *data = [NSData dataWithContentsOfURL:url];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.imageStore setImageData:data forURL:url];
            });
        });
    }
    

    这段代码肯定是有问题的,因为获取数据NSData *data = [NSData dataWithContentsOfURL:url];是同步的,台线程被这段代码阻塞调,系统会自动创建新的线程去执行下一个循环,最终结果会是获取多少次数据将创建了多少个后台线程。而创建线程本身是有成本的,所以如果创建太多的后台线程会占用大量的系统资源,这时应该用dispatch I/O来解决:

    //Main Thread
    for (NSURL *url in [self.imageStore URLs]) {
        dispatch_io_t io = dispatch_io_create_with_path(DISPATCH_IO_RANDOM, [[url path] fileSystemRepresentation], 0_RDONLY, 0, NULL, NULL);
        dispatch_io_set_low_water(io, SIZE_MAX);
        
        dispatch_io_read(io, 0, SIZE_MAX, dispatch_get_main_queue(), ^(bool done, dispatch_data_t data, int error) {
            [self.imageStore setImageData:data forURL:url];
        });
    }
    

    4. 与主循环(Main Runloop)结合

    通常我们一系列后台执行代码结束后,需要将结果反馈到主线程中,我们可以直接调用 dispatch_get_main_queue() 获取主线程,并在其中执行代码。

    还有一些API是带有基于runloop的回调的,如NSTimer、一些performSeletor:方法和代理方法回调,所有这些API都会默认回调函数所在的runloop,所以在使用这些API时应该知道回调方法的runloop是属于哪个线程。还应该注意两点:

    • 不要在自动分配的工作线程中调用这些API
    • 不要阻塞在main runloop中的回调函数
    - (void)downloadFromRemotePictureViewer:(NSString *)name {
        //Main Thread
        NSNetService *service = [[NSNetService alloc] initWithDomain:@"" type:@"_pictureviewer._tcp" name:name];
        [service setDelegate:self];
        [service resolveWithTimeout:5.0];
    }
    
    - (void)netServiceDidResolveAddress:(NSNetService *)service {
        [self downloadFromRemoteService:service];
    }
    

    在上面初始化和发起NetService请求都应该在主线程执行,如果你通过GCD让它在后台运行,那么它的代码回调函数是永远也不会被调用,与此类似的还有NSURLConnection。代理方法也默认是在主线程中调用的,所以为了不阻主线程,我们应该将回调里面的处理放在后台:

    - (void)netServiceDidResolveAddress:(NSNetService *)service {
        dispatch_async(self.downloadQueue, ^{
            [self downloadFromRemoteService:service];
        });
    }
    

    5. 为每个子系统对应一个队列

    通常我们应该将程序分割成多个独立的子系统,通过对应的调度队列来控制每个部分,界面部分由主队列(Main Queue)控制。

    如我们一项任务需要涉及数据下载,数据存储,视图渲染和界面展现几个流程,我们可以分别创建downloadQueuestoreQueue, renderQueue,界面展现则只需要使用“main queue”。

    - (void)netServiceDidResolveAddress:(NSNetService *)service {
    	dispatch_async(self.downloadQueue, ^{
    	    NSData *data = [self downloadFromRemoteService:service];
    	    
    	    dispatch_async(self.storeQueue, ^{
    	        int img = [self.imageStore addImage:data];
    	        
    	        dispatch_saync(self.renderQueue, ^{
    	            [self renderThumbnail:img];
    	            
    	            dispatch_async(dispatch_get_main_queue(), ^{
    	                [[self thumbnailViewForId:img] setNeedsDisplay:YES];
    	            });
    	        });
    	    });
    	});
    }
    

    6. 通过读写访问提升效率

    我们在设计读写时通常允许并发同步的的读(read),串行异步的写(write),并且读写不能同时进行。

    self.concurrentQuene = dispatch_queue_create("com.example.current", DISPATCH_QUEUE_CONCURRENT);
    
    - (id)objectAtIndex:(NSUInteger)index {
        __block id obj;
        dispatch_sync(self.concurrentQueue, ^{
           obj = [self.array objectAtIndex:index];
        });
        return obj;
    }
    
    - (void)insertObject:(id)obj atIndex:(NSUInteger)index {
        dispatch_barrier_async(self.concurrentQueue, ^{
            [self.array insertObject:obj atIndex:index];
        });
    }
    

    7. 区分控制和数据流

    调度队列(dispatch queue)并不是为一般的数据存储而设计的,它没有取消操作和随机存储,所以需要合理使用数据结构。

    假设我们有一组图片需要渲染,如果我们每渲染一张图片时都去存储队列中读取对应的数据,那个渲染队列和存储队列就会因为依赖的大大降低执行效率。我们可以合理的利用数据结构,如我们可以每次从存储队列中取多个图片然后渲染,完后再去存储队列中取,这样就大大减少了依赖,而且也避免了频繁的队列切换。

    8. 异步的更新状态

    有时候我们先知道队列中操作执行的进度,并通过状态显示出来,如通过progress view显示当前图片渲染的进度,我们可以使用GCD的dispatch source。

    //先设置接受到数据的处理(类似监听)
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_ADD, 0, 0, dispatch_get_main_queue());
    
    dispatch_source_set_event_handler(self.source, ^{
        self.progress += dispatch_source_get_data(self.source);
        [self.progressView setProgress:(self.progress/self.total) animated:YES];
    });
    dispatch_resume(self.source);
    
    //在渲染的时候将数据传递给dispatch source
    dispatch_async(self.renderQueue, ^{
        //...
        dispatch_source_merge_data(self.source, 1);
    });
    
    //可以取消掉dispatch source的处理
    dispatch_source_cancel(self.source);
    

    Posted by TracyYih - Aug 28 2013
    如需转载,请注明: 本文来自 Esoft Mobile

  • 相关阅读:
    [iOS 主要框架的总结]
    [无线传感器 网络中的节点定位技术]
    [JAVA 多种方式读取文件]
    [IOS swift对比oc]
    [IOS 静态库]
    [U3D 导出Xcode工程包,用Xcode给U3D脚本传递参数]
    [U3D 添加大地、天空,用第一视角看看自己做的世界]
    [U3D 画起重机,绑脚本和控制它运动的基本操作]
    Flutter 国际化适配
    Error:Unable to resolve dependency for ':app@releaseUnitTest/compileClasspath': Coul完美解决
  • 原文地址:https://www.cnblogs.com/tracy-e/p/3411478.html
Copyright © 2011-2022 走看看