iPad app应用开发系列文章之三 -- iOS的多核编程和内存管理

摘要:
距离我上次写一篇关于iPad应用程序开发的文章已经过去了10个月。iPad应用程序开发概况不错,它已经成为谷歌关键词“iPad应用程序发展”的第一个搜索。可能是因为丹尼尔太忙了,无法从应用商店赚钱,所以我只能写这篇文章。本文重点介绍iOS的多核编程和内存管理。为什么?因为iPad2已经是双核CPU了!尽管iPad 1的应用速度并不慢,但您可以使用苹果的多核编程框架来编写响应速度更快的应用程序。维护线程是GCD中的主队列。

隔上一次写iPad app开发文章已经是10个月,那篇iPad app开发概述还不错,曾经成为了google关键字“iPad app 开发”搜索的第一位,可能是大牛们都太忙于赚app store的钱了,留下我这个小虾来写文章。这次的文章集中与iOS的多核编程和内存管理,为什么?因为iPad 2已经是双核CPU了!虽然iPad 1的应用已经不慢了,但大家完全可以使用苹果的多核编程框架来写出更加responsive的应用。

多核运算

在iOS中concurrency编程的框架就是GCD(Grand Central Dispatch), GCD的使用非常简单。它把任务分派到不同的queue队列来处理。开发者把任务代码装到一个个block里面,操作系统把这些任务代码分派到不同的资源里去处理,一个简单的例子来说,为什么初学者写tableview的时候,滑动列表时总会很卡,因为很多初学者把图片装载放到main thread主线程去执行,例如我们要滑动畅顺的话,iOS最快可以1秒内刷新60次,如何你的一个cell的文字和图片装载超过1/60秒的话,自然就会卡。所以一般我们会把图片装载这些需要多点时间的移出main thread来处理,对于用GCD来说,就是把图片载入放到另外一个queue队列中来异步执行,当资源准备好了后,放回到main thread中显示出来。main thread在GCD中就是main queue。

创建一个新queue队列的代码:

C代码  收藏代码
  1. dispatch_queue_t network_queue;  
  2.   
  3. network_queue = dispatch_queue_create("com.myapp.network", nill);  

例如,我们图片读取放到network_queue来进行异步执行

C代码  收藏代码
  1. dispatch_async(network_queue, ^{    
  2.     UIImage *cellImage = [self loadMyImageFromNetwork:image_url];    
  3.     // 将图片cache到本地    
  4.     [self cacheImage:cellImage];    
  5.     
  6.     .....    
  7.         
  8. } );  

dispatch_async的意思就是将任务进行异步并行处理,不一定需要一个任务处理完后才能处理下一个。以上代码loadMyImageFromNetwork的意思就是从网络中读取图片,这个任务交给network_queue来处理。这样读取图片的时间过长也不会阻塞主线程界面的处理。

当我们处理完图片后,应该更新界面,从queue的概念去设计,就是要将更新界面的代码放到main queue中去,因为iOS里面永远是主线程来刷新重画UI。所以代码应该为,

C代码  收藏代码
  1. dispatch_async(network_queue, ^{    
  2.     UIImage *cellImage = [self loadMyImageFromNetwork:image_url];    
  3.     // 将图片cache到本地    
  4.     [self cacheImage:cellImage];    
  5.     
  6.    // 回到主线程    
  7.    dispatch_async(dispatch_get_main_queue(), ^{    
  8.       // 显示图片到界面    
  9.       [self displayImageToTableView:cellImage];    
  10.    }];    
  11.         
  12. } );  

dispatch_get_main_queue() 函数就是返回主线程,^{} 封装的就是任务代码,这样嵌套方式就可以从一个队列queue,跳到另一个queue,就是这么简单。

我们一般可以把networking有关的代码放到一个queue,把图片resize的代码放到另外一个queue,处理完后更新界面,只需要嵌套跳回到 main queue。这样加上几行代码,你的程序就可以利用到系统多核资源,把具体的调度工作交给了操作系统自己来分配。有了这样的代码,不管你的硬件是单核,双核还是iMac的4核,甚至8核,都可以非常好地并行处理。

内存管理

我一直惊叹iOS和Objective-C内存处理能力,例如iPad版本的iWork,Pages应用就是一个内存处理技术应用的鬼斧神工之作。想想256M内存的iPad,可以带来如此的华丽的界面同时获得如此流畅的用户体验,真是不简单。原因就是iOS一直提倡开发者在有限硬件资源内写出最优化的代码,使用CPU最少,占用内存最小。

(以下代码适用于iOS SDK 4.1, 由于新SDK 4.2对内存使用有新改动,所以可能有不同。。。)

1. 尽量少的UIView层

通常我们喜欢把很多控件层(UILabel,UIButton,UIView等)一起放到一个大的UIView容器来显示我们的内容,这个方法一般是可以的,但是如果要经常重新刷新内容的大区域界面,多数发生在iPad的应用中,这个方法会带来过多的内存使用和动画的延迟(比较卡),例如,scrollview的动画比较卡,又或者,经常收到内存警告。其中一个重要原因是每个控件,特别是透明底的,会多次重新绘制(drawRect)过多。其解决办法是,尽量将几个控件合并到一个层上来显示,这样系统会减少系统调用drawRect,从而带来性能上的提升。

很简单的一个例子,就是iNotes提供手写功能,用户可以在iPad屏幕上写出不同的笔画,开始的设计是,用户每写一划,iNotes就会生成一个新的透明底UIView来保持这个笔画,用户写了10笔,系统就生产了10个UIView,每个view的大小都是整个屏幕的,以便用户的undo操作。这个方案带来严重的内存问题,因为系统将每个层都保持一个bitmap图,一个像素需要4bit来算,一个层的大小就是 4x1024x768 ~ 3M, 10个层就是 10x3M = 30M,很明显,iPad很快爆出内存警告。

这个例子最后的方案是,所有笔画都画在同一个层,iNotes可以保存笔画的点进行undo操作。这样的方案就是无论用户画多少笔画,界面重画需要的资源都是一样的。

2. 显示最佳尺寸的图片

很多程序员比较懒,网络上拿下来的图片,直接就用UIImageView将其显示给用户,这样的后果就是,程序需要一直保存着大尺寸的图片到内存。解决办法应该是先将图片缩小到需要显示的尺寸,释放大尺寸图片的内存,然后再显示到界面给用户。

3. 尽量使用图片pattern,而不是一张大的图片

例如,很多界面设计者喜欢在界面上放一个大底图,但这个底图是老是占用着内存的,最佳方案是,设计出一个小的pattern图,然后用这个方案显示成底图。

C代码  收藏代码
  1. UIImage *smallImage = [[UIImage alloc] initWithContentsOfFile:path];  
  2.   
  3. backgroundView.backgroundColor = [UIColor colorWithPatternImage:smallImage];  
  4.   
  5. [smallImage release];  

4. 使用完资源后,立即释放

一般objective-c的习惯是,用完的资源要立即释放,因为明白什么时候用完某个资源的是程序员你自己。

例如,我们要读较大的图片,把它缩小后,显示到界面去。当大图片使用完成后,应该立即释放。代码如下:

C代码  收藏代码
  1. UIImage *fullscreenImage = [[UIImage alloc] initWithContentOfFile:path];  
  2. UIImage *smallImage = [self resizeImage:fullscreenImage];  
  3.   
  4. [fullscreenImage release];  
  5.   
  6. imageView.image = smallImage;  
  7.   
  8. ......  

5. 循环中大量生成的自动释放autorelease对象,可以考虑使用autorelease pool封装

代码范例:

C代码  收藏代码
  1. for(UIView *subview in bigView.subviews) {  
  2.     // 使用autorelease pool自动释放对象池  
  3.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  4.   
  5.     UIImageView *imageView = (UIImageView *)subview;  
  6.       
  7.     // subview处理代码  
  8.     .......  
  9.   
  10.     // 销毁自动释放对象  
  11.     [pool  drain];  
  12. }  

 自动释放对象池把每个循环内生成的临时对象使用完后立即释放

以上的意见是本人多年来编写iPad/iPhone程序的经验,另外iOS4.0的multi-tasking特性发布后,程序可以被调入后台运行,苹果工程师的意见是,进入后台运行时,你的应用应该释放掉能释放的对象,尽量保持在16M左右,这样别的程序运行时才不容易把你的应用挤掉.

注意: 

  dispatch_async是多核编程框架调度函数,performSelectorInBackground只是线程调度而已,而且有了GCD以后,苹果不提倡使用老式的线程调度了,都用queue。dispatch_async可以把block里面的任务扔到另外一个核了处理,performSelectorInBackground明显没有这个功能。

免责声明:文章转载自《iPad app应用开发系列文章之三 -- iOS的多核编程和内存管理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇iphone开发多线程ObjectiveC中一种消息处理方法performSelector: withObject:下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

自用linux

Linux的内核源代码可以从很多途径得到。一般来讲,在安装的linux系统下,/usr/src/linux目录下的东西就是内核源代码。 对于源代码的阅读,要想比较顺利,事先最好对源代码的知识背景有一定的了解。对于linux内核源代码来讲,我认为,基本要求是:1、操作系统的基本知识;2、对C语言比较熟悉,最好要有汇编语言的知识和GNU C对标准C的扩展的知识的...

JS内存管理测试

打开调试器,切换到timer,点击左下角的record按钮开始,切换到memory视图,在文档中点击鼠标左右键,看股价走势图 functionAllocate(kbs){ this.mem = new Array(kbs * 1024 + 1).join(' '); this.destroy = function(){ th...

Web移动端实现自适应缩放界面的方法汇总

方案一: 设置tranform/scale 首先设置内容固定宽度、自动高度(以下举例) 375px; height: auto; 通过获取窗口的宽度与固定宽度相除,获得缩放比例 const scaleValue=window.innerWidth / 375 在html层,添加一段script: <script dangerouslyS...

ucos-内存管理:

注意:一个内存分区至少含有2个内存块(块的大小至少能满足一个指针大小) 1先定义一个内存块结构指针OS_MEM *buffMEM,在定义一个而为指针A[m][n] 2创建内存分区:buffMEM=OSMemCreate(A,m,n,&err) 3申请一个内存块:void *Pblk1=OSMemGet(buffMEM,&err)//使用...

Cocos2d-x开发中C++内存管理

由于开始并没有介绍C++语言,C++的内存管理当然也没进行任何的说明,为了掌握Cocos2d-x中的内存管理机制,是有必要先了解一些C++内存管理的知识。C++内存管理非常复杂,如果完全地系统地介绍可能需要一本书的篇幅才能解释清楚。这里只给大家介绍C++内存管理最为基本的用法。内存分配区域创建对象需要两个步骤:第一步,为对象分配内存,第二步,调用构造函数初始...

linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解?

问: linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解? 每个进程所拥有的4G独立的虚拟内存空间是什么意思?linux系统的虚拟4G空间中,高位的1G是用于系统内核运行的,那么每个进程都有4G的话岂不都要运行内核了,这样是不是很浪费很低效? 答: 4G 指的是最大的寻址空间为4G 一个进程用到的虚拟地址是由内存区域表来管理的,实...

最新文章