最近一段时间的工作整理

正式转为全职 iOS 工程师已经有一年多了,这一年里工作并不忙,使得我有更多的时间和精力来研究些有意思的东西。最近整理了下这一年攒下来的代码,拆分成了几个库,在接下来的一段时间内会陆续开源:

YYModel 类似 Mantle/JSONModel 的工具,性能比 Mantle 高一个数量级,有更好的容错性,更简洁的 API。
YYCache 类似 TMCache 那样的工具,有着更好的性能,支持 LRU,磁盘缓存支持 SQLite。
YYImage iOS图像库,支持高性能的 APNG/WebP/GIF 动图播放、编码和解码,支持帧动画等。
YYWebImage 类似 SDWebImage 的工具,基于 YYImage 和 YYCache,有更好的性能、更丰富的功能。
YYText UILabel 和 UITextView 的开源实现,支持异步排版渲染、图文混排、更多文字特效/点击效果、动画/表情输入、竖排版等。

YYKeyboardManager 从 YYText 分离出来的一个键盘监听工具,能实时监听和获取键盘视图、位置、动画。
YYDispatchQueuePool 从 YYText 分离出来的一个很简单的队列管理工具,用于管理全局并发任务。
YYAsyncLayer 从 YYText 分离出来的一个很简单的 CALayer 的子类,用于进行异步绘制和显示。
YYCategories Category 类型的工具库。

YYKit 上面所有工具的打包工具集,全部工具都兼容 iOS6~9。
YYKitDemo YYKit 的功能/性能演示,实现有 Twitter 和 Weibo 的 Feed 列表、发布视图,有着和官方 App 完全一致的 UI 和更流畅的交互体验。

每个库都会配有几篇博客来介绍相关技术、性能评测,这也算是我最近一年工作的总结吧。

深入理解RunLoop

RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。

(更多…)

大屏 iPhone 的适配

自从苹果出了大屏 iPhone 后,iOS 开发也要做适配了,想必 Android 程序员正在偷着乐呢 :lol: , 这里大概总结下这几天了解到的大屏适配的注意事项。

启用高分辨率模式

从 Xcode6 GM 版本开始,模拟器新增了 iPhone6 和 iPhone6 Plus 两种,如果旧的工程直接跑到这两个模拟器中时,默认是”兼容模式”,即系统会简单的把内容等比例放大,显示效果有些模糊但尚可接受。此时 App 内部获取到的设备分辨率和 iPhone5 是一样的:320*568 point。

启用高分辨率模式有2个方法(目前我能找到的):

1.添加大屏的 LaunchImage:
在 Images.xcassets 里,新建或更改 LaunchImage 组,添加对应高分辨率的图片。对此,这里有一篇更详细的图文介绍:How to Add a Launch Image for the iPhone 6。如果想要快速测试一下新的效果,这里有3张示例图片下载。

2.添加 Launch Screen File
Launch Screen 是 Xcode6 和 iOS8 新加的功能,它用一个 xib 文件来作为启动画面。App 在旧版 iOS 启动时,该属性会被自动忽略,不会造成异常。
首先,点击 New File ->iOS User Interface ->Launch Screen,然后在工程设置项里启用它:

LaunchFile

上面两处设置,只要启用任意一个即可让 App 进入高分辨率模式;但如果两处都没有设置,则 App 会回退到兼容模式。鉴于现在不少 App 还需要兼容 iOS5,而第一种方法在 iOS5 上据说可能有bug,所以这里推荐用第二种方法。

(更多…)

WWDC2014

熬夜看完了直播,新闻类的东西就去新闻网站看去吧,这里来吐槽开发相关的东西。

总的来说,WWDC = World Wide Developers Crying。
iMessage 重新造了半个微信,iDrive 实现了 Dropbox,iCloud Mail 把 QQ 中转站弄来了、CloudKit 把一些小的云服务干掉了、出了个 Metal 把 OpenGL 换掉了、矢量地图语音导航给各种 Map 带来压力,等等。。最后,还出了一门新语言 Swift 换掉了 Objective-C!!

新语言 Swift 的书可以在这里下载。从网站上的文档来看,苹果把所有 ObjC 和 C 的 API 都用 Swift 重新实现了一遍,并且文档里是 Swift 优先。这点上来看,下面我们该洗洗天赋点了 :?: .

iOS 8.0 API Diffs里面可以看到下面一些变化:

1.苹果把大量基础类中的方法改成了 property。
2.把大量的 id 返回值改成了 instancetype 或者实际的对象类型以适配 swift。
3.增加了大量的新 Framework,包括这次重点介绍的 CloudKit、HealthKit、HomeKit、Metal、Webkit等。
4.新增的 NetworkExtension 允许 App 自定义 DNS、VPN,有着完整和详细的 VPN 控制。
5.新增的 NotificationsCenter 允许 App 自定义 Widget了(Android啊)。

因为最近自己私下里搞 Midi 合成的库,所以其他的 API 没仔细看,音频相关的 API 倒是仔细翻了一下。这次 iOS 支持完整的 MIDI 了,可以加载 sf2/dls 格式的音色库、有了 MidiSynth 支持、有了完整的 Midi CC 控制、支持 SysEx 信息。有了专门播放 Midi 的类:AVMIDIPlayer。音频处理方面,添加了混响、滤波、重采样、变调、3D音效(比如说HRTF)等等。很棒。想想我折腾我自己的库干毛啊,苹果都帮我实现了。。性能和效果上估计会比我的实现要好。。(摔)(摔)(摔) :cry: 那这样,我就抄抄 API 设计好了。。然后移植到 Android 去喵的。。

好困,睡觉了喵。。没吃药果然一点都不萌萌哒。。。

~~~~~~~~~~~~~~~~~~

update:后来在 iOS8 真机上试了一下,苹果原生的 midi synth 效果很渣。。就算给个好的音色库依然很渣。。之前期望太高。。

实现 CFArray

平时都在用 NSArray,但最近搞一个纯C的库,不想有太多依赖,所以想把 NSArray 的功能提取出来。NSArray 和 CFArray 是 Toll-Free bridged的,而且 CFArray 是开源的。所以这里研究了一下 CFArray 的实现,并且给出一个纯C的、无其他依赖的替代实现。

苹果的实现

在翻CF的源码前,我在网上找到一篇很有意思的老文章:http://ridiculousfish.com/blog/posts/array.html (2005年)。这篇文章里面,对 CFArray 和 STL Vector 做了一次性能对比,结果如下:
vector_resultscfarray_results

这篇文章写于2005年,那时的 CFArray 实现的非常有意思:在 CFArray 容量超过300000左右时,消耗的时间完全变了。我很好奇的测试了一下现在系统的实现,已经跟那时的结果完全不一样了。于是我翻了一下CF的历史源码,看到了 CFArray 这些年的变化。

1.最开始,CFArray 底层是用 deque(双端队列)实现的,在头部尾部进行插入删除性能很高,但是在 deque 中间插入删除时,就需要 memmove 来挪动内存了,性能就会降下来。苹果的方法是,当 CFArray 长度超过某个数值(具体来说是262040)时,将底层的 deque 换成 balanced tree

具体来说,苹果写了一个名为 CFStorage 的类,用 balanced tree 实现了大数量的数值的存储和编辑,并且在插入和删除时有很好的性能。CFArray 在长度达到阈值(262040)时,就会在底层替换为CFStorage来完成操作。

2. 但是在2011年,CF-635.15中,苹果把这个特性去掉了。具体的对比可以看下面两个文件。
http://opensource.apple.com/source/CF/CF-550/CFArray.c
http://opensource.apple.com/source/CF/CF-635.15/CFArray.c 在这之后, CFArray 就只是用简单的deque实现了,长度越大,位于中间的插入删除耗时就越长。至于为什么这么改?我也不知道。。也许是怕切换数据结构造成的时间抖动?也许是这个 feature 根本没有多少人用到?

3.最新的 CFArray 中,代码应该又调整了。性能比单纯的双端队列高,我猜测应该换成了一个环形缓冲区。但是苹果还没有开源最新的代码。

 我的测试和实现

在我的系统(10.9.1)里面,我参考最新的 CFArray 源码实现了一个纯 C 的 deque。随后与系统CFArray做性能对比,发现在随机插入的时候,系统的CFArray仍然有更高的性能。当我把deque换成circular后再进行对比,性能就完全一致了。所以,我猜测现在系统的 CFArray 底层又换成了 circular,只是最新的代码还没有开源出来。

我的实现代码放到了 https://github.com/ibireme/yy_music_base/blob/master/yy_music_base/ym_array.c,里面有个纯C的、简单的引用计数对象的实现,有个 CFMutableArray 和 CFMutableDictionary 的模仿实现。所有 API 都和 CoreFoundation 类似。 还没有没仔细 test,但应该没什么大问题。

下面是我自己的实现和CFArray的一个性能对比:除去随机数的影响,插入性能应该是一致的。
yy_array_time_cost

 

 结论

好了,现在可以回头看看苹果给出的时间复杂度的描述了:

用开头那篇博客的话来说:Don’t second guess Apple, because Apple has already second guessed YOU.

 

PS: [NSMutableArray arrayWithCapacity:xxx]中的capacity没有任何作用。。仅仅是一个hint而已。。