关于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的有关内容,加上各种事儿,没有时间写东西,先把最近发现的一些好东西分享出来吧。

文章

项目

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]如何避免图像解压缩的时间开销

编译你自己的MobileTerminal

先扯点题外话,没兴趣看的直接跳过这一段:话说iOS 5.0.1完美越狱终于出来了,虽然还有一些Bug(比如ibooks),以及一些4.x时代的越狱软件不能用,但是终归是出来了,最近还有weiphone的好心人分享4s的siri认证,就是那个Spire一直下不下来很蛋疼。再吐槽下Sbsettings,虽然放到通知栏里了,但是也太影响加载速度了吧,你就不能在viewdidappear里面加载那几个图标么(也许通知栏的控件没有这个方法,但是那个天气股票啥的都挺流畅的啊),搞得通知栏没有一回是流畅拉下来的。依赖的Activator显然已经没什么用了,就应该老实儿做个通知栏版的Sbsettings,也不能费多少劲。

吐完槽进入正题:MobileTerminal是一个写的很蛋疼的app,为什么很蛋疼,原因有很多:

  • 删除(backspace/delete)操作总是有历史字母残留,这个不知道为什么,对终端的实现不是很了解
  • 左下角那个快捷菜单不能添加ESC这种常用的快捷键
  • swipe left right up down 的手势识别率太低,就不应该用这种不好识别的手势,本来终端就是个没什么触摸交互的东西,手势操作看不到结果。
  • 等等…

这里先说下环境:Xcode 4.2,iOS SDK 5.0, 有开发者帐号证书

幸好编译MobileTerminal还算简单,编译脚本该有的都有了,不用去操心这些实在是很方便,大概步骤如下:

  • 找个目录 svn checkout http://mobileterminal.googlecode.com/svn/ mobileterminal-read-only 这个目录路径最好全英文,无空格,否则编译deb时有问题
  • check下来的东西有好几个project,真正有用的在branches/applesdk/MobileTerminal 打开这个project
  • Target选择成MobileTerminal,编译到真机
  • 编译遇到错误 Command ./build_svnversion.sh failed with exit code 1 问题,这个svnversion似乎是用来生成版本号的,我们也不需要就可以无视。打开项目配置文件,选择Targets下面的MobileTerminal,BuildPhases >> Target Dependencies里面删除svnversion(MobileTerminal)
  • 继续编译,多了一个错误:TerminalGroupView.m 文件里#import部分,找不到 Perferences/Settings.h;直接改成#import "Settings.h"
  • 继续编译,可能有签名问题,对于有开发者帐号的就直接设置好签名就OK了,没有帐号的如果之前已经做好XCode免证书调试的各种工作了这个错误应该不会出现,具体没有条件尝试,大家自行解决吧,不是大问题。
  • 继续编译,第一次遇到的错误没有了,现在在 MobileTerminalAppDelegate.m 文件里提示找不到 svnversion.h;删除这句import,修改applicationDidFinishLaunching:里面的SVN_VEFRSION为任意数字,比如999
  • 过程中还可能遇到找不到gen_entitlements.py的问题,这个估计是XCode3.x版本免证书调试遗留下来的东西(参考这里),解决方法,终端中输入如下命令(要sudo):

    mkdir /Developer/iphoneentitlements312
    cd /Developer/iphoneentitlements312
    curl -O http://www.alexwhittemore.com/iphone/gen_entitlements.txt
    mv gen_entitlements.txt gen_entitlements.py
    chmod 777 gen_entitlements.py

经过以上修改,编译就应该成功,在你的机器上就应该会出现沙盒版的MobileTerminal,运行会提示在沙盒里不能fork,这就需要将App安装到/Application/下,直接将编译好的terminal.app拖过去运行会秒退,需要编译deb包,像Cydia那样安装。

Target选择deb,编译,可能会提示缺少dpkg-deb;直接下载这个二进制文件dpkg-deb-fat(参考这里),放到/usr/local/bin/下面(或者/usr/bin/),重命名为dpkg-deb,在终端直接敲dpkg-deb命令检查以下,接着编译成功,编译好的deb在~/Library/Developer/Xcode/DerivedData/MobileTerminal-xxxxxxxxxx/Build/Products/Debug-iphoneos/ 下面,这个路径可以通过在项目Products文件夹右键点击已经编译好的Terminal.app >> Show in Finder来找到

至于Deb包的安装我就不用多说了,iFile傻瓜式还是命令行dpkg,再或者蛋疼地架个repo,方法很多了。

至于Terminal的修改我还没有研究,最近也比较忙没那闲工夫折腾,有兴趣的自己看看吧,牛人可以挑几个bug啥的上传到code上以后也可以作为炫耀的资本(开完笑),以后Cydia上的那个版本就可能时你编译的了。

PS.其实MobleTerminal可以直接在模拟器上运行,没有沙盒问题,证书问题,也方便调试

最后感谢一下为MobileTerminal作出贡献的我不认识的大牛们,虽然我之前吐了很多槽,幸好你们看不懂中文。

—以上—

[WWDC][AppFramework]Session 121 Understanding UIKit Rendering

Session 121

Understanding UIKit Rendering


个人感觉是WWDC 2011里比较精品的Session,强烈建议各位同学反复看几遍视频,可以了解到许多iOS的实现机制

提要
  • UIView and CALayer
  • CATransaction and when views get rendered
  • Quality and Performance

     

    • Clipping and masking
    • Edge anti-aliasing
    • Group opacity
    • Shadows

UIView and CALayer

Geometry

iPhone 4
UIKit 左上为原点,320 x 480, 横屏原点不变 CoreAnimation 左上为原点,640 x 960

iPad 2
UIKit 左上原点 768 x 1024 CoreAnimation 左下为原点 1024 x 768

关于坐标问题直接参看官方文档就可以了

Drawing
  • view.layer.contents
  • UIImageView
  • drawRect

参考Practical Drawing for iOS Developers

UIImageView 与 drawRect 对比

放大一个图片,drawRect会占用更多内存,UIImageView只占原始图片所占内存,没有额外开销 用一个基础图片(tile)填充一个更大的区域,drawRect同样占用更多内存,UIImageView会在性能与最小内存占用之间选择一个平衡点(将多个tile拼成一个大一些的tile)

CATransaction

转换在runloop结束时生效

Performance
Avoid Offscreen Rendering

“正常"的渲染都直接在当前可视区域进行 屏幕外渲染,让显卡指向一个屏幕外的内存,alloc那段内存并开始绘图,渲染结束后,显卡指向主屏幕,使用之前在屏幕外渲染来在屏幕内绘图,这个过程需要消耗额外的内存,显卡从一个buffer切换到另一个buffer也需要消耗额外的时间,还需要刷新缓冲区,所以在屏幕内的和屏幕外的buffer之间切换代价是很昂贵的。 由于CoreAnimation是逐帧渲染的,所以屏幕外的渲染将会带来极大的开销, 每次屏幕刷新都会有屏幕外的渲染发生,比如一个scrollView,每次scroll的时候,都会在屏幕内进行一些渲染,屏幕外进行一些渲染,然后又将屏幕外的绘制到屏幕内。

Layer Rasterization

这个算是offscreen rendering的一种解决方法吧,屏幕外的渲染会被缓存起来,这样就不用每次都重新绘制一下屏幕外的buffer. ([CALayer setShouldRasterize:])
使用这个特性要保证你的内容是固定不变的,因为如果是个变化的内容,那么每一帧的内容都会与前一帧不同,缓存不能被重复利用,反而影响了性能。


比如要实现图中所示效果
CuriousFrog

Clipping and Masking

这里有两个技术点,一是圆角的实现,我们可以直接用CALayer的cornerRadius来达到这个效果,虽然很方,但是效率很低。二是倒影的实现,实现倒影的方法是首先取得上半部分原始View hierarchy的一个副本,y轴翻转绘制在下面,然后根据剃度来设置mask,但是这个mask开销是很大的,一方面要对整个View hierachy进行操作,另一方面需要进行Offscreen Render。

下面的方法效率都比较低,虽然很方便 * [CALayer cornerRadius] * [CALayer mask] * [UIView clipsToBounds] 或 [CALayer masksToBounds]

一些Trick * [CALayer contentsRect] 设置需要渲染的范围,本例中并不适用。 * [UIView drawRect:] 预先渲染好来防止每一帧都进行渲染 * Transparent overlay 比如圆角就可以通过覆盖黑色来让它看起来是圆的,但是本例中还要看到圆角后面的内容,所以不采用

Group Opacity

设置Group Opacity和没设置的区别

GroupOpacity

可以通过一下几个方法实现: * 在Info.plist里设置UIViewGroupOpacity键值 这样就又会产生offscreen rendering * 在drawRect:里预先渲染 * 设置shouldRasterize为YES

Shadowed text

使用CALayer的shadow属性很方便,但是开销也很昂贵

Demo

首先给出检测offscreen rendering的方法:使用Instruments的Core Animation模板,选中Color Offscreen-Rendered Yellow就可以让所有的Offscreen rendering都染成黄色

首先使用

UIBezierPath *capsulePath = [UIBezierPath bezierPathWithRoundedRect:myBounds cornerRadius:myBounds.size.height / 2.0];
[[UIColor grayColor] set];
[capsulePath fill];

来绘制圆角的背景,免去setCornerRadius的开销。

接着使用

CGContextSetShadow(context, CGSizeMake(3.0f, 3.0f), 10.0f);
CGContextSetShadowWithColor(context, CGSizeMake(3.0f, 3.0f), 10.0f, [UIColor colorWithWhite:0 alpha:.4].CGColor);
 
[[UIColor blackColor] set];
[_labelTitle drawInRect:titleFrame withFont:[UIFont boldSystemFontOfSize:28]];

来代替setShadowOffset: 和 setShadowColor:,这里注意一点,CoreGraphics在你执行绘制的代码后立即就会进行绘制,所以要在绘制之前先把属性设置好;而Core Animation在Transaction commit之后才开始渲染,所以属性可以在之后设置。

倒影的绘制,预先绘制成一个图片来防止逐帧渲染

if (_isReflected) {
    // create mask image
 
    UIGraphicsBeginImageContext([self bounds].size);
    CGColorSpaceRef deviceGray = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {1.0, 0.0);
    NSArray *colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.0] CGColor], (id)[[UIColor colorWithWhite:0..0 alpha:0.5] CGColor], nil];
    CGGradientRef gradient = CGGradientCreateWithColors(deviceGray, (CFArrayRef)colors, locations);
 
    CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), gradient, CGPointMake(0, 0), CGPointMake(0, [self bounds].size.height), 0);
    CGGradientRelease(gradient);
    CGColorSpaceRelease(deviceGray);
    CGImageRef maskImage = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
    UIGraphicsEndImageContext();
 
    CGContextClipToMask(context, myBounds, maskImage);
}
Edge Antialiasing

抗锯齿的实现有下面两个方法

  • Info.plist里设置UIViewEdgeAntialiasing键值 开销很大
  • 用一个像素的边来模拟

代码如下:

- (UIImageView *)createImageViewForImage:(UIImage *)image
{
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [imageView setContentMode:UIViewContentModeScaleAspectFit];
 
    //Add a shadow around the image.
    [[imageView layer] setShadowOffset:CGSizeMake(10, 10)];
    [[imageView layer] setShadowColor:[[UIColor blackColor] CGColor]];
    [[imageView layer] setShadowOpacity:.5];
 
    //Rotate the image by some small random angle.
    [imageView setTransform:CGAffineTransformMakeRotation((((float)random() / RAND_MAX) * MAX_ROTATION) - MAX_ROTATION / 2)];
 
    //Rather than using Core Animation's edge antialiasing, which requires an off-screen rendering pass, we'll draw our image int own image with an empty 1px border on each side. That way, when it's rotated, the edges will appear smooth because the outermost pixels of the original image will be sampled with the clear pixels in the outer border we add. This sampling is much faster but not as high-quality.
 
    CGSize imageSizeWithBorder = CGSizeMake([image size].width + 2, [image size].height + 2);
    UIGraphicsBeginImageContext(imageSizeWithBorder);
    // The image starts off filled with clear pixels, so we don't need to explicitly fill them here.
    [image drawInRect:(CGRect){{1,1}, [image size]}];
    [imageView setImage:UIGraphicsGetImageFromCurrentImageContext()];
    UIGraphicsEndImageContext();
 
    // We no longer need CA's edge antialiasing on this layer
    [[imageView layer] setEdgeAntialiasingMask:0];
 
    return imageView;
}

阴影绘制:

- (void)layoutImageView:(UIImageView *)imageView inFrame:(CGRect)celFrame withAnimationDuration:(NSTimeInterval)duration
{
    // The image view may be rotated, so set its frame in terms of the identity transform and then reapply the transform.
    CGAffineTransform imageViewTransform = [image View transform];
    [imageView setTransform:CGAffineTransformIdentity];
    [imageView setFrame:CenteredRectInRect(CGRectMake(0, 0, [imageView image].size.width, [imageView image].size.height), celFrame)];
    [imageView setTransform:imageViewTransform];
 
    // Save Core Animation a pass to figure out where the transparent pixels are by informing it explicitly of the contents's shape.
    CGPathRef oldPath = CGPathRetain([[imageView layer] shadowPath]);
    [[imageView layer] setShadowPath:[[UIBezierPath bezierPathWithRect:[imageView bounds]] CGPath]];
 
    // Since the layer's delegate (its UIView) will not create an action for this change (via the CALayerDelegate method actionForLayer:forKey:), we must explicitly create the animation between these values.
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
    [pathAnimation setFromValue:(id)oldPath];
    [pathAnimation setToValue:(id)[[imageView layer] shadowPath]];
    [pathAnimation setDuration:duration];
    [pathAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [pathAnimation setRemovedOnCompletion:YES];
 
    [[imageView layer] addAnimation:pathAnimation forKey:@"shadowPath"];
    CGPathRelease(oldPath);
}

以上就是这个session中个人觉得比较精髓的部分了,一些地方用中文表达的也不是太好,代码都是手打的,难免有错误,如有问题还请留言指正 对这篇文章涉及的内容感兴趣的同学也看看这篇文章(在墙外,如有需要我可以帮忙做个pdf传上来)

—以上—

[WWDC 2011 Summary & Practice] [AppFramwork] Session 100

What’s New in Cocoa Touch


WWDC 2011的视频真心不错,身为iOS开发者必看,但是光看也记不住什么,就发个博文一方面当做笔记,一方面与懒得看视频的人分享一下其中的部分内容.

虽然很久以前就把Session 100看完了,但还是决定先用这个练练手,内容我尽量覆盖全,并且提供一些我测试的例子,也会贴些图.写过博客的都知道贴图是很繁琐的,所以请原谅的我的懒惰,我真的没有那么充裕的时间去截很多图.

WWDC 2011视频以及 Keynote(PDF格式)下载详见这里http://developer.apple.com/videos/(需要注册个开发者账号,但是不是交$99那种,“野生"开发者即可)


UIStepper

UIStepper

一个新控件,附带很少的属性,和UIButton用法差不多:

UIStepper *stepper = [[UIStepper alloc] initWithFrame:CGRectMake(44, 44, 100, 44)];
[self.view addSubview:stepper];
[stepper addTarget:self action:@selector(stepperTouched:) forControlEvents:UIControlEventTouchUpInside];
 
- (void)stepperTouched:(id)sender {
NSLog(@"stepper touched");
}

UIAlertView

终于可以在AlerView里输入文字了,真是佩服iOS5之前那些牛人怎么想到AlertView的各种使用技巧的.

typedef enum {
      UIAlertViewStyleDefault = 0,
      UIAlertViewStyleSecureTextInput,
      UIAlertViewStylePlainTextInput,
      UIAlertViewStyleLoginAndPasswordInput
  } UIAlertViewStyle;
@property(nonatomic,assign) UIAlertViewStyle alertViewStyle;
- (UITextField *)textFieldAtIndex:(NSInteger)textFieldIndex

UIScreenOverscanCompensation

跟外接屏幕有关,本人目前为止还没遇到使用这个功能的情景,不多评论

typedef enum {
     UIScreenOverscanCompensationScale,
     UIScreenOverscanCompensationInsetBounds,
     UIScreenOverscanCompensationInsetApplicationFrame
 } UIScreenOverscanCompensation;
 @property(nonatomic) UIScreenOverscanCompensation
 overscanCompensation;

UIScreen

可以像iOS一样调节亮度了,两个新属性:

@property (nonatomic) CGFloat brightness;
@property (nonatomic) BOOL wantsSoftwareDimming;

结合之前的UIStepper使用:

UIStepper *stepper = [[UIStepper alloc] initWithFrame:CGRectMake(44, 44, 100, 44)];
[stepper setMaximumValue:2.0];
[stepper setMinimumValue:-2.0];
[stepper setStepValue:0.1];
[self.view addSubview:stepper];
[stepper addTarget:self action:@selector(stepperTouched:) forControlEvents:UIControlEventTouchUpInside];
[[UIScreen mainScreen] setWantsSoftwareDimming:YES];
 
- (void)stepperTouched:(id)sender {
    NSLog(@"stepper touched with value %f", [(UIStepper *)sender value]);
    [[UIScreen mainScreen] setBrightness:[(UIStepper *)sender value]];
}

brightness范围在0.0-1.0,wantsSoftwareDimming是使用软件模拟来获得更低的亮度,但是开启会损失性能,本人实测似乎没有效果,或许是方法不对.鉴于不太实用,暂且搁置.

 

继续阅读[WWDC 2011 Summary & Practice] [AppFramwork] Session 100

10.7.2+iCloud+iOS5简评

冒着各种各样的危险升级了(我是黑苹果),简评一下,或者说是吐槽

10.7.2:

  • LaunchPad图标变大了,终于变大了,之前的小图标真是点3次也点不中啊,不过总感觉比以前丑了.
  • 支持拖动文件到全屏程序了,在此之前我的mail是全屏模式,添加附件特别但疼,开始的时候先恢复成窗口模式然后再托文件进去,后来用了usb overdrive把鼠标滚轮左右设置成切换桌面快捷键,接着按住鼠标拖动文件,同时拨动滚轮,成功,费死劲了.
  • 黑苹果N卡DSDT驱动的话,需要在DSDT里面小改一下,不然开机黑屏(显示器没信号):
    "device_type", 
    Buffer (0x0D)
    {
        "NVDA,Geforce"
    },
    改成

     

    "device_type", 
    Buffer (0x0C)
    {
        "NVDA,Parent"
    },
    以上方法来自 pcbeta.com,最好在升级10.7.2前改,除非你喜欢在Win下搞DSDT

     

  • 更新中提到safari提升性能,这个估计不跑分也看不出啥来.另外还有说温度低了,开机快了的,我是黑苹果,没什么参考价值,就不说了.
  • 10.16更新10.7.2的Application文件夹(我放在dock上了)图标加载不卡了.

iCloud,这个测试版的时候就一直在用,跟测试版没有太大区别:

  • 同步联系人,日历很happy,不用依赖与Google了,要么两个一起还重复,毕竟Mac用不了Google的Exchange,只有iOS可以
  • iCloud网页端的Mail挺好用的,编辑文字选项比较多,可以单独设置字体,颜色等等

iOS 5

  • 一些UI真的感觉变丑了,iPod变成音乐+视频了,感觉那个音乐图标没有iPod图标好看,等完美越狱出了看看怎么改回去.
  • 一些HDU提示感觉长的都跟Cydia里面的提示似的,大大的汉字提示显得有些恶心
  • iCloud备份目前不好使,提示备份失败,原因不明
  • FaceTime激活似乎不需要发短信了,AppleID也可以,注意提示你激活FaceTime时选取消
  • 同步时不会锁屏,正常使用,很方便
  • iMessage发送方法:进入特定联系人,选择发送信息,选择邮箱发送.直接在发信息时添加联系人似乎不行.蓝色背景的信息是通过iMessage发送的,绿色是短信,偏好设置里面有对iMessage的一些设置建议看看.
  • 设置里面多了好多细节设置,比如"定位"里面的"系统服务"
  • Newsstand就是报刊杂志App放在一起,支持图标改变,没啥神奇之处,iTunes里面还是当App处理,内容还是InApp Purchase
  • Wifi-Sync不错,以后可以少占一个USB口了,就是需要先在iTunes里面设置好同步内容,如要更改就只能插到电脑上.因为Wifi同步之前iTunes看不到设备,现在也能在不连接USB的情况下在iTunes里看到设备了
    具体使用方法:
    1.iDevice通过USB连接到电脑
    2.选中"通过Wi-Fi与此设备同步(必须有这步,要不然下一步做不了)
    3.在你的iDevice>>设置>>通用>>iTunesWiFi同步 里面选择同步
  • 自带软件功能细节增强,还有一些都介绍烂了的就不在这里罗列了.

总结:Lion渐渐成熟一些了,其他感觉都是初期,出的有些仓促,各种细节问题不少,不过也可以理解,毕竟现在竞争越来越激烈了.

–以上–