关于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 ;

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

而新的模板是这样的:

#import ;

#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 
#else
#import 
#import 
#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, "[email protected]:");

    // 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, "[email protected]:@");

其中types参数为"[email protected]:@“,按顺序分别表示:

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

本文参考了:

推荐去看看
—以上—