[iOS]关于Audio Queue Audio Unit Audio Converter以及一个Music Visualizer Demo

事先声明:

  1. 能用傻瓜式的API还是去用傻瓜式吧,MediaPlayerFramework基本都能满足正常的音频播放需求。本文所涉及的内容用这些是实现不了的。

  2. 如果你指望本文是一个关于标题中提到的东西的教程,那我建议你还是关掉这个标签去翻文档吧,我看过的几个市面上容量仅有一篇博文那么多的关于这些方面的教程都没有什么实际意义,真的想学就踏踏实实去看文档,没有比文档对这方面讲的更清楚的了。除了文档建议补一下WWDC 2010 2011的相关Session,此外就是几个官方Sample。遇到问题了尤其是设计底层的API时,基本只有一个地方回答的比较靠谱,就是Apple Mailing List,另外StackOverflow也不错。本文仅仅回提供一些思路和链接,给后来的人指一个大概的方向。

以下进入正文:

记得小时候mp3刚开始流行那阵,那时候WINAMP称霸一时,虽然当时对音乐还没有什么感觉,但对WINAMP自带的众多可视化效果甚是着迷,直到今天终于有能力自己实现一个了,做了个Demo录了个视频发出来,效果较挫,嫌弃不好的就当听歌了,原曲来自niconico,sm18028378。youku视频如下:(也有u2b新浪

想实现视频中的效果有一种简单的方法和一种困难的方法

先说简单的:首先你要有音频的源文件(这不是废话么),压缩非压缩均可,格式要在iOS设备支持的范围内,另外就是对AudioQueue工作方式比较熟悉,如果你能成功用AudioQueue播放音频的话基本就成功一半了,其实从一定程度上来说AudioQueue也算是半傻瓜式的API,接下来就是获取level meter,这里提示点关键东西,自己看下就明白了:

  • AudioQueueGetProperty, kAudioQueueProperty_CurrentLevelMeterDB
  • Sample Code, Speak Here,MeterTable.h

这样就可以在每次OpenGL刷新的时候获取level meter值,
关于最后的OpenGL展示集中在后面讲

比较困难的方法就是用Audio Unit了,说困难其实不是API多么难用,而是越到底层我们做的工作就越多,真的要实现一个播放器的话我们就要人工处理音乐播放器的各种逻辑,时间控制,播放列表控制等等,实现功能以后还有一段很长的路要走。

进入正题,要求音频文件最好是liner PCM,也就是无损无压缩的,因为Audio Unit只能接受liner PCM的数据,如果是压缩格式还需要转换,转换格式可能就需要用到ExtAudioFile,或者AudioFile+AudioConverter,前者比较方便,后者相当复杂,尤其是要考虑对各种格式的兼容问题,需要很多判断,很多分支,对音频格式的知识要求也相当高,参考Sample Code,ConvertFile,这是一个控制台应用,代码很多,耐心看。如果格式的问题回避或解决了,就可以进入AudioUnit部分了,这部分其实也不难,基本就是设置一大堆属性,然后在回调函数里面提供数据就可以了,看官方文档会对你有很大帮助。至于获取level meter,可以让音频数据通过一次MultiChannleMixer Unit(这个AudioUnit的使用可以参考下Sample Code,Mixer Host),不过我们只用一个输入口,不真正mix,只是为了借用其kMultiChannelMixerParam_PostAveragePower,获取和kAudioQueueProperty_CurrentLevelMeterDB相同的东西,然后沿用MeterTable.h,转换成更直观的数据,最后显示出来。

如果做更复杂显示效果,可能就需要fft了,也就是将时域转换成频域,根据频率数据来实现比如做那种频谱图(Spectrometer),可参考的资料:

FFT部分:

  • 理论基础,随便找个教程看吧,这方面有很多
  • 实际一点的看下Create a FFT Analyzer这个系列的前半部分,这个系列从头到尾讲了下Mac上一个显示频谱的AudioUnit的实现,但是iOS目前(5.x.x)并不支持自制的Audio Unit,所以即使你照着这个教程做出来东西也不能用到iOS平台上,不过对实现过程还是能有一个比较清晰的认识的。另外该教程也对学习路线有一明确指引。

AudioUnit部分:

  • Sample Code, AurioTouch2, Aurio Touch;前者用了AccelerateFramework,这个Framework里面有个硬件加速版的FFT,以及一些iOS 5才出现的API,但仅仅FFT部分还是能支持到iOS 4的,至于后者是纯用CPU算FFT,可以兼容iOS 4之前的系统。

接下来是显示部分:

这部分实在没什么可说的,对OpenGL ES我还是个小白,现在只是用了一个现成的Particle System,在每次draw view之前根据level meter改变了下各点的尺寸,然后就没有然手了。我也跪求高手指导如何才能做出漂亮的效果。

最后说下我的学习路线:

  1. WWDC 2010 2011关于Audio的所有Session,对Audio部分有一个直观的认识,
  2. 官方文档,从Multimedia Programming Guide开始,所有关于Audio的分支文档,从表层到底层都有个了解,明确实现某种功能需要从哪一层入手,这部分内容很多,拖拖拉拉看至少4天吧。
  3. 期间参杂之前提到的所有Sample Code,文章,以及StackOverflow,Mailing list中的各种问题。

其实有一些关于CoreAudio的书也不错,但是太大部头了,短期纯为了实现功能看书太慢了。

本文Demo的代码我就不放出来了,没有太大价值,只能起到急功近利的效果,对学习没有什么帮助,另外就是代码都是拼凑了,结构混乱,还是不要拿出来让人喷了。

PS.题外话,关于Mac向Youku,新浪传视频,Mac下的转码软件基本都是个悲剧,前面用的视频是用Video Converter-Clone2Go这个转换的,这个软件虽然UI非常悲剧,但至少能设置下音频,视频的bit率,这对于要保证音频质量来说是必不可少的(youku没爆音真是很幸运)。另外就是对比新浪和youku,新浪的审核转码都比youku快不少,音质也明显比youku高,至于u2b各方面都非常好,也就是对于我们这些国内的人上传超费劲。

祝各位玩得愉快。

–以上–

关于C++和Objective-C混编

Objective-C在大部分情况下足够满足我们的需求,但是还是会有一些情况必须要使用C++,比如:

  1. 使用C++的库
  2. 当Objective-C不够快的时候

第一点自然不必多说,至于第二点,Objective-C的消息机制比起函数调用还是比较慢的,当对性能有极高要求的时候,就需要C/C++来替代。

C++与Objective-C混编只要注意将包含C++代码的.m文件改为.mm即可,XCode就会自动判断该使用何种编译器来编译。

这看起来简单,但实际操作中还是很容易出现令人费解的编译问题,比如最经典的"Unknow type name ‘class’; did you mean ‘Class’?“。究其原因就是我们没有遵守这两条规则:

  • .m文件不能含有C++代码
  • .m文件所import或include的.h文件中不能直接或间接包含C++代码

在保证你的C++代码是正确前提下,如果发现相关编译错误可以通过如下几个方法修复

  1. 将.h文件中的c++代码转移到其他地方
  2. 阻段include或import链
  3. 将相关的.m文件后缀改成.mm

显然第三种方法相对于前两种实施起来更方便,但如果你使用XCode 4以及之后的版本所包含的模板建立项目的话有可能会忽视一个问题:

当你在AppDelegate.h中include或import一个C++的头文件时,当然你一定不会忘记修改AppDelegate.m为AppDelegate.mm,如果只做了这点儿还不够,我们还忽略了隐藏在Supporting Files组内的一个文件的存在–main.m

在以前的XCode模板中main.m默认是这样的

#import <UIKit/UIKit.h>;
 
int main(int argc, char *argv[]) {
 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

而新的模板是这样的:

#import <UIKit/UIKit.h>;
 
#import "AppDelegate.h"
 
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

看出问题来了吧,main.m新增了#import “AppDelegate.h",这就导致了main.m作为一个Objective-C源文件却引入了C++代码,而Xocde会使用Objective-C的编译器进行编译,从而产生编译错误,因此我们还需要将main.m的后缀改成.mm。

总结+吐槽:我在发现main.m这个问题之前对编译错误纠结了好久,一度修改代码,也一度怀疑是XCode本身的问题(XCode 4出奇的不稳定让人不得不对其产生质疑,虽然这次是我错了),希望同样使用Objective-C++的同学不要像我一样栽在main.m上。

–以上–

[iOS]开源控件GSBookShelf–iBooks风格书架实现

GSBookShelf是我参照iBooks的操控实现的一个书架界面(或者说是控件)。项目放在github上了,大家可以去参考。

由于前两天GSBookShelf被Cocoa Controls收录了(不知道谁给我提交上去了,或者他们编辑自己发现的),而且还获得了Control of the week称号,所以有必要提供一个github之外的反馈平台吧。

演示:

效果图(图片比较大,请耐心等待):

imageimageimage

视频演示(感谢Cocoa Controls制作):

u2b

简介:

看过上面效果图并且用过iBooks的同学应该清楚这个控件的基本功能了吧,就是实现书籍拖拽(支持拖拽的同时ScrollView滚动),批量添加,批量删除,以及这些动作的相应动画。

部分功能实现细节:

  • 整体采用了类似UITableView的cell,重用等机制
  • 在拖拽的同时ScrollView滚动是通过NSTimer每隔0.xx秒触发,让ScrollView的contentOffset加1实现的,这种方法看起来可能不太靠谱,但实测性能上没有问题,2代touch上滚动速度略慢。

其他说明:

  • 别忘了开ARC
  • demo中的“书”(bookView)是UIButton,可以替换成任意UIView,只要你别拦截长按手势。
  • 为你自定义的bookView,和cell添加GSBookView和GSBookShelfCell协议来支持重用,这个对性能很重要。
  • 超出书架拖动范围,在书架顶部之上可以添加一个自定义的UIView来实现ibooks中那个苹果图表的效果,在书架底部之下也可以添加一个自定义的UIView来显示出更多层,防止直接显示scrollview的背景的突兀。
  • 在书架顶部可以加一个自定义UIView,demo中我添加了一个UISearchView,可以用来查询
  • “书”(bookView)的尺寸是固定的,如果你希望有所变化,可以把一个透明的UIView设置成bookView,然后在这个view之上添加你的内容,但是手势识别仍旧以透明的UIView的范围为基准,这个问题主要是为了权衡bookView的可定制性,以及实现的难度所做出的妥协。
  • 横屏的图片我没做,原因一是比较懒,二是如果你用这个控件应该会自己作图,三是考虑到下面TODO中提到的内容。

TODO:

  • 在之前一版的demo中虽然支持了横竖屏切换,但是只是简单地reload了一次,而且跳到了书架顶部,细节不太完美,而iBooks在旋转过程中还有一些动画等细节功能,这个会在以后添加,但肯定不是最近(目前比较忙)。

总结:

感谢Cocoa Controls收录,欢迎各位批评指正,如有问题请回复或者通过各种可能的方式联系我。

PS:我无节操地在github项目那里加了个Donate,欢迎支持!

–以上–

[iOS]分享最近发现的一些好东西(120311)

好久没更新了,最近在鼓捣一些Core Audio的有关内容,加上各种事儿,没有时间写东西,先把最近发现的一些好东西分享出来吧。

文章

项目

[iOS]借助VoiceOver来破解优秀App的实现方式

为了开发出更好的iOS App,我们经常会去破解(我更喜欢叫Hack)优秀的App的实现方式,一般我们做的无非就是拆了ipa包浏览一下App所用到的图片资源,找个抓包工具看看都访问了那些网络资源,更虎点的就是去反编译,当然最基础的就是把玩一下各个功能,凭经验看出实现方法。

今天就介绍一个比较另类的Hack方式——VoiceOver,估计大部分人都没在自己的iOS设备上打开过这个功能,我就简单说一下效果吧,更细节的东西还是要各位亲身体验一下。

VoiceOver需要在:通用 >> 辅助功能 >> VoiceOver 页面里开启,打开后会进入一个相对比较难操作的环境,随着手指在屏幕上移动,会有一个黑框附着在手指附近的UI元素上面,同时随着黑框的移动,iOS设备会自动朗读UI元素的内容,不难发现VoiceOver这个黑框具有以下几个特性:

  1. 黑框所在位置就是当前UI元素的真实边框
  2. 对于一些元素比如UIButton,当黑框附着在起上面时,系统就会朗读“xxxxx按钮”,对于UITableView更是可以朗读出当前所在行号,以及一共有多少行。朗读的内容通常包含了这些UI元素的独有特性。
  3. 由于大部分的App都不会对“辅助功能”有所支持,有些没有文字的UI元素系统甚至会朗读其变量名,这跟反编译已经比较相似了。

借助第一个特性我们可以破解App的UI布局,借助第二个特性我们可以找出一些UI元素的实现方式,比如有些Gird View就是用UITableView实现的,如果系统朗读了行号等UITableView所具有的特征内容,我们就可以基本确定这个Gird View就是用TableView来实现的,TabBar之类的也是如此。借助第三条特性,我们可以通过其变量名判断其功能和实现方法。此外对于那些开源应用,变量名也可以方便我们更快地研究开源代码。

相对的,如果我们想避免App的关键实现细节被破解,就要在代码中加入防止VoiceOver响应的代码。

综上,虽然VoiceOver属于辅助功能范畴,但其一些特性为我们Hack App提供了方便。在一般的应用中加入Accessibility支持可能会使开发成本有所提高,但对于某些领域的应用,Accessibility支持无疑会成为应用的一大两点,在此也推荐大家看一下 WWDC 2011 Session 127 – Design Patterns to Simplify Mac Accessibility,真的挺有爱的。

–以上—

Xcode 4.3+ NSLog中文不输出Bug及解决方法

这个Bug折腾了我近一天,读一个文件,NSLog输出文件内容,结果死活读不完整,不光中文没有,英文也不全,考虑了编码,文件大小,文件位置等各种可能的因素,尝试用各种方式重写这个操作,最后发现是NSLog的问题,跟文件一毛钱关系都没有,感谢这两个链接:link1 link2 。

鉴于第二个链接已经解释的很清楚了,我这里就简要说一下,毕竟Wall还是有些麻烦

重现Bug很简单,Xcode 4.3+,用lldb在真机上运行(模拟器没有问题)下面代码:

NSLog(@"English1");
NSLog(@"中文");
NSLog(@"English2");

中文那行神马都不输出啊!如果NSLog一个NSString,String里面有中文,那么输出也会悲剧。

解决方法两种:

  1. 如果你执着于lldb,那么用Organizer >> Devices >> 你的设备 >> Console 这里会显示中文
  2. 按住Option点Run(或者 Product >> Edit Scheme…),Info >> Debugger 设置为GDB

一切回归正常,WTF!

另外有人说4.3.2解决了这个问题,事实是:没有解决!

祝愿被这个问题折腾死的人能早日看到这篇文章或者link2那篇文章。

Becareful with XCode!!

–以上–

XCode 4.3.1一个小众Bug及解决方法

哦… 首先希望闲来无事发现此文的人,可以回复报下你们一天中XCode Crash的次数,附上版本,大家比比谁受气受得多。(我:4.3.1 10多次crash)

目前测试Bug的环境:XCode 4.3.1,用svn作为版本控制
PS. 4.3可能也有这个Bug,4.2.1貌似没有,但不排除svn下的其他毛病

Bug产生过程:

  1. Project处于svn版本控制之下
  2. (可有可无)向Project中添加文件,并加入版本控制(A)
  3. 从项目中Delete某个文件,并选择Move to trash
  4. 编译的时候会有Wraning提示缺少了之前删除的那个文件(Missing File xxxx)

虽然这是一个误报,对编译没有任何影响,但是有Warning看着就心烦,而且如果删除了很多文件就会造成满屏Warning,既影响心情,又影响工作。

下面是几种解决方法

  1. 如果你对XCode 4.3+移植抱有敌视态度,那么你的/Developer或者废纸篓里面可能还有XCode 4.2.1,用4.2.1进行删除的步骤,那些Warning就不会出现。
  2. 完全用XCode 4.3.1 + Terminal解决
    1. 对那些你要删除的并且有"A"(版本控制)标记的文件,右键 >> Source Control >> Discard Changes…
    2. 右键 >> Source Control >> Ignore
    3. Delete并Move to trash

这样编译就不会有warning,如果你删除的是一个文件夹,那么还会提示Missing这个文件夹,再或者上一步删除文件之前忘记设置ignore了,warning就会出现,用终端解决:
1. 打开终端,cd到项目主目录下
2. svn status,可以看到那个Warning的文件(夹)
3. svn rm xxxxx 从版本控制里去掉那个文件(夹)

这时再编译就不会有那个warning了,世界终于清净了

总结:XCode 4+对svn支持非常废,经常有莫名其妙错误,要手动解决,所以一个更方便的方法就是不用svn,现在git这么火,不怕开源的项目就直接放到github上得了,保密的就放到Bitbucket上,或者找个服务器自己搭一个,总比折腾svn这些破事好,不过广大开发者最终还是应该鄙视苹果的,照这么下去苹果还是回到几年前的小众状态比较好,那时无论是SL,还是Xcode 3都是精品中的精品。现在苹果到了风口浪尖上,做东西都很浮躁,各种神奇,傻X的Bug层出不穷,老乔也不托梦骂他们几句。

懒得喷了,话说中午写的这篇,晚上发的,发现XCode 4.3.2出了,不知道老乔显灵没,如果还Crash记得上App Store评一星啊!link

更新:恭喜,XCode 4.3.2下这个Bug依旧

–以上–

iPad 3 图片解压缩测试

之前翻译过Avoiding Image Decompression Sickness[iOS]如何避免图像解压缩的时间开销,现在iPad 3出了,原文也有了后续iPad 3 Image Decompression Benchmarked,所以我也就简单再翻译个后续,推荐先看一眼之前那篇,以下内容都是建立在之前的基础上的。

此次原文没有太多重要内容,挑重点简要翻译,括号内大部分是我的注释,少部分原文,很好辨别。

iPad !!!(iPad 3,这是跟k-on!!或者working!!学的么?),根据GeekBench测试,CPU几乎没有变化(因为A5X只是显卡4核心,CPU还是和iPad 2一样),根据GLBenchmark的结果,苹果对iPad 3做了很大优化,是的在Retina屏幕上的祯数几乎和iPad 2相同。

一位Australia的同学帮忙在他的iPad 3上帮我们运行了benchmark,并把测试结果发给了我们。(之前的结果参考之前的那篇文章,我就不贴图了)

我们预想iPad 3解压缩速度将是iPad 2的2-4倍,iPad 2默认的launch image需要100ms来解压缩并显示,如果在iPad 3上也有相同的速度就可以了。

测试结果如下

PNG Crushed, 1024*768 (init+decode+draw)

  • iPad 2: 5 ms + 89 ms + 18 ms = 113 ms
  • iPad 3: 1 ms + 50 ms + 18 ms = 69 ms

-47%

JPG 80%, 1024*768 (init+decode+draw)

  • iPad 2: 2 ms + 32 ms + 18 ms = 52 ms
  • iPad 3: 2 ms + 32 ms + 17 ms = 51 ms

-2%

PNG Crushed, 2048*1536 (init+decode+draw)

  • iPad 2: 5 ms + 266 ms + 96 ms = 368 ms
  • iPad 3: 2 ms + 171 ms + 66 ms = 238 ms

-33%

JPG 80%, 2048*1536 (init+decode+draw)

  • iPad 2: 1 ms + 121 ms + 69 ms = 192 ms
  • iPad 3: 1 ms + 122 ms + 66 ms = 189 ms

-2%

粗略看下我们发现iPad 3的速度并没有我们预想的那样有4倍的提升。退一步讲,iPad 3的效率的确像Apple描述的那样有2倍的提升(-50%)(A5X比A5显卡快了一倍),但这仅仅针对处理PNG Crushed的情形(-47%),在处理JPEG的时候速度仅仅提高2%,这点儿提升也许是来自SSD读取速度的提升,看起来苹果的开发人员似乎都将JPEG遗忘了。

至于"感觉上" iPad 3是不是比iPad 2快了呢?

恐怕不是,iPad 2显示launch image要113ms,iPad 3上却要消耗238ms,如果没有任何优化的话,一个应用启动要多消耗125ms(记住CPU性能是相同的)。

因此最大的问题就是,杂志类应用开发者更期望这种“感觉上”的速度提升,而实际上即使使用80%全屏大小的JPEG也只能得到5-19(128ms-52ms)的祯数,这就导致了如果在主线程绘制图片,从一个页Scroll到下一个页时就会产生一个明显的卡顿。

总结

现在我们知道为什么新的核心命名为A5X了,这仅仅是与A5相同的CPU配上了一个性能稍高点的显示核心。这个“X”我们计算出来大概为1.4,但仅仅针对PNG,对于JPEG,也许在下一个iOS更新中体现吧。

目前发生这种情况的原因很可能是这样:针对JPEG图片的硬件加速处理模块因为某种情况在iOS 5.1中被遗忘了,或者干脆没有准备好。如果事实果真如此,那么对于苹果来说这真的很尴尬。

最后一段原文打广告。大概就是建议用大图的人暂时只留下必须的小图片,使用他们做的DTCoreText来渲染富文本(蛋疼核心文本?LOL)。(要我说按苹果这么个折腾法矢量图才是王道啊!)

–以上–

[iOS]在运行时为类添加方法

Obj-C用起来真是各种happy,比如现在有这样一种情况:有一个类,我们希望它能响应一个消息(message),但是这个类没有相应的方法(method),而你又偏偏不能重写/继承这个类。这时我们可能会想到,能不能动态地给类添加一个方法呢?感谢Obj-C,仅需简单几步就能实现。

先看一段代码

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
 
@interface EmptyClass:NSObject
 
@end
 
@implementation EmptyClass
 
@end
 
void sayHello(id self, SEL _cmd) {
    NSLog(@"Hello");
}
 
- (void)addMethod {
    class_addMethod([EmptyClass class], @selector(sayHello2), (IMP)sayHello, "v@:");
 
    // Test Method
    EmptyClass *instance = [[EmptyClass alloc] init];
    [instance sayHello2];
 
    [instance release];
 
}

我们首先定义了一个EmptyClass,继承NSObject,没有任何自带方法,接着定义了一个函数。这里提一句,Obj-C的方法(method)就是一个至少需要两个参数(self,_cmd)的C函数,这个函数仅仅输出一句Hello。接下来在addMethod方法中,我们调用class_addMethod()为EmptyClass添加方法,class_addMethod()是这样定义的:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

参数说明:

cls:被添加方法的类

name:可以理解为方法名,这个貌似随便起名,比如我们这里叫sayHello2

imp:实现这个方法的函数

types:一个定义该函数返回值类型和参数类型的字符串,这个具体会在后面讲

接着创建EmptyClass的实例,调用sayHello2,运行,输出Hello,添加方法成功。

接下来说一下types参数,
比如我们要添加一个这样的方法:-(int)say:(NSString *)str;
相应的实现函数就应该是这样:

int say(id self, SEL _cmd, NSString *str) {
    NSLog(@"%@", str);
    return 100;//随便返回个值
}

class_addMethod这句就应该这么写:

class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");

其中types参数为"i@:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void

@:参数id(self)

::SEL(_cmd)

@:id(str)

这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档

最后调用say:方法:

int a = [instance say:@"something"];
NSLog(@"%d", a);

输出something和100。

关于本文所涉及内容的详细信息请参考Objective-C Runtime Reference

本文参考了:

推荐去看看
—以上—

[iOS]如何避免图像解压缩的时间开销

这是一篇译文,(原文"Avoiding Image Decompression Sickness"在此),原文是我看过的非常不错的一篇关于iOS图片显示的一些文章,解决了我的一些疑惑和问题,因此翻译过来分享,为保证一定的通顺性其中一部分内容与原文有些许出入,但我尽量保证了意思的一致性,欢迎指正批评,横线之间为译文,略挫,见谅:


当开始iCatalog.framework的工作时,我发现使用大尺寸图片会引起一些恼人的问题,“大”意味着这个图片有足够大的分辨率(1024×768)来覆盖iPad的整个屏幕,或者覆盖未来Retina Display iPad(如果有的话)的双倍分辨率(2048×1536)屏幕。

想像一个杂志类型的App,一个分页的UIScrollView,每页显示一个UIImageView,一旦某一页进入屏幕区域你就要为这个页创建或者重用一个UIImageView并把它放到scrollView的当前显示区域,即使这个页只有一个像素进入到屏幕区域,你还是要做这些工作。这在模拟器上运行得非常好,但在真机上进行测试,你会发现每次进入下一页时都会有一个明显的延迟。这个延迟来自于将图片从文件解压缩并渲染到屏幕上这一系列的工作。不幸的是UIImage仅在图片将要显示的时候做这个解压工作。

因为添加一个view到当前的view层次结构中必须在主线程上进行,所以图片的解压缩和之后渲染到屏幕上的工作也在主线程进行,这就是这个延迟产生的原因,这个问题也可以在store里的其他有类似这种效果的app中发现。

一般我们使用的图片有两种主要格式,jpeg和png。Apple通常推荐你使用png作为用户界面的图片格式,这些图片会被一个叫pngcrush开源的工具优化(译者注:这个工具就在/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush ),这样对于iOS设备就可以在显示时更快地进行解压和渲染。iPad平台上首批出现的杂志应用,比如Wired,就曾用过png作为杂志内容图片的格式,这导致了这个应用的某一版本大小超过了500MB(link)[http://www.cocoanetics.com/2010/05/saturday-morning-breakfast-wired-emag/]

虽然png格式的图片会被事先优化好,但是这并不意味着在所有情况下png都是最佳的图片格式,png对于那些app中自带的图片来说非常好,但是对于要从internet上down下来的图片来说又会怎样呢。png和jpeg这两种格式都有各自的优缺点:

png格式的图片有alpha通道,jpeg则没有。png无损压缩,jpeg允许你选择0-100%的压缩质量。如果需要alpha通道(透明),就只能用png格式。但是如果你不需要一个完美的图片,就可以使用jpeg格式,jpeg格式会忽略那些你看不到的信息,对于大部分的图片可以使用60-70%的压缩质量而不对图片造成明显的影响,对于比如文字那样有"sharp pixels"的图片就可能需要较高的压缩质量,对于照片可以使用较低的压缩质量。

来看一下一个图片的空间消耗:

  1. 磁盘空间或者通过internet传输所消耗的空间
  2. 解压缩空间,通常是长X宽X高X4字节(RGBA)
  3. 当显示在一个view中时,view本身也需要空间来存储layer

对于这里的第一个问题,有一个可能的优化方法:将压缩的文件拷贝到内存中不如映射到内存中,NSData有能力来假设一块磁盘空间是在内存中的,这样当访问这个图片时实际上就是从磁盘访问而不是从内存。据说CGImage知道哪种访问方式是最高效的,UIImage只是将CGImage封装了一下。

对于“将这些像素显示到屏幕上最快要多久?”这个问题,显示一个图片所消耗的时间由以下三个因素决定:

  1. 从磁盘上alloc/init UIImage的时间
  2. 解压缩的时间
  3. 将解压缩后的比特转换成CGContext的时间,通常需要改变尺寸,混合,抗锯齿工作。

要逐一解答各个问题,我们需要一个benchmark来测量。

测试环境和测试内容

继续阅读[iOS]如何避免图像解压缩的时间开销