日期: 2021 年 3 月 31 日

如何在 4 层协议的基础上实现域名转发

有一个特殊的需求:需要在 4 层协议上实现域名转发
正常情况下域名在 http 层,

这个能实现吗?各位有没有好的方案
目前尝试过 nginx 和 haproxy 都实现不了

第 1 条附言 · 2019-02-15 11:02:35 +08:00
表达能力太弱。。。。
原始需求:实现一个支持域名分发、负载均衡的 4 层高性能代理服务器,
关键点在:要支持在第四层,也就是传输层 解析出请求要访问的域名,然后根据这个域名将请求转发出去
第 2 条附言 · 2019-02-15 11:37:36 +08:00
先谢谢大家
了解到不存在“支持在第四层解析出域名”的情况,现在把需求改为:
实现一个支持域名分发、负载均衡的高性能代理服务器,
因为开启 TLS 双向验证,如何在握手失败前解析出 Host,然后根据 Host 进行分发
有一位朋友建议使用 sni

momocraft 2019-02-15 10:27:00 +08:00
什么叫 4 层协议和域名转发…
xenme 2
xenme 2019-02-15 10:32:07 +08:00
同没看懂,感觉像是反代
iAcn 3
iAcn 2019-02-15 10:37:12 +08:00 via Android
四层 => 传输层?
域名转发 => 代理?
CMGS 4
CMGS 2019-02-15 10:40:59 +08:00
就是 tcp 层面转发吧。。
gam2046 5
gam2046 2019-02-15 10:43:13 +08:00
http 层???

OSI 模型并没有所谓的 HTTP 层,HTTP 协议是属于第七层;
而第四层是属于 TCP/UDP 协议这样的;
同样的是,DNS 也属于七层;

所以并不懂你说的什么意思。
Y4ssss 6
Y4ssss 2019-02-15 10:44:32 +08:00
@iAcn @CMGS 是的
Y4ssss 7
Y4ssss 2019-02-15 10:51:52 +08:00
@gam2046 实现一个支持域名分发、负载均衡的 4 层高性能代理服务器,要支持在第四层解析出域名
Y4ssss 8
Y4ssss 2019-02-15 10:53:07 +08:00
原始需求:实现一个支持域名分发、负载均衡的 4 层高性能代理服务器,
关键点在:要支持在第四层,也就是传输层解析出域名
oott123 9
oott123 2019-02-15 11:04:04 +08:00
要解析出域名,你就变成 7 层了,当然你可以解析出来之后原样把 tcp 包发过去……
lychnis 10
lychnis 2019-02-15 11:06:05 +08:00 via Android
大厂一般都有这些东西
没有就自己写一个

Y4ssss 2019-02-15 11:10:08 +08:00
@oott123 开启了双向 TLS 验证,没有客户端密钥时,怎么解析
Y4ssss 13
Y4ssss 2019-02-15 11:10:36 +08:00
@lychnis 能提供下思路吗
oott123 14
oott123 2019-02-15 11:13:42 +08:00
@Y4ssss sni 试试? tls 不太熟,不确定 sni 加密了没,握手的时候如果没有 esni 应该是没加密的
lychnis 15
lychnis 2019-02-15 11:17:23 +08:00 via Android
负载均衡应该做过吧 ? nginx 之类能不能实现我不了解,网上查查
你要是一点背景知识都不会的话,,,这论坛上不可能说的清楚的
xihefeng 16
xihefeng 2019-02-15 11:17:24 +08:00 via Android
没有用的,你了解下 tcp 协议就知道,不可能,在第四层的时候,只有 ip 和端口的,没有域名的概念
gam2046 17
gam2046 2019-02-15 11:18:41 +08:00
四层没有域名啊。

域名这个东西是七层搞出来的。同样,DNS 是工作的七层,目的是将七层自定义的“域名”与三层的 IP 做一个映射关系。

所以不存在“支持在第四层解析出域名”的情况。

而 IP 协议是工作在三层的,我们常说的 TCP/IP 是协议簇,而不是一个协议。TCP 与 IP 是分开的。

当信息流进入四层的 TCP 后,已经只存在 IP 信息了。

你这不是一个伪需求,而是一个不存在的需求。

不过四层是可以做负载均衡的。

所以,如果如是我理解错你的意思的话,那就是你对这些概念理解有一点偏差。
Y4ssss 18
Y4ssss 2019-02-15 11:24:51 +08:00
@xihefeng
@gam2046 了解了,谢谢 2 位解惑
AstroProfundis 19
AstroProfundis 2019-02-15 11:28:39 +08:00
需求描述有点奇怪,楼上已经说了,在四层是不存在域名的概念的,解析出域名就变成一个七层的事情了

但我猜你需要的是类似 LVS 的东西?
Y4ssss 20
Y4ssss 2019-02-15 11:40:12 +08:00
@AstroProfundis 难点主要在开启 TLS 时如何解析出 host,根据 host 进行分发,我了解的 LVS 主要是负载均衡功能
rockyou12 21
rockyou12 2019-02-15 12:26:06 +08:00
@Y4ssss 确实没读懂 lz 的需求,但 nginx 的反代是支持单机、对多个开启 tls 的域名分别进行反代的,我司生产也是这么用的
Y4ssss 22
Y4ssss 2019-02-15 14:00:44 +08:00
@rockyou12 可以贴下 nginx 的 tls 域名反代 配置信息吗
Y4ssss 23
Y4ssss 2019-02-15 14:04:10 +08:00
@Y4ssss 这边有一点不同,代理服务器上没有证书,TLS 握手不能成功,所以正常情况下取不到域名,所以才有上述需求
lty1993 24
lty1993 2019-02-15 14:20:36 +08:00
我能想到的只有 SNI 了。如果客户端不支持 SNI 就没办法了。
lty1993 25
lty1993 2019-02-15 14:21:03 +08:00
如果只是要根据 SNI 进行转发,我记得 HAProxy 应该是可以的。
Y4ssss 26
Y4ssss 2019-02-15 14:30:43 +08:00
@lty1993 谢谢,我去试试
reus 27
reus 2019-02-15 14:52:49 +08:00
不就是中间人攻击嘛
如果可以实现,那还要 tls 之类的干嘛?
定期做 dns 查询,根据域名对应的 ip 做转发即可
当然也只是转发,想解析是不可能的
coderscala 28
coderscala 2019-02-15 15:15:23 +08:00
DNS 负载均衡
rockyou12 29
rockyou12 2019-02-15 22:33:54 +08:00
@Y4ssss 没证书别想了……就像 27 楼说的,你这是做中间人攻击

阿里云轻量服务器和 ESC 服务器该选哪个?

阿里云轻量服务器和 ESC 服务器该选哪个?价格都一样。有用过的大佬吗?

blueskea 2
blueskea 2018-12-20 00:35:41 +08:00 via Android
前者带宽是 5M,后者是 1M,其他差别不是很清楚
ithou 3
ithou 2018-12-20 00:48:46 +08:00 via Android
@blueskea 1000G 也用不完,学生机而已。感觉 5M 要好一些。另外腾讯有一款 98 一年的服务器,有点心动。不知道怎么选了 ??
liuwenxi163 4
liuwenxi163 2018-12-21 18:00:57 +08:00
没记错的话一个是实实在在的服务器,一个是只有网页控制台,只能跑 nginx,php,mysql 环境的“服务器”
ithou 5
ithou 2018-12-21 18:38:09 +08:00 via Android
@liuwenxi163 ?? 轻量服务器可以跑 Django 吗?貌似不可以连数据库?(有帖子这样反馈的)
jim9606 6
jim9606 2018-12-26 03:25:51 +08:00
轻量服务器也是 kvm 的,可以换内核,不过带宽高一些,我反正主要是为了网络,性能不在乎,ECS 弄到 5M 要贵不少,所以我选前者
ithou 7
ithou 2018-12-26 08:22:44 +08:00 via Android
@jim9606 轻量的是不是对一些功能有限制?不是很明白
ayconanw 8
ayconanw 2019-01-17 17:23:39 +08:00
@ithou 没有限制,跟 ecs 并无本质区别。ssh 连上去该怎么操作怎么操作
ithou 9
ithou 2019-01-17 19:37:07 +08:00 via Android
@ayconanw 好的,谢谢!
SuXiaoJin 10
SuXiaoJin 2019-02-01 09:38:13 +08:00 via Android
这个机子是学生机,一个月 9.5 元

opengps 2019-03-23 08:13:21 +08:00 via Android
都是学生机,那么只要你的应用是单机应用,那就毫无疑问轻量合适
ChinaClouder 12
ChinaClouder 2019-10-11 09:40:53 +08:00
关于学生机轻量服务器和 ESC 服务器的选择问题
轻量应用服务器:1 核 CPU/2G 内存 /5M 宽带 /40G SSD 云盘;
ECS 云服务器:1 核 CPU/2G 内存 /1M 宽带 /40G 高效云盘。
公网宽带对比
宽带方面轻量应用服务器 5M 峰值宽带,而 ECS 云服务器只有 1M ;但是轻量应用服务器是限制流量的,每月 1000G 流量包,但是对于一般用户而言 1000G/月的流量足够用了。如果流量每月不超 1000G,5M 宽带的轻量应用服务器更香。

云盘对比
轻量应用服务器配备的是 40G 的 SSD 云盘,而 ECS 云服务器配备的是 40G 的高效云盘,云盘性能方面 SS 云盘完胜高效云盘,所以云盘方面轻量应用服务器又赢了一局。

集群对比
轻量应用服务器只能做单机应用,而 ECS 云服务器可以做集群使用。

操作系统及应用性
轻量应用服务器支持应用镜像和系统镜像,系统镜像支持较少,轻量应用服务器对于技术门槛要求较低,适用于个人用户使用。ECS 云服务器支持的系统镜像会丰富一些,需要用户有一些技术基础。

综上,如果您购买学生云服务器是用来搭建单机应用,如网站,并且流量每月不超过 1000G,那么轻量应用服务器是*佳的选择。如果需要搭集群,那么只能选 ECS 云服务器。

原文: http://www.xueshengfuwuqi.com/aliyun/swas-ecs.html
Tucaizhu 13
Tucaizhu 2020-03-20 15:37:34 +08:00
单机应用选轻量,轻量 5M 宽带而且是 SSD 云盘 https://dashi.aliyun.com/site/cloud/student

SCDPM2019服务器备份

SCDPM2019服务器备份

1.保护-新建-服务器

%title插图%num

2.选择你服务器要备份的文件夹

%title插图%num

3.保护组取名

%title插图%num

4.设置保持器、同步频率、恢复点时间(客户端*少是一小时备份一次,服务器*小时间能15分钟一次)

%title插图%num

5.默认下一步

%title插图%num

6.下一步

%title插图%num

7.默认即可

%title插图%num

8.创建组

%title插图%num

9.创建成功

%title插图%num

10.可以再监视下查看详细情况,是否报警、任务状态

%title插图%num

iOS图片加工—图片水印,图片裁剪和屏幕截图

一.图片水印

1.创建个UIImageView
@property (weak, nonatomic) IBOutlet UIImageView *neImage;

2.创建个方法实现水印功能
– (void)viewDidLoad {
[super viewDidLoad];

UIImage *bgImage = [UIImage imageNamed:@””];

//创建一个位图上下文
UIGraphicsBeginImageContextWithOptions(bgImage.size, NO, 0.0);

//将背景图片画入位图中
[bgImage drawInRect:CGRectMake(0, 0, bgImage.size.width, bgImage.size.height)];

//将水印Logo画入背景图中
UIImage *waterIma = [UIImage imageNamed:@””];
[waterIma drawInRect:CGRectMake(bgImage.size.width – 40 – 5, bgImage.size.height – 40 – 5, 40, 40)];

//取得位图上下文中创建的新的图片
UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//在创建的ImageView上显示出新图片
self.neImage.image = newimage;

//压缩新照片为PNG格式的二进制数据
NSData *data = UIImagePNGRepresentation(newimage);

//将图片写入到手机存储中
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”new.png”];
[data writeToFile:path atomically:YES];

}
二.图片裁剪

//1.加载原图
UIImage *oldImage = [UIImage imageNamed:@”me”];

//2.获取位图上下文
CGFloat bigCic = oldImage.size.width + 2 * 2;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(bigCic, bigCic), NO, 0.0);

//3.画大圆
[[UIColor whiteColor] set];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddArc(ctx, bigCic * 0.5, bigCic * 0.5, bigCic * 0.5, 0, M_PI * 2, 0);
CGContextFillPath(ctx);

//4.画小圆
CGFloat smallCic = oldImage.size.width * 0.5;
CGContextAddArc(ctx, bigCic * 0.5 , bigCic * 0.5, smallCic, 0, M_PI * 2, 0);
CGContextClip(ctx);

//5.画图
[oldImage drawInRect:CGRectMake(2, 2, oldImage.size.width, oldImage.size.height)];

//6.获取新图
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

//7.结束上下文
UIGraphicsEndImageContext();

//8.显示新图
self.IconView.image = newImage;

//9.写入到手机存储
NSData *data = UIImagePNGRepresentation(newImage);
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”newClip.png”];
[data writeToFile:path atomically:YES];
三,屏幕截图

//1.开启位图上下文
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);

//2.渲染截图
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];

//3.获取新图
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

//4.写入到手机存储
NSData *data = UIImagePNGRepresentation(newImage);
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”newClip.png”];
[data writeToFile:path atomically:YES];

//5.关闭上下文
UIGraphicsEndImageContext();

iOS基础教程:深入浅出 Cocoa 之 Core Data

Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂。Core Data 相关的类比较多,初学者往往不太容易弄懂。计划用三个教程来讲解这一部分:

框架详解:讲解 Core data 框架,运作过程,设计的类;
Core data应用程序示例:通过生成一个使用 Core data 的应用程序来讲解如何 在 XCode 4 中使用 Core data。
手动创建Core data示例:不利用框架自动生成代码,完全自己编写所有的 Core data 相关代码的命令行应用程序来深入讲解 Core data的使用。

本文为*部份:框架详解

一,概观
下面先给出一张类关系图,让我们对它有个总体的认识。

%title插图%num

在上图中,我们可以看到有五个相关模块:
1, Managed Object Model
Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)

2,Managed Object Context
Managed Object Context 参与对数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。

3,Persistent Store Coordinator
Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。

4,Managed Object
Managed Object 数据对象,与 Managed Object Context 相关联。

5,Controller
图中绿色的 Array Controller, Object Controller, Tree Controller 这些控制器,一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据。

这写模块是怎样运作的呢?
%title插图%num

1,应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为 xcdatamodeld)读取。
2,然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,前者对用户透明地调用后者对数据文件进行读写。
3,NSPersistentStoreCoordinator 负责从数据文件(xml, sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件。
4,NSManagedObjectContext 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是*常被用到的。
5,Array Controller, Object Controller, Tree Controller 这些控制器一般与 NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象。

二, Model class

模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 “Description” 结尾。我们可以看出:模型就是描述数据类型以及其关系的。

主要的 Model class 有:

Model Classes
Managed Object Model NSManagedObjectModel 数据模型
Entity NSEntityDescription 抽象数据类型,相当于数据库中的表
Property NSPropertyDescription Entity 特性,相当于数据库表中的一列
> Attribute NSAttributeDescription 基本数值型属性(如Int16, BOOL, Date等类型的属性)
> Relationship NSRelationshipDescription 属性之间的关系
> Fetched Property NSFetchedPropertyDescription 查询属性(相当于数据库中的查询语句)

1)Entity – NSEntityDescription
Entity 相当于数据库中的一个表,它描述一种抽象数据类型,其对应的类为 NSManagedObject 或其子类。

NSEntityDescription 常用方法:
+insertNewObjectForEntityForName:inManagedObjectContext: 工厂方法,根据给定的 Entity 描述,生成相应的 NSManagedObject 对象,并插入 ManagedObjectContext 中。
-managedObjectClassName返回映射到 Entity 的 NSManagedObject 类名
-attributesByName以名字为 key, 返回 Entity 中对应的 Attributes
-relationshipsByName以名字为 key, 返回 Entity 中对应的 Relationships

2)Property – NSPropertyDescription
Property 为 Entity 的特性,它相当于数据库表中的一列,或者 XML 文件中的 value-key 对中的 key。它可以描述实体数据(Attribute),Entity之间的关系(RelationShip),或查询属性(Fetched Property)。

> Attribute – NSAttributeDescription
Attribute 存储基本数据,如 NSString, NSNumber or NSDate 等。它可以有默认值,也可以使用正则表达式或其他条件对其值进行限定。一个属性可以是 optional 的。

> Relationship – NSRelationshipDescription
Relationship 描述 Entity,Property 之间的关系,可以是一对一,也可以是一对多的关系。

> Fetched Property – NSFetchedPropertyDescription
Fetched Property 根据查询谓词返回指定 Entity 的符合条件的数据对象。

上面说的比较抽象,举个例子来说,

我们有一个 CocoaDataDemo.xcdatamodeld 模型文件,应用程序根据它生成一个 NSManagedObjectModel 对象,这个模型有三个 Entity,每个 Entity 又可包含 Attribute Relationship, Feteched Property 三种类型的 Property。在本例中, Author Entity 包含两个Attribute : name 和 email,它们对于的运行时类均为 NSManagedObject;还包含一个与 Post 的 Relationship;没有设置 Feteched Property。

我们通常使用 KVC 机制来访问 Property。下面来看代码:

  1. NSManagedObjectContext * context = [[NSApp delegate] managedObjectContext];
  2. NSManagedObject * author = nil;
  3. author = [NSEntityDescription insertNewObjectForEntityForName: @“Author” inManagedObjectContext: context];
  4. [author setValue: @“nemo@pixar.com” forKey: @“email”];
  5. NSLog (@“The Author’s email is: %@”, [author valueForKey:@“email”]);

在上面代码中,我们先取得 NSManagedObjectContext, 然后调用 NSEntityDescription 的方法,以 Author 为实体模型,生成对应的 NSManagedObject 对象,插入 NSManagedObjectContext 中,然后给这个对象设置特性 email 的值。

三,运行时类与对象
> Managed Object – NSManagedObject
Managed Object 表示数据文件中的一条记录,每一个 Managed Object 在内存中对应 Entity 的一个数据表示。Managed Object 的成员为 Entity 的 Property 所描述。
比如在上面的代码,author 这个 NSManagedObject,对应名为 Author 的 Entity。

每一个 Managed Object 都有一个全局 ID(类型为:NSManagedObjectID)。Managed Object 会附加到一个 Managed Object Context,我们可以通过这个全局 ID 在 Managed Object Context 查询对应的 Managed Object。

NSManagedObject 常用方法
-entity 获取其 Entity
-objectID 获取其 Managed Object ID
-valueForKey: 获取指定 Property 的值
-setValue: forKey: 设定指定 Property 的值

> Managed Object Context – NSManagedObjectContext
Managed Object Context 的作用相当重要,对数据对象进行的操作都与它有关。当创建一个数据对象并插入 Managed Object Context 中,Managed Object Context 就开始跟踪这个数据对象的一切变动,并在合适的时候提供对 undo/redo 的支持,或调用 Persistent Store Coordinato 将变化保存到数据文件中去。

通常我们将 controller 类(如:NSArrayController,NSTreeController)或其子类与 Managed Object Context 绑定,这样就方便我们动态地生成,获取数据对象等。

NSManagedObjectContext 常用方法
-save: 将数据对象保存到数据文件
-objectWithID: 查询指定 Managed Object ID 的数据对象
-deleteObject: 将一个数据对象标记为删除,但是要等到 Context 提交更改时才真正删除数据对象
-undo 回滚*后一步操作,这是都 undo/redo 的支持
-lock 加锁,常用于多线程以及创建事务。同类接口还有:-unlock and -tryLock
-rollback 还原数据文件内容
-reset 清除缓存的 Managed Objects。只应当在添加或删除 Persistent Stores 时使用
-undoManager 返回当前 Context 所使用的 NSUndoManager
-assignObject: toPersistantStore: 由于 Context 可以管理从不同数据文件而来的数据对象,
这个接口的作用就是指定数据对象的存储数据文件(通过指定 PersistantStore 实现)
-executeFetchRequest: error: 执行 Fetch Request 并返回所有匹配的数据对象

> Persistent Store Coordinator – NSPersistentStoreCoordinator
使用 Core Data document 类型的应用程序,通常会从磁盘上的数据文中中读取或存储数据,这写底层的读写就由 Persistent Store Coordinator 来处理。一般我们无需与它直接打交道来读写文件,Managed Object Context 在背后已经为我们调用 Persistent Store Coordinator 做了这部分工作。

NSPersistentStoreCoordinator 常用方法
-addPersistentStoreForURL:configuration:URL:options:error: 装载数据存储,对应的卸载数据存储的接口为 -removePersistentStore:error:
-migratePersistentStore:toURL:options:withType:error: 迁移数据存储,效果与 “save as”相似,但是操作成功后,
迁移前的数据存储不可再使用
-managedObjectIDForURIRepresentation: 返回给定 URL所指示的数据存储的 object id,如果找不到匹配的数据存储则返回 nil
-persistentStoreForURL: 返回指定路径的 Persistent Store
-URLForPersistentStore: 返回指定 Persistent Store 的存储路径

> Persistent Document – NSPersistentDocument
NSPersistentDocument 是 NSDocument 的子类。 multi-document Core Data 应用程序使用它来简化对 Core Data 的操作。通常使用 NSPersistentDocument 的默认实现就足够了,它从 Info.plist 中读取 Document types 信息来决定数据的存储格式(xml,sqlite, binary)。

NSPersistentDocument 常用方法
-managedObjectContext 返回文档的 Managed Object Context,在多文档应用程序中,每个文档都有自己的 Context。
-managedObjectModel 返回文档的 Managed Object Model

四,Fetch Requests
Fetch Requests 相当于一个查询语句,你必须指定要查询的 Entity。我们通过 Fetch Requests 向 Managed Object Context 查询符合条件的数据对象,以 NSArray 形式返回查询结果,如果我们没有设置任何查询条件,则返回该 Entity 的所有数据对象。我们可以使用谓词来设置查询条件,通常会将常用的 Fetch Requests 保存到 dictionary 以重复利用。

示例:

  1. NSManagedObjectContext * context = [[NSApp delegate] managedObjectContext];
  2. NSManagedObjectModel * model = [[NSApp delegate] managedObjectModel];
  3. NSDictionary * entities = [model entitiesByName];
  4. NSEntityDescription * entity = [entities valueForKey:@“Post”];
  5. NSPredicate * predicate;
  6. predicate = [NSPredicate predicateWithFormat:@“creationDate > %@”, date];
  7. NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey:@“title”];
  8. NSArray * sortDescriptors = [NSArray arrayWithObject: sort];
  9. NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
  10. [fetch setEntity: entity];
  11. [fetch setPredicate: predicate];
  12. [fetch setSortDescriptors: sortDescriptors];
  13. NSArray * results = [context executeFetchRequest:fetch error:nil];
  14. [sort release];
  15. [fetch release];

在上面代码中,我们查询在指定日期之后创建的 post,并将查询结果按照 title 排序返回。

NSFetchRequest 常用方法
-setEntity: 设置你要查询的数据对象的类型(Entity)
-setPredicate: 设置查询条件
-setFetchLimit: 设置*大查询对象数目
-setSortDescriptors: 设置查询结果的排序方法
-setAffectedStores: 设置可以在哪些数据存储中查询

参考资料:
Core Data ReferenceAPI listing for the Core Data classes
http://developer.apple.com/documentation/Cocoa/Reference/CoreData_ObjC/index.html

NSPredicate ReferenceAPI listing for NSPredicate

http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSPredicate.html

 

前面详细讲解了 Core Data 的框架以及设计的类,下面我们来讲解一个完全手动编写代码使用这些类的示例,这个例子来自苹果官方示例。在这个例子里面,我们打算做这样一件事情:记录程序运行记录(时间与 process id),并保存到xml文件中。我们使用 Core Data 来做这个事情。

 

一,建立一个新的 Mac command-line tool application 工程,命名为 CoreDataTutorial。为支持垃圾主动回收机制,点击项目名称,在右边的 Build Setting 中查找 garbage 关键字,将找到的 Objective-C Garbage Collection 设置为 Required [-fobj-gc-only]。并将 main.m 中 的 main() 方法修改为如下:

  1. int main (int argc, const char * argv[])
  2. {
  3. NSLog(@” === Core Data Tutorial ===”);
  4. // Enable GC
  5. //
  6. objc_startCollectorThread();
  7. return 0;
  8. }

二,创建并设置模型类

在 main() 之前添加如下方法:

  1. NSManagedObjectModel *managedObjectModel()
  2. {
  3. static NSManagedObjectModel *moModel = nil;
  4. if (moModel != nil) {
  5. return moModel;
  6. }
  7. moModel = [[NSManagedObjectModel alloc] init];
  8. // Create the entity
  9. //
  10. NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
  11. [runEntity setName:@“Run”];
  12. [runEntity setManagedObjectClassName:@“Run”];
  13. [moModel setEntities:[NSArray arrayWithObject:runEntity]];
  14. // Add the Attributes
  15. //
  16. NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
  17. [dateAttribute setName:@“date”];
  18. [dateAttribute setAttributeType:NSDateAttributeType];
  19. [dateAttribute setOptional:NO];
  20. NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc] init];
  21. [idAttribute setName:@“processID”];
  22. [idAttribute setAttributeType:NSInteger32AttributeType];
  23. [idAttribute setOptional:NO];
  24. [idAttribute setDefaultValue:[NSNumber numberWithInteger:-1]];
  25. // Create the validation predicate for the process ID.
  26. // The following code is equivalent to validationPredicate = [NSPredicate predicateWithFormat:@”SELF > 0″]
  27. //
  28. NSExpression *lhs = [NSExpression expressionForEvaluatedObject];
  29. NSExpression *rhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInteger:0]];
  30. NSPredicate *validationPredicate = [NSComparisonPredicate
  31. predicateWithLeftExpression:lhs
  32. rightExpression:rhs
  33. modifier:NSDirectPredicateModifier
  34. type:NSGreaterThanPredicateOperatorType
  35. options:0];
  36. NSString *validationWarning = @“Process ID < 1”;
  37. [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
  38. withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
  39. // set the properties for the entity.
  40. //
  41. NSArray *properties = [NSArray arrayWithObjects: dateAttribute, idAttribute, nil];
  42. [runEntity setProperties:properties];
  43. // Add a Localization Dictionary
  44. //
  45. NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
  46. [localizationDictionary setObject:@“Date” forKey:@“Property/date/Entity/Run”];
  47. [localizationDictionary setObject:@“Process ID” forKey:@“Property/processID/Entity/Run”];
  48. [localizationDictionary setObject:@“Process ID must not be less than 1” forKey:@“ErrorString/Process ID < 1”];
  49. [moModel setLocalizationDictionary:localizationDictionary];
  50. return moModel;
  51. }

 

在上面的代码中:

1)我们创建了一个全局模型 moModel;
2)并在其中创建一个名为 Run 的 Entity,这个 Entity 对应的 ManagedObject 类名为 Run(很快我们将创建这样一个类);
3)给 Run Entity 添加了两个必须的 Property:date 和 processID,分别表示运行时间以及进程 ID;并设置默认的进程 ID 为 -1;
4)给 processID 特性设置检验条件:必须大于 0;
5)给模型设置本地化描述词典;

本地化描述提供对 Entity,Property,Error信息等的便于理解的描述,其可用的键值对如下表:

Key

Value

“Entity/NonLocalizedEntityName”

“LocalizedEntityName”

“Property/NonLocalizedPropertyName/Entity/EntityName”

“LocalizedPropertyName”

“Property/NonLocalizedPropertyName”

“LocalizedPropertyName”

“ErrorString/NonLocalizedErrorString”

“LocalizedErrorString”

三,创建并设置运行时类和对象

由于要用到存储功能,所以我们必须定义持久化数据的存储路径。我们在 main() 之前添加如下方法设置存储路径:

  1. NSURL *applicationLogDirectory()
  2. {
  3. NSString *LOG_DIRECTORY = @“CoreDataTutorial”;
  4. static NSURL *ald = nil;
  5. if (ald == nil)
  6. {
  7. NSFileManager *fileManager = [[NSFileManager alloc] init];
  8. NSError *error = nil;
  9. NSURL *libraryURL = [fileManager URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask
  10. appropriateForURL:nil create:YES error:&error];
  11. if (libraryURL == nil) {
  12. NSLog(@“Could not access Library directory\n%@”, [error localizedDescription]);
  13. }
  14. else
  15. {
  16. ald = [libraryURL URLByAppendingPathComponent:@“Logs”];
  17. ald = [ald URLByAppendingPathComponent:LOG_DIRECTORY];
  18. NSLog(@” >> log path %@”, [ald path]);
  19. NSDictionary *properties = [ald resourceValuesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] error:&error];
  20. if (properties == nil)
  21. {
  22. if (![fileManager createDirectoryAtPath:[ald path] withIntermediateDirectories:YES attributes:nil error:&error])
  23. {
  24. NSLog(@“Could not create directory %@\n%@”,
  25. [ald path], [error localizedDescription]);
  26. ald = nil;
  27. }
  28. }
  29. }
  30. }
  31. return ald;
  32. }

在上面的代码中,我们将持久化数据文件保存到路径: /Users/kesalin/Library/Logs/CoreDataTutorial 下。

下面,我们来创建运行时对象:ManagedObjectContext 和 PersistentStoreCoordinator。

  1. NSManagedObjectContext *managedObjectContext()
  2. {
  3. static NSManagedObjectContext *moContext = nil;
  4. if (moContext != nil) {
  5. return moContext;
  6. }
  7. moContext = [[NSManagedObjectContext alloc] init];
  8. // Create a persistent store coordinator, then set the coordinator for the context.
  9. //
  10. NSManagedObjectModel *moModel = managedObjectModel();
  11. NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:moModel];
  12. [moContext setPersistentStoreCoordinator: coordinator];
  13. // Create a new persistent store of the appropriate type.
  14. //
  15. NSString *STORE_TYPE = NSXMLStoreType;
  16. NSString *STORE_FILENAME = @“CoreDataTutorial.xml”;
  17. NSError *error = nil;
  18. NSURL *url = [applicationLogDirectory() URLByAppendingPathComponent:STORE_FILENAME];
  19. NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE
  20. configuration:nil
  21. URL:url
  22. options:nil
  23. error:&error];
  24. if (newStore == nil) {
  25. NSLog(@“Store Configuration Failure\n%@”, ([error localizedDescription] != nil) ? [error localizedDescription] : @“Unknown Error”);
  26. }
  27. return moContext;
  28. }

在上面的代码中:
1)我们创建了一个全局 ManagedObjectContext 对象 moContext;
2)并在设置其 persistent store coordinator,存储类型为 xml,保存文件名为:CoreDataTutorial.xml,并将其放到前面定义的存储路径下。

好,至此万事具备,只欠 ManagedObject 了!下面我们就来定义这个数据对象类。向工程添加 Core Data->NSManagedObject subclass 的类,名为 Run (模型中 Entity 定义的类名) 。

Run.h

  1. #import <CoreData/NSManagedObject.h>
  2. @interface Run : NSManagedObject
  3. {
  4. NSInteger processID;
  5. }
  6. @property (retain) NSDate *date;
  7. @property (retain) NSDate *primitiveDate;
  8. @property NSInteger processID;
  9. @end

Run.m

  1. //
  2. // Run.m
  3. // CoreDataTutorial
  4. //
  5. // Created by kesalin on 8/29/11.
  6. // Copyright 2011 kesalin@gmail.com. All rights reserved.
  7. //
  8. #import “Run.h”
  9. @implementation Run
  10. @dynamic date;
  11. @dynamic primitiveDate;
  12. – (void) awakeFromInsert
  13. {
  14. [super awakeFromInsert];
  15. self.primitiveDate = [NSDate date];
  16. }
  17. #pragma mark –
  18. #pragma mark Getter and setter
  19. – (NSInteger)processID
  20. {
  21. [self willAccessValueForKey:@“processID”];
  22. NSInteger pid = processID;
  23. [self didAccessValueForKey:@“processID”];
  24. return pid;
  25. }
  26. – (void)setProcessID:(NSInteger)newProcessID
  27. {
  28. [self willChangeValueForKey:@“processID”];
  29. processID = newProcessID;
  30. [self didChangeValueForKey:@“processID”];
  31. }
  32. // Implement a setNilValueForKey: method. If the key is “processID” then set processID to 0.
  33. //
  34. – (void)setNilValueForKey:(NSString *)key {
  35. if ([key isEqualToString:@“processID”]) {
  36. self.processID = 0;
  37. }
  38. else {
  39. [super setNilValueForKey:key];
  40. }
  41. }
  42. @end

注意:
1)这个类中的 date 和 primitiveDate 的访问属性为 @dynamic,这表明在运行期会动态生成对应的 setter 和 getter;
2)在这里我们演示了如何正确地手动实现 processID 的 setter 和 getter:为了让 ManagedObjecContext 能够检测 processID的变化,以及自动支持 undo/redo,我们需要在访问和更改数据对象时告之系统,will/didAccessValueForKey 以及 will/didChangeValueForKey 就是起这个作用的。
3)当我们设置 nil 给数据对象 processID 时,我们可以在 setNilValueForKey 捕获这个情况,并将 processID 置 0;
4)当数据对象被插入到 ManagedObjectContext 时,我们在 awakeFromInsert 将时间设置为当前时间。

三,创建或读取数据对象,设置其值,保存
好,至此真正的万事具备,我们可以创建或从持久化文件中读取数据对象,设置其值,并将其保存到持久化文件中。本例中持久化文件为 xml 文件。修改 main() 中代码如下:

  1. int main (int argc, const char * argv[])
  2. {
  3. NSLog(@” === Core Data Tutorial ===”);
  4. // Enable GC
  5. //
  6. objc_startCollectorThread();
  7. NSError *error = nil;
  8. NSManagedObjectModel *moModel = managedObjectModel();
  9. NSLog(@“The managed object model is defined as follows:\n%@”, moModel);
  10. if (applicationLogDirectory() == nil) {
  11. exit(1);
  12. }
  13. NSManagedObjectContext *moContext = managedObjectContext();
  14. // Create an Instance of the Run Entity
  15. //
  16. NSEntityDescription *runEntity = [[moModel entitiesByName] objectForKey:@“Run”];
  17. Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moContext];
  18. NSProcessInfo *processInfo = [NSProcessInfo processInfo];
  19. run.processID = [processInfo processIdentifier];
  20. if (![moContext save: &error]) {
  21. NSLog(@“Error while saving\n%@”, ([error localizedDescription] != nil) ? [error localizedDescription] : @“Unknown Error”);
  22. exit(1);
  23. }
  24. // Fetching Run Objects
  25. //
  26. NSFetchRequest *request = [[NSFetchRequest alloc] init];
  27. [request setEntity:runEntity];
  28. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@“date” ascending:YES];
  29. [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
  30. error = nil;
  31. NSArray *array = [moContext executeFetchRequest:request error:&error];
  32. if ((error != nil) || (array == nil))
  33. {
  34. NSLog(@“Error while fetching\n%@”, ([error localizedDescription] != nil) ? [error localizedDescription] : @“Unknown Error”);
  35. exit(1);
  36. }
  37. // Display the Results
  38. //
  39. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  40. [formatter setDateStyle:NSDateFormatterMediumStyle];
  41. [formatter setTimeStyle:NSDateFormatterMediumStyle];
  42. NSLog(@“%@ run history:”, [processInfo processName]);
  43. for (run in array)
  44. {
  45. NSLog(@“On %@ as process ID %ld”, [formatter stringForObjectValue:run.date], run.processID);
  46. }
  47. return 0;
  48. }

在上面的代码中:
1)我们先获得全局的 NSManagedObjectModel 和 NSManagedObjectContext 对象:moModel 和 moContext;
2)并创建一个Run Entity,设置其 Property processID 为当前进程的 ID;
3)将该数据对象保存到持久化文件中:[moContext  save : &error]。我们无需与 PersistentStoreCoordinator 打交道,只需要给 ManagedObjectContext 发送 save 消息即可,NSManagedObjectContext 会透明地在后面处理对持久化数据文件的读写;
4)然后我们创建一个 FetchRequest 来查询持久化数据文件中保存的数据记录,并将结果按照日期升序排列。查询操作也是由 ManagedObjectContext 来处理的:[moContext executeFetchRequest :request  error :&error];
5)将查询结果打印输出;

大功告成!编译运行,我们可以得到如下显示:

  1. 2011-09-03 21:42:47.556 CoreDataTutorial[992:903] CoreDataTutorial run history:
  2. 2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:41:56 as process ID 940
  3. 2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:16 as process ID 955
  4. 2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:20 as process ID 965
  5. 2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:24 as process ID 978
  6. 2011-09-03 21:42:47.559 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:47 as process ID 992

 

通过这个例子,我们可以更好理解 Core Data 的运作机制。在 Core Data 中我们*常用的就是 ManagedObjectContext,它几乎参与对数据对象的所有操作,包括对 undo/redo 的支持;而 Entity 对应的运行时类为 ManagedObject,我们可以理解为抽象数据结构 Entity 在内存中由 ManagedObject 来体现,而 Perproty 数据类型在内存中则由 ManagedObject 类的成员属性来体现。一般我们不需要与 PersistentStoreCoordinator 打交道,对数据文件的读写操作都由 ManagedObjectContext 为我们代劳了。

25条提高iOS App性能的技巧和诀窍

这篇文章来自iOS Tutorial Team 成员 Marcelo Fabri, 他是 Movile 的一个iOS开发者. Check out his personal website or follow him on Twitter.原文地址

     当我们开发iOS应用时,好的性能对我们的App来说是很重要的。你的用户也希望如此,但是如果你的app表现的反应迟钝或者很慢也会伤害到你的审核。

然而,由于IOS设备的限制有时很难工作得很正确。我们开发时有很多需要我们记住这些容易忘记的决定对性能的影响。

这是为什么我写这篇文章的原因。这篇文章用备忘录的形式集合了25个技巧和诀窍可以用来提高你的app性能。所以保持阅读来给你未来的App一个很不错的提高。

      Note:在优化代码之前,必须保证有个需要解决的问题!不要陷入”pre-optimizing(预优化)”你的代码。勤 用Instruments分析你的代码,发现任何一个需要提高的地方。Matt Galloway写了一个使用Instruments优化代码的的教程

   

    以下这些技巧分为三个不同那个的级别—基础,中级,高级。

   基础

   这些技巧你要总是想着实现在你开发的App中。

1. 用ARC去管理内存(Use ARC to Manage Memory)

2.适当的地方使用reuseIdentifier(Use a reuseIdentifier Where Appropriate)

3.尽可能设置视图为不透明(Set View as Opaque When Possible)

4.避免臃肿的XIBs文件(Avoid Fat XiBs)

5.不要阻塞主进程(Don’t Block the Main Thread)

6.调整图像视图中的图像尺寸(Size Images to Image Views)

7.选择正确集合(Choose the Correct Collection)

8.启用Gzip压缩(Enable GZIP Compression)

 

   中级

   这些技巧是当你遇到更复杂的情况的时候使用。

9. 重用和延迟加载视图(Reuse and Lazy Load Views)

10.缓存,缓存,缓存(Cache,Cache,Cache)

11.考虑绘图(Consider Drawing)

12.处理内存警告(Handle Memory Warnings)

13.重用大开销对象(Reuse Expensive Objects)

14.使用精灵表(Use Sprite Sheets )

15.避免重复处理数据(Avoid Re-Processing Data)

16.选择正确的数据格式(Choose the Right Data Format)

17.适当的设置背景图片(Set  Background Images Appropriately)

18.减少你的网络占用(Reduce Your Web Footprint)

19.设置阴影路径(Set the Shadow Path )

20.你的表格视图Optimize Your Table Views)

21.选择正确的数据存储方式(Choose Correct Data Storage Option)

 

高级

   这些技巧你应该只在你很积*认为它们能解决这个问题,而且你觉得用它们很舒适的时候使用。

22.加速启动时间(Speed up Launch Time )

23.使用自动释放池(Use AutoRelease Pool)

24.缓存图像(Cache Images-Or not )

25.尽可能避免日期格式化器(Avoid Date Formatters Where Possible)

没有其他的,一起去看看这些技巧吧!

 

 基础的性能提升

1)用ARC去管理内存

ARC是伴随IOS5 一起发布的,它用来消除常见的的内存泄漏。

ARC是”Automatic Reference Counting”的缩写。它自动管理你代码中的retain/release循环,这样你就不必手动做这事儿了。

下面这段代码展示了创建一个view的常用代码

查看源码

打印 ?

1 UIView *view =[[UIView alloc] init];
2 //...
3 [self.view addSubview:view];
4 [view release];

这里*其容易忘记在代码结束的地方调用release,ARC将会自动的,底层的为你做这些工作。

除了帮助你你避免内存泄漏,ARC还能保证对象不再使用时立马被回收来提高你的性能。你应该在你的工程里多用ARC。

这里是一些学习更多关于ARC的非常棒的资源

  • Apple’s official documentation 苹果的官方文档。
  • Matthijs Hollemans’s Beginning ARC in iOS Tutorial
  • Tony Dahbura’s How To Enable ARC in a Cocos2D 2.X Project
  • 如果你还是不确信ARC的好处,看看这篇文章 eight myths about ARC 说服你为什么用ARC。

值得注意的是ARC不能消除所有的内存泄漏。你依然有可能内存泄漏,这主要可能是由于blocks(块),引用循环,CoreFoundation对象管理不善(通常是C结构体,或者是确实很糟糕的代码)。

2)适当的地方使用reuseIdentifier

在app开发中的一个常见的为UITableViewCells,UICollectionViewCells,UITableViewHeaderFooterViews设置一个正确的reuseIdentifier(重用标识)。

为了*大化性能,一个tableView的数据源一般应该重用UITableViewCell对象,当它在tableView:cellForRowAtIndexPath:中分配数据给cells的时候。一个表视图维护了一个UITableViewCell对象的队列或者列表,这些对象已被数据源标记为重用。

如果你不用reuseIdentifier 会怎么样呢?

如果你用,你的tableview每显示一行将会配置一个全新的cell。这是非常费事的操作而且*对会影响你app滚动的性能。

自从引进了iOS6,你应该为header and footer 视图设置reuseIdentifiers,就像在 UICollectionView’s cells 和 supplementary views(补充视图)一样。

使用reuseIdentifiers,当你的数据源要求提供一个新的cell给tableview的时候调用这个方

查看源码

打印 ?

1 NSString *CellIdentifier = @"Cell";
2  
3 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

3)可能的时候设置视图为不透明

如果你有不透明视图(opaque views)–也就是说,没有透明度定义的视图,你应该设置他们的opaque属性为YES。

为什么? 这会允许系统以*优的方式绘制你的views。这是一个简单的属性可以在Interface Builder 和代码中设置。

苹果的文档 Apple documentation中有对这个属性的描述

这个属性提供了一个提示给图系统如何对待这个视图。如果设置为YES,绘制系统将会把这个视图视为完全不透明。这样允许系统优化一些绘制操作和提高性能。如果设置为NO,绘图系统会复合这个视图和其他的内容,这个属性的默认值是YES

在相对静态的屏幕上,设置opaque属性不会有什么大问题。尽管如此,如果你的视图是嵌入在一个scrollView,或者是一个复杂的动画的一部分,不设置这个属性*对会影响你的程序的性能。

你也可以使用Debug\Color olor Blended Layers选项 在你的模拟器中形象化的看见没有设置为不透明(opaque)的视图.你的目标应该是尽可能多的设置视图为透明。

4)  避免臃肿的XIB文件

故事板,由iOS5引进,很快的替代XIBs。尽管如此,XIBs在一下情况下依然是很有用的。如果你需要在IOS5之前版本的设备上运行或者你想自定义重用的视图,那么你确实不能避免使用它们。

如果你专注使用XIBs,那么让它们尽量的简单。尝试为一个试图控制器创建一个XIB,如果可能的话,把一个视图控制器的视图分层管理在单独的XIBs中。

注意当你加载一个XIB到内存的时候,它所有的内容都会载入内存,包括所有的图片。如果你有视图但不是要立即使用,那你就浪费了珍贵的内存。值得注意的是这不会发生在故事板中,因为故事版只会在需要的时候实例化一个视图控制器。

当你载入一个xib,所有的图像文件会被缓存,如果是开发OSX,那么音频文件也会被缓存。

Apple’s documentation 如是说:

当你载入一个包含了图和声音资源引用的nib文件时,nib加载代码读取实际的图片文件和音频文件到内存中并缓存它。在OS X中,图片和音频资源被存储在已命名的缓存 中这样你可以在之后需要的时候访问它们。在iOS中,只有图片资源被缓存,访问图片,你使用NSImage或者UIImage的imageNamed:方法来访问,具体使用取决于你 的平台。

显然这也发生在使用故事板的时候。尽管如此,我还不能找到这种说法的证据。如果你知道,请给我留言。

想学习更多关于故事板的更多内容吗?看看Matthijs Hollemans的 Beginning Storyboards in iOS 5 Part 1and Part 2.

5)不要阻塞主进程

你永远不应该在主线程中做任何繁重的工作。这是因为UIKIt的所有工作都在主线程中进行,比如绘画,管理触摸,和响应输出。

你的app的所有工作都在主线程上进行就会有阻塞主线程的风险,你的app会表现的反应迟钝。这是在App Store里获一星评论的快速途径!(作者卖萌..)

阻塞主线程*多的情况就是发生在你的app进行I/O操作,包括牵扯到任何需要读写外部资源的任务,比如读取磁盘或者网络

你可以异步的执行网络任务使用NSURLConnection中的这个方法:

查看源码

打印 ?

1 + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用第三方框架比如 AFNetworking.

如果你在做任何大开销的操作(比如执行一个耗时的计算,或者读写磁盘)使用Grand Central Dispatch(GCD)或者 NSOperations 和 NSOperationQueues.

使用GCD的模板如下代码所示:

查看源码

打印 ?

01 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
02  
03     // switch to a background thread and perform your expensive operation
04  
05   
06  
07     dispatch_async(dispatch_get_main_queue(), ^{
08  
09         // switch back to the main thread to update your UI
10  
11   
12  
13     });
14  
15 });

这里为什么dispatch_async 嵌套在*个的里面?这是因为任何UIKit相关的代码都必须在主线程上执行。

对NSOperation和GCD的详情感兴趣?看看Ray Wenderlich’s Multithreading and Grand Central Dispatch on iOS for Beginners 教程,和 Soheil Azarpour’s How To Use NSOperations and NSOperationQueues 教程。

6)调整图像视图中的图像尺寸

如果你用UIImageView呈现app束中的图片时,确认图片和UIImageView的尺寸相同。缩放图片会非常的耗时,特别是当你的UIImageView被嵌入UIScrollView。

如果图片是从远程服务器上下载的,有时你没法控制图片尺寸,或者你不能在服务器上在下载之前缩放它。在这些情况下你可以在图片下载完成后手动缩放一次,*好是在后台进程中。然在UIImageView中使用调整尺寸之后的图片。

7)选择正确集合

学着怎么在手头工作中使用*合适的类或对象是写出高效代码的基本。当时用集合是(collections),这个说法特别对。

可喜的是在苹果开发者文档( Collections Programming Topics)中有详细解释可用类之间的关系,还有解释各个类的适用情况。这个文档是每个使用集合的人的必读文档。

这是一个*常见的集合类型的快速简介:

  • Arrays:有序的值的列表,用index快速查找,通过值查找慢,insert/delete操作慢。
  • Dictionaries:存储键/值对.用index快速查找。
  • Sets: 无序的值列表。通过值快速查找,insert/delete快。

8)启用Gzip压缩

大量和持续增长的app依赖从远端服务器或者外部APIs获取的外部数据。某些时候你可能会开发一些需要下载XML,JSON,HTML或者其他文本格式的应用。

问题是移动设备不能保证网络环境,用户可能一分钟在边缘网络,下一分钟又是3G网络,无论什么情况下,你不想你的用户一直等待。

一个减少文件大小并加速下载的网络资源的方法是同时在你的服务器和客户端上使用GZIP压缩,对于文本数据这种有高比率压缩的数据来说非常有用。

好消息是iOS早已默认支持GZIP压缩,如果你是使用NSURLConnection或者建立在这之上的框架比如AFNetworking。更好的消息是一切云服务提供商像 Google App Engine早已发送压缩之后的响应数据。

这里有一篇文章great article about GZIP compression 介绍如何在你的Apache或IIS服务器上启用GZIP。

中级性能提升

好的,当谈到优化你的代码时,你应该很自信你已经初级的方法已经完全掌握了。但有时候有的问题的解决方法并不是那么显而易见,它由你app的结构和代码决定,尽管如此,在正确的上下文中,它们可能是没有价值的。

9)重用和延迟加载视图
越多的视图就有越多的绘图操作,*终意味着更多的CPU和内存开销。这说得特别对如果你的app嵌入很多视图在UIScrollView时。

管理这个的技巧是去模拟UITableView 和 UICollectionView的行为:不要一次创建所有的子视图,而是在需要的时候创建,然后把他们假如重用队列中。

这样,你只需要在视图浮动时配置你的视图,避免昂贵的资源分配开销。

视图创建的时机问题也同样适用于你app的其他地方。试想当你点击一个button时呈现一个视图的情景。至少有两种方法:

1.屏幕*次载入时创建视图并隐藏它。当你需要的时候,显示出来。

2.需要呈现的时候一次创建视图并显示它。

每种方法都有各自的优缺点

使用*种方法,你消耗了更多内存因为从创建开始到它释放前你都保持了它的内存,然而,当你点击button的时候,你的app会表现得响应快速因为它只需要更改视图的可视化属性。

使用第二种方法会有相反的效果,在需要的时候创建视图,消耗更少的内存,但当button被点击时应用会表现得不那么响应快速。

10)缓存,缓存,缓存

在开发应用时的一个伟大的经验是”Cache what matters”–也就是说那些不大会改变但会平凡被访问的东西。

你能缓存些什么呢?缓存的候选项有远程服务器的响应,图片,已计算过的值(比如UITableView的行高)。

NSURLConnection 根据处理的Http头缓存资源到磁盘或者内存中,你甚至可以手动创建一个NSURLRequest值加载缓存过的值。

这里有一段很棒的代码,用在任何时候你需要针对一个不大会改变的图片创建一个NSURLRequest。

查看源码

打印 ?

01 + (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
02  
03    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
04  
05  
06    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
07  
08     request.HTTPShouldHandleCookies = NO;
09  
10     request.HTTPShouldUsePipelining = YES;
11  
12     [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
13  
14     return request;
15  
16 }

如果想知道更多关于Http caching,NSURLCache,NSURLConnection等内容,请阅读the NSURLCache entry

注意,你可以通过NSURLConnection获取取一个URL请求,AFNetworking也可以。有了这个技巧这样你不用改变任何你的网络代码。

如果要缓存不牵扯到HTTP请求的其他东西,NSCache是很好的选择。

NSCache像NSDictionary,但是当系统需要回收内存的时候会自动的移除内容。

对HTTP Cache感兴趣并想学更多的内容?推荐阅读这篇文章best-practices document on HTTP caching

11)考虑绘图

在IOS中有很多方法可以制作拥有很棒外观的buttons,你可以是由全尺寸的图像,也可以使用调整尺寸之后的图像,或者你用CALayer,CoreGraphics,甚至OpenGL手动的它们。

当然,每种途径都有不同的复杂度级别和不同的性能,这篇文章非常值得一读post about iOS graphics performance here,这是Apple UIKit团队成员Andy Matuschak发表的文章,里面对各种方法有一些非常棒的见解和对性能的权衡。

使用预渲染图片更快,因为iOS不用创建一张图像和绘制图形到屏幕上(图像已经处理好了)。问题是你需要全部把这些图片放进应用束里,增加它的尺寸。那就是为什么使用可调整尺寸的图片是那么好:你通过移除”浪费了的“图片空间来节约空间。你也不需要为不同的元素生成不同的图片。(例如 buttons)

尽管如此,用图片你会失去代码调整你图片的能力,需要一次又一次的生成它们然后把它们加入到应用中。这是个缓慢的过程。另外一点如果你有动画或者很多张稍微变化的图片(例如 颜色叠加),你需要加很多的图片增加了应用束的大小。

总结一下,你需要想对你来说*重要的是什么:绘图性能还是app的大笑.通常两个都很重要,所以你会在一个工程里使用这两种方法。

12)处理内存警告

当系统内存低的时候iOS会通知所有的正在运行的app,关于低内存警告的处理苹果官方文档 official Apple documentation描述:

如果你的应用收到这个警告,它必须尽可能多的释放内存。*好的方法是移除对缓存,图像对象,和其他稍后要创建的对象的强引用。

幸运的是,UIKit提供了一些方法去接收低内存警告:

  • 实现App代理中的applicationDidReceiveMemoryWarning:方法。
  • 重载你自定义UIViewController子类中的didReceiveMemoryWarning方法。
  • 注册接收UIApplicationDidReceiveMemoryWarningNotification的通知

一旦收到这些警告,你的处理方法必须立刻响应并释放不必要的内存。

举例,如果视图当前不可见,UIViewController的默认行为是清除这些视图;子类可以通过清除额外的数据结构来补充父类的默认行为。一个应用程序维护一个图片的缓存,没有在屏幕上的图片都会被释放。

一旦收到内存警告,释放可能的全部内存是很重要的,否则你就有让你的app被系统杀死的的风险。

尽管如此,开始扑杀对象释放内存的时候要小心,因为你需要保证它们会在之后重新创建。当你开发app的时候,用你的模拟器上的模拟内存警告功能测试这种情况。

13)重用大开销对象

有的对象的初始化非常慢–NSDateFormatter 和 NSCalendar是两个例子,但是你不能避免使用它们,当你从 JSON/XML响应中解析日期时。

避免使用这些对象时的性能瓶颈,试着尽可能的重用这些对象。你可以加入你的类中成为一个属性,也可以创建为静态变量。

注意如果你选择了第二种方式,这个对象在app运行的时候会一直保持在内存里,像单例一样。

下面这段代码演示了NSDateFomatter作为一个属性的lazy加载,*次被调用然后创建它,之后就使用已创建在的实例

查看源码

打印 ?

01 // in your .h or inside a class extension
02  
03 @property (nonatomic, strong) NSDateFormatter *formatter;
04  
05   
06 // inside the implementation (.m)
07  
08 // When you need, just use self.formatter
09  
10 - (NSDateFormatter *)formatter {
11  
12     if (! _formatter) {
13  
14         _formatter = [[NSDateFormatter alloc] init];
15  
16         _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"// twitter date format
17  
18     }
19  
20     return _formatter;
21  
22 }

同样要记住设置一个NSDateFormatter的日期格式几乎跟创建一个新的一样慢。因此,如果在你的应用中你频繁需要处理多个日期格式,你的代码应该获利于初始化创建,重用,多个NSDateFormatter对象。

14) 使用精灵表

你是一个游戏开发者吗?精灵表是你的好朋友之一.精灵表让绘制比标准屏幕绘制方法更快速,消耗更少的内存。

这里有两个很棒的精灵表使用的教程

  1. How To Use Animations and Sprite Sheets in Cocos2D
  2. How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

第二个教程详细覆盖了像素格式,它可以对游戏性能有一个可衡量的影响。

如果对精灵表还不是很熟悉,一个很好的介绍 SpriteSheets – The Movie, Part 1and Part 2. 这些视频的作者是Andreas Löw,一个*流行的创建精灵表的工具Texture Packer的创建者。

除了使用精灵表之外,之前已经说到的内容也可以用在游戏上.举个例子,如果你的游戏有很多精灵,比如在标准的敌人或炮弹射击游戏,你可以重用精灵表额如是每次重新创建它们。

15)避免重复处理数据

很多app调用函数获取远程服务器上的数据.这些数据通常是通过JSON 或者 XML格式来传输。非常重要的是在请求和接收数据的时候努力在两端使用相同的数据结构。

理由?在内存中操纵数据以合适你的数据结构是非常昂贵的。

比如,如果你需要在表格视图中显示数据,*好请求和接收数据是数组的格式,以避免任何中间操纵数据,使其适合你在app中使用的数据结构

相似的,如果你的应用程序依赖于访问特定值的键,那么你可能会想要请求和接收一个键/值对的字典

通过*次就获取正确格式的数据,在自己的应用程序中你就会避免很多的重复处理工作,使数据符合你的选择的结构。

16)选择正确的数据格式

你可以有很多方法从web 服务中传递数据到你的app中

JSON 是一种通常比XML小且解析更快的格式,它的传输的内容也比较小。自iOS5起,内置的JSON解析很好用 built-in JSON deserialization

尽管如此,XML的一个优势当你使用SAXparsing方法时,你可以传输过程中读取它,在面的非常大的数据时,你不必像JSON一样在数据下载完之后才开始读取。

17)适当的设置背景图片

像iOS编码的其他工作一样,至少有两种不同方式去替换你视图的背景图片。

  1. 你可以设置你的视图的背景颜色为UIColor的colorWithPatternImage创建的颜色。
  2. 你可以添加一个UIImageView子试图给View

如果你有全尺寸的背景图片,你*对要用UIImageView,因为UIColor的colorWithPatternImage是重复的创建小的模式图片,在这种情况下用UIImageView方式会节约很多内存。

查看源码

打印 ?

1 // You could also achieve the same result in Interface Builder
2  
3  UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
4  
5 [self.view addSubview:backgroundView];

尽管如此,如果你计划用模式图片背景,你应该是用UIColor的colorWithPatternImage。它更快一些,而且这种情况不会使用很多内存。

查看源码

打印 ?

1 self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

18)减少你的网络占用

UIWebView 是非常游泳的.它非常容易用来显示web内容,甚至创建你app的视窗。这些都是标准UIKit 空间很难做到的。

尽管如此,你可能注意你可以用在你的app中的UIWebView组件并没有Apple的Safari app快。这是Webkit’s的Nitro引擎的限制使用。JIT compilation.

所以为了获得*佳的性能,你需要调整你的HTML。*件事是尽可能多的避免Javascript,包括避免大的框架比如jQuery。有时使用vanilla Javascript取代依赖的框架会快很多。

随时随地遵循异步加载Javascript文件的实践。特别当它们不直接影响到页面表现的时候,比如分析脚本。

*后,总是要意识到你在用的图片,保持图片的正确尺寸。正如这个教程前面所提到的,利用精灵表的优势来节约内存和提高速度。

想要获取更多的信息,看看WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS.

19) 设置阴影路径

你需要给视图或者layer添加一个阴影,你应该怎么做?

大多数开发者是添加 QuartzCore框架到工程中,然后写如下代码:

查看源码

打印 ?

01 #import <QuartzCore/QuartzCore.h>
02  
03  // Somewhere later ...
04  
05 UIView *view = [[UIView alloc] init];
06  
07  // Setup the shadow ...
08  
09 view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
10  
11 view.layer.shadowRadius = 5.0f;
12  
13 view.layer.shadowOpacity = 0.6;

看起来非常简单,是吧?

不好的是这个方法有一个问题。核心动画必须要先做一幕动画确定视图具体形状之后才渲染阴影,这是非常费事的操作。

这里有个替代方法让系统更好的渲染,设置阴影路径:

查看源码

打印 ?

1 view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

如果你想知道这个内容的更多技巧,Mark Pospesel 写过一篇post about shadowPath.

设置阴影路径,iOS不需要总是计算如何绘制阴影。而是用已经计算好的的路径。坏消息是它依赖与你的视图格式,你是视图可能很难计算这个路径。另一个问题是你需要在每次视图的框架改变时更新阴影路径。

20) 优化你的表格视图

表格视图需要快速的滚动,如果不能,用户能确切注意到很滞后。

为了让你的表格视图流畅的滚动,保证你实现了下列的建议。

  • 通过正确的reuseIdentifier重用cells
  • 尽量多的设置views 为不透明,包括cell本身。
  • 避免渐变,图像缩放,屏幕以外的绘制。
  • 如果行高不总是一样,缓存它们。
  • 如果cell显示的内容来自网络,确保异步和缓存。
  • 使用shadowPath来建立阴影。
  • 减少子视图的数目。
  • cellForRowAtIndexPath:中做尽量少的工作,如果需要做相同的工作,那么只做一次并缓存结果。
  • 使用适当的数据结构存储你要的信息,不同的结构有对于不同的操作有不同的代价。
  • 使用rowHeight,sectionFooterHeight,sectionHeaderHeight为常数,而不是询问代理。

21) 选择正确的数据存储方式

当要存储和读取大数据的时候你的选择是什么?

你有一些选项,包括:

  • 使用 NSUserDefaults存储它们。
  • 存储在结构化文件中,XML,JSON,Plist格式中。
  • 是用NSCoding打包?
  • 存储在本地数据库,如SQLite
  • 使用NSData

NSUserDefaults有什么问题呢?虽然说NSUserDefaults是好而且简单,它确实很好只有当你有很少的数据要存(像你的等级,或者音量是开还是关)。一旦你接触大数据,会有更好的其他选择。

保存在结构化文件中也可能有问题。一般的,在解析之前,你需要加载整个文件到内存中,这是非常耗时的操作。你可以使用SAX去处理XML文件,但是那是一个复杂的作法。同时你加载了全部的对象进内存,其中有你想要的也有不想要的。

那么NSCoding怎么样呢?不幸的是,它也同样要读写文件,跟上面说的方法有同样的问题。

你*好的解决方法是使用SQLite或者 Core Data. 通过这些技术,你可以执行特定的查询只加载需要的对象,避免强力搜索方法来检索数据。性能方面,SQLite和Core Data 非常接近。

SQLite 和 Core Data*大的不同就是它们的使用方法。Core Data呈现为一个对象图模型,但是SQLite是一个传统的DBMS(数据库管理系统).通常Apple建议你用Core Data,但是除非你有特殊的原因不让你你会想避开它,使用更低级的SQLite。

如果在你的app中使用SQLite,一个方便的库 FMDB 允许你使用SQLite而不用专研SQLite的C API。

高级性能技巧

寻找一些精英的方式去成为十足的代码忍者?这些高级性能技巧可以合适的时候使用让你的app运行得尽可能的高效。

22)加速启动时间

App的启动时间非常重要,特别是*次启动的时候。*影响意味着太多了!

*大的事情是保证你的App开始尽量的快,尽量的多的执行异步任务,不如网络请求,数据库访问,或者数据解析。

尽量避免臃肿的XIBs,因为你在主线程中加载。但是在故事板中不会有这个问题,所以尽量用它们。

Note: 监察人不会运行你的app在Xcode调试中, 所以确保测试启动性能时断开与Xcode的连接。

23)使用自动释放池

NSAutoreleasePool负责释放在代码块中的自动释放对象。通常,它是被UIKit自动调用的。但是也有一些场景我们需要手动创建NSAutoreleasePools。

举个例子,如果你创建太多的临时对象在你的代码中,你会注意到你的内存用量会增加直到对象被释放掉。问题是内存只有在UIKit排空(drains)自动释放池的时候才能被释放,这意味着内存被占用的时间超过了需要。

好消息是你可以在你的@autoreleasepool段中创建临时对象来避免上述情况。代码如下所示。

查看源码

打印 ?

01 NSArray *urls = <# An array of file URLs #>;
02 for (NSURL *url in urls) {
03  
04     @autoreleasepool {
05  
06         NSError *error;
07  
08         NSString *fileContents = [NSString stringWithContentsOfURL:url
09  
10                                          encoding:NSUTF8StringEncoding error:&error];
11  
12         /* Process the string, creating and autoreleasing more objects. */
13  
14     }
15  
16 }

在每次迭代之后会自动释放所有的对象。

 

你可以阅读更多关于NSAutoreleasePool的内容Apple’s official documentation.

24)缓存图像

这里有两种方法去加载app束中的Image,*个常见的方式是用imageNamed. 第二个是使用imageWithContentsOfFile

为什么会有两种方法,它们有效率吗?

imageNamed 在载入时有缓存的优势。文档 documentation for imageNamed是这样解释的:

这个方法看起来在系统缓存一个图像对象并指定名字,如果存在则返回对象,如果匹配图像的对象不在缓存中,这个方法会从指定的文件中加载数据,并缓存它,然后返回结果对象。

作为替代,imageWithContendsOfFile 简单的载入图像并不会缓存。

这两个方法的的演示片段如下:

查看源码

打印 ?

1 UIImage *img = [UIImage imageNamed:@"myImage"]; // caching
2  
3 // or
4 UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching

如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。什么时候使用哪一种呢?

然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。

25) 尽可能避免日期格式化器

如果你要用NSDateFormatter来解析日期数据,你就得小心对待了。之前提到过,尽量的重用NSDateFormatters总是一个好的想法。

然而,如果你需要更快的速度,你可以使用C代替NSDateFormatter来解析日期。 Sam Soffes写了一篇 blog post about this topic来说明如何用代码来解析 ISO-8601日期串。尽管如此,你可以很容易的修改他的代码例子来适应你的特殊需求。

噢,听起来很棒,但是你相信有更好的办法吗?

如果你能控制你所处理日期的格式,尽可能的选择使用 Unix timestamps。Unix时间戳是简单的整数代表从某个起始时间点开始到现在的秒数。这个起始点通常是1970年1月1日 UTC 00:00:00。

你可以容易的把时间戳转换为NSDate,如下面所示:

查看源码

打印 ?

1 - (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
2  
3   return [NSDate dateWithTimeIntervalSince1970:timestamp];
4  
5 }

这甚至比C函数更快

注意,很多WEB APIs返回时间戳是毫秒,因为这对于javascript*终来使用和处理数据是非常常见的。只要记住将这个时间戳除以1000再传递给dateFromUnixTimestamp方法即可。

iOS定位操作,获取当前位置,计算两点之间距离

摘要:  通过CoreLocation定位,获取到用户当前位置,跟地图中的定位不同,并计算两点之间的距离

 

一、导入CoreLocation.framework

二、#import <CoreLocation/CoreLocation.h>

三、声明代理 <CLLocationManagerDelegate>

四、代码实现

1、声明

01 CLLocationManager *locationManager;//定义Manager
02 // 判断定位操作是否被允许
03 if([CLLocationManager locationServicesEnabled]) {
04     CLLocationManager *locationManager = [[[CLLocationManager alloc] init] autorelease];
05
06      self.locationManager.delegate = self;
07 }else {
08      //提示用户无法进行定位操作
09 }
10
11 // 开始定位
12 [locationManager startUpdatingLocation];

2、更新位置后代理方法,iOS6.0一下的方法

01 - (void)locationManager:(CLLocationManager *)manager
02     didUpdateToLocation:(CLLocation *)newLocation
03            fromLocation:(CLLocation *)oldLocation {
04
05     //latitude和lontitude均为NSString型变量
06         //纬度
07     self.latitude = [NSString  stringWithFormat:@"%.4f", newLocation.coordinate.latitude];
08
09         //经度
10     self.longitude = [NSString stringWithFormat:@"%.4f",                           newLocation.coordinate.longitude];
11     
12 }

3、iOS6.0以上苹果的推荐方法

01 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
02 {
03     //此处locations存储了持续更新的位置坐标值,取*后一个值为*新位置,如果不想让其持续更新位置,则在此方法中获取到一个值之后让locationManager stopUpdatingLocation
04     CLLocation *currentLocation = [locations lastObject];
05     
06     CLLocationCoordinate2D coor = currentLocation.coordinate;
07     self.latitude =  coor.latitude;
08     self.longitude = coor.longitude;
09    
10     //[self.locationManager stopUpdatingLocation];
11     
12 }

4、更新失败的方法

1 - (void)locationManager:(CLLocationManager *)manager
2        didFailWithError:(NSError *)error {
3     
4   if (error.code == kCLErrorDenied) {
5       // 提示用户出错原因,可按住Option键点击 KCLErrorDenied的查看更多出错信息,可打印error.code值查找原因所在
6   }
7 }

五、根据两点坐标计算两点之间的距离,此方法为苹果自带方法,亲测速度比高德API速度快很多,但是数据与高德API得到的不一样,准确度本人未能证实

1 //*个坐标
2 CLLocation *current=[[CLLocation alloc] initWithLatitude:32.178722 longitude:119.508619];
3 //第二个坐标
4 CLLocation *before=[[CLLocation alloc] initWithLatitude:32.206340 longitude:119.425600];
5 // 计算距离
6 CLLocationDistance meters=[current distanceFromLocation:before];

iOS图片拉伸的3种常见技巧

纵观移动市场,一款移动app,要想长期在移动市场立足,*起码要包含以下几个要素:实用的功能、*强的用户体验、华丽简洁的外观。华丽外观的背后,少不了美工的辛苦设计,但如果开发人员不懂得怎么合理展示这些设计好的图片,将会糟蹋了这些设计,功亏一篑。

比如下面张图片,本来是设计来做按钮背景的:

%title插图%num尺寸为:24×60

现在我们把它用作为按钮背景,按钮尺寸是150×50:

// 得到view的尺寸
CGSize viewSize = self.view.bounds.size;

// 初始化按钮
UIButton *button = [[UIButton alloc] init];
// 设置尺寸
button.bounds = CGRectMake(0, 0, 150, 50);
// 设置位置
button.center = CGPointMake(viewSize.width * 0.5f, viewSize.height * 0.5f);

// 加载图片
UIImage *image = [UIImage imageNamed:@”button”];
// 设置背景图片
[button setBackgroundImage:image forState:UIControlStateNormal];

// 添加按钮
[self.view addSubview:button];
运行效果图:

%title插图%num

可以看到,效果非常地差。原因很简单,因为原图大小为24×60,现在整张图片被全方位拉伸为150×50,比较严重的是图片的4个角。

有些人可能马上想到一个解决方案,你叫美工把图片做大一点不就好了么,怎么拉伸都没事。没错,这是一种解决方案,不过不建议采取。原因很简单:1.图片大,导致安装包也大,加载到内存中也大;2.有更好的解决方案。

细看一下图片,其实图片会变得难看,完全是因为4个角被拉伸了,中间的拉伸并没有明显地丑化外观。因此要想小图片被拉伸后不会变得难看,在图片拉伸的时候,我们只需拉伸图片的中间一块矩形区域即可,不要拉伸边缘部分。

比如只拉伸下图的矩形区域,上下左右的边缘都不拉伸:

%title插图%num

iOS中提供很好用的API帮我们实现上述功能。到iOS 6.0为止,iOS提供了3种图片拉伸的解决方案,接下来分别详细介绍这些方案。

一、iOS 5.0之前

iOS中有个叫端盖(end cap)的概念,用来指定图片中的哪一部分不用拉伸。比如下图中,黑色代表需要被拉伸的矩形区域,上下左右不需要被拉伸的边缘就称为端盖。

%title插图%num

使用UIImage的这个方法,可以通过设置端盖宽度返回一个经过拉伸处理的UIImage对象

– (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;
这个方法只有2个参数,leftCapWidth代表左端盖宽度,topCapHeight代表顶端盖高度。系统会自动计算出右端盖宽度(rightCapWidth)和底端盖高度(bottomCapHeight),算法如下:

// width为图片宽度
rightCapWidth = width – leftCapWidth – 1;

// height为图片高度
bottomCapHeight = height – topCapHeight – 1
经过计算,你会发现中间的可拉伸区域只有1×1

// stretchWidth为中间可拉伸区域的宽度
stretchWidth = width – leftCapWidth – rightCapWidth = 1;

// stretchHeight为中间可拉伸区域的高度
stretchHeight = height – topCapHeight – bottomCapHeight = 1;
因此,使用这个方法只会拉伸图片中间1×1的区域,并不会影响到边缘和角落。

下面演示下方法的使用:

// 左端盖宽度
NSInteger leftCapWidth = image.size.width * 0.5f;
// 顶端盖高度
NSInteger topCapHeight = image.size.height * 0.5f;
// 重新赋值
image = [image stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
调用这个方法后,原来的image并不会发生改变,会产生一个新的经过拉伸的UIImage,所以第6行中需要将返回值赋值回给image变量

运行效果:

%title插图%num

可以发现,图片非常美观地显示出来了

注意:

1.这个方法在iOS 5.0出来后就过期了

2.这个方法只能拉伸1×1的区域

 

二、iOS 5.0

在iOS 5.0中,UIImage又有一个新方法可以处理图片的拉伸问题

– (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets
这个方法只接收一个UIEdgeInsets类型的参数,可以通过设置UIEdgeInsets的left、right、top、bottom来分别指定左端盖宽度、右端盖宽度、顶端盖高度、底端盖高度

CGFloat top = 25; // 顶端盖高度
CGFloat bottom = 25 ; // 底端盖高度
CGFloat left = 10; // 左端盖宽度
CGFloat right = 10; // 右端盖宽度
UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
// 伸缩后重新赋值
image = [image resizableImageWithCapInsets:insets];
运行效果:

%title插图%num

三、iOS 6.0

在iOS6.0中,UIImage又提供了一个方法处理图片拉伸

– (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode
对比iOS5.0中的方法,只多了一个UIImageResizingMode参数,用来指定拉伸的模式:

UIImageResizingModeStretch:拉伸模式,通过拉伸UIEdgeInsets指定的矩形区域来填充图片
UIImageResizingModeTile:平铺模式,通过重复显示UIEdgeInsets指定的矩形区域来填充图片
CGFloat top = 25; // 顶端盖高度
CGFloat bottom = 25 ; // 底端盖高度
CGFloat left = 10; // 左端盖宽度
CGFloat right = 10; // 右端盖宽度
UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
// 指定为拉伸模式,伸缩后重新赋值
image = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
运行效果:

%title插图%num

《iPhone 3D 编程》第二章:数学与抽象

第二章:数学与抽象

 

计算机图形领域比计算机其它领域对数学的要求都高,如果你想成为一个合格的OpenGL程序员,那么你得撑握线性代数,并能抽象一些内容。

 

在本章,我将解释这些抽象内容与回忆线性代数的内容。其中OpenGL涉及到的概念都会得到讲解,于是在HelloArrow示例代码中的神密面纱将一层一层解开。

 

在本章结束时,我们会运用这些数学知识将HelloArrow这个示例转化为到3D空间中去,完成一个叫”HelloCone”的示例。

 

集装线的抽象概念

你们可以 把包括OpenGL ES在内的任何图形API都看作是集装线的工程流程,将各种原始材料如纹理,顶点做为输入,*终组装成五彩缤纷的图形。

 

这些传入OpenGL的数据是我们学习OpenGL首先要学的内容,在本章我们重点学习顶点。在图2.1中,用抽象的视角展示了顶点逐渐演变成像素的过程。首先是一系列的顶点变换,接着这些顶点被组装成图元,*后将这些图元光栅化到像素。

 

图2.1 OpenGL集装线

%title插图%num

注意

OpenGL ES 1.1与2.0都能抽象出集装线的概念,但ES 2.0更为明显。图2.1中,*左边的小精灵接手处理vertex shader,*右边的小精灵处理完后交给fragment shader。

 

在本章我们重点介绍集装流程中的变换,但是首先我们概述一下装配图元的流程,因为它很容易理解。

装配顶点为图元

在三维空间中,一个物体的形将可以用几何图形表示。在OpenGL中,这些几何图形是由基础图元,这些基础图元包括三角形,点,线。其础元是由一些顶点通过不同的拓扑规则构建起来的。在OpenGLES中,共有7种拓扑规则,参看图2.2“图形拓扑规则”。

图2.2 “图形拓扑规则”

%title插图%num

 

 

在*章Hello Arrow的代码中,有这样一行代码利用OpenGL绘制一个三角形到缓冲区:

glDrawArrays(GL_TRIANGLES, 0, vertexCount);

 

*个参数指明OpenGL绘制的时候拓扑规则为:GL_TRIANGLES,采用这种规则后OpenGL组装基础图形的时候,首先取顶点缓冲区中的前三个顶点出来组成*个三角形,接着到后面三个顶点组成第二个三角形,以此类推。

 

大多数情况下,同于顶点都挨着的,所以在顶点组数中会有重复出现的。为了解决这个问题,GL_TRIANGLE_STRIP规则出来了。这样一来,就可以用更小的顶点数组绘制出数量相同的三角形,看表2.1会明了许多,其中v表示顶点数,p表示图元数。这样说吧,如果绘制三个三解形,用GL_TRIANGLES规则,我们需要9个顶点数据(3*p),如果用GL_TRIANGLE_STRIP规则,我们则只需要5个顶点数据(p+2)。

 

表2.1 图元相关计数

拓扑规则

图元数

顶点数

GL_POINTS v p
GL_LINES v/2 2p
GL_LINE_LOOP v p
GL_LINE_STRIP v-1 p+1
GL_TRIANGLES v/3 3p
GL_TRIANGLE_STRIP v-2 p+2
GL_TRIANGLE_FAN v-1 p+1

 

GL_RTINGLE_FAN这个规则得说一下, 花多边形,圆或锥体的时候这个规则很好用。*个顶点表示顶点,其它的表示底点。很多种情况下都是用GL_TRINGLE_STRIP,但是在用FAN的时候如果用成了STRIP,那么这个三角形将退化(0区域三角形)。

 

图2.3 两个三角形组成的四方形

%title插图%num

        图2.3中用两个三角形绘制了一个方形。(顺便说一下,OpenGL有一种规则GL_QUADS是用来直接绘制方形的,但是OpenGL ES不支持这种规则。)下面的代码分别用三种拓扑规则绘制同一个方形三次。

 

const int stride = 2 * sizeof(float);

 

float triangles[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangles);

glDrawArrays(GL_TRIANGLES, 0, sizeof(triangles) / stride);

 

float triangleStrip[][2] = { {0, 1}, {0, 0}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangleStrip);

glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(triangleStrip) / stride);

 

float triangleFan[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangleFan);

glDrawArrays(GL_TRIANGLE_FAN, 0, sizeof(triangleFan) / stride);

 

在OpenGL ES中图元并不是只有三角形,GL_POINTS可以用来绘制点。点的大小是可以自定义的, 如果太大看起来就像方形。这样一来,就可以将小的位图与这样的点关联起来,构成所谓的点精灵。在第七章,精灵与字符中会讲到。

 

OpenGL中关于线的图元拓扑规则有三个,分别是:separatelines, strips与loops。在strips与loops规则中,每一条件的结束点是下一条线的顶点,而loops更特别,*条线的开始点是*后一条件的结始点。如果你绘制图2.3中方形的边框,下面的代码分别用三种规则实现了。

const int stride = 2 * sizeof(float);

 

float lines[][2] = { {0, 0}, {0, 1},

{0, 1}, {1, 1},

{1, 1}, {1, 0},

{1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, lines);

glDrawArrays(GL_LINES, 0, sizeof(lines) / stride);

 

float lineStrip[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, lineStrip);

glDrawArrays(GL_LINE_STRIP, 0, sizeof(lineStrip) / stride);

 

float lineLoop[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, lineLoop);

glDrawArrays(GL_LINE_LOOP, 0, sizeof(lineLoop) / stride);

 

涉及顶点的属性

 

现在来看看OpenGL中集装线的输入数据。在OpenGL的世界里,每一个顶点至少得有一个属性,其中位置是*为重要的。表2.2罗列了OpenGL ES 1.1的顶点属性。

 

表2.2 OpenGL ES中的顶点属性

Attribute

OpenGL Enumerant

OpenGL Function Call

Dimensionality

Types

Position GL_VERTEX_ARRAY

 

glVertexPointer

 

2, 3, 4

 

byte, short, fixed, float

 

Normal GL_NORMAL_ARRAY

 

glNormalPointer

 

3

 

byte, short, fixed, float

 

Color GL_COLOR_ARRAY

 

glColorPointer

 

4 ubyte, fixed, float

 

Point Size GL_POINT_SIZE_ARRAY_OES

 

glPointSizePointerOES

 

1 fixed, float

 

Texture Coordinate GL_TEXTURE_COORD_ARRAY

 

glTexCoordPointer

 

2,3,4 byte, short, fixed, float

 

Generic Attribute(ES 2.0) N/A

 

glVertexAttribPointer

 

1,2,3,4 byte, ubyte, short, ushort, fixed, float

 

 

OpenGL ES 2.0只有*后一行,它需要你自定义属性。回忆一下HelloArrow中不同rendering engines开启属性的方法:

 

//ES 1.1

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_COLOR_ARRAY);

 

//ES 2.0

glEnableVertexAttribArray(positionSlot);

glEnableVertexAttribArray(colorSlot);

 

在ES 1.1中,是用内置常量来开启顶点属性的,而ES 2.0中则是用从shader中导出的常量来开始(positionSlot与colorSlot)。接着向OpenGL指明要开启顶点属性的类型与维度:

 

 

    // OpenGL ES 1.1

glVertexPointer(2, GL_FLOAT, … );

glColorPointer(4, GL_FLOAT, … );

 

// OpenGL ES 2.0

glVertexAttribPointer(positionSlot, 2, GL_FLOAT, …);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT, …);

 

顶点数据的数据类型可能是表2.3中的一个。如果是ES 2.0可以使用其中任意一个,而ES 1.1则有限制, 具体要看是什么属性(参看表2.2*右列)。

 

表2.3 顶点属性数据类型

OpenGL Type

OpenGL Enumerant

Typedef Of

Length in Bits

GLbyte GL_BYTE signed char 8
GLubyte GL_UNSIGNED_BYTE unsigned char 8
GLshort GL_SHORT short 16
GLushort GL_UNSIGNED_SHORT unsigned short 16
GLfixed GL_FIXED int 32
GLfloat GL_FLOAT float 32

 

OpenGL ES 1.1中,位置属性有点特殊,因为它是必须的顶点属性。它可以是二维,三维或是四维,但是在OpenGL内部总是把它们转化为四维浮点型进行处理。

 

        四维空间?这可与那些物理学家所说的不一样, 它并不涉及时间与物理,只是一种方法,它可以将所以变换都用矩阵相乘的方式表达。这里的四维坐标就是我们所谓的齐次坐标。当把三维坐标转化为齐次坐标的时候,往往第四个元素是为1(通宵用w表示),为0的情况表示点无限远, 这种情况非常少。(在OpenGL中在设置灯光位置的时候w=0,第四章中会看到。),为负的情况是没有实际意义的。

 

 

齐次坐标

        齐次坐标是在Möbius于1827年8月发表Der barycentrische Calcul中诞生的。随便说说Möbius发明的barycentrische坐标系,它用于iPhone图形芯片中计算三角形插值颜色。这个术语源于古老的词汇“barycentre”,表示中心的意思。如果你将一个三角的三个角放上不同的权重,那么你就可以通过barycentric坐标系计算平衡点。关于它的推导不在本书讨论的范围,如果你有兴趣可以自行研究。

 

再次回到OpenGL集装线流程,其中所有的点都变为4维,那么它们可能变成2维的点吗?明确的告诉你,会的!特别是苹果发布了触摸屏的iPhone。我们将在下一节介绍顶点是如何变化为2维点,现在我们首先关心如何拆除第四个变量w的,方程式如下:

方程式 2.1 透视变换

 

这种除以w的计算就叫着透视变换。z也进行同样的处理,紧接着的深度与真实性,你会看到更深入分析。

 

顶点的生命周期

图2.4, “顶点前期流程。上一排是概念,下一排是OpenGL的视图”与 图2.5,“光珊化前顶点的*后三个流程”描绘了顶点从三维变到二维的过程。在OpenGL的集装线中,它们叫着变换与灯光,或用T&L表示。我们将在第四章,深度与真实性中介绍灯光,现在重点是介绍变换。

 

每一次变换,顶点就有新的位置。*原传入的顶点是位于对象空间,即叫着对象坐标系。在对象空间中,原点就是对象的中心点,有时候我们把对象空间也叫着模型空间。

 

通过模型-视图矩阵,对象坐标就被转化为眼坐标空间。在眼坐标空间中,原点是照像机的位置。

 

接着,通过投影矩阵顶点变转化到裁剪空间。由于OpenGL将位于视图平截面外的顶点会切除掉,所以形像的叫着裁剪空间。在这儿就是w表演的时候了,如果x或y的值大于+w或小于-w,这些点将会被裁剪掉。

 

图2.4 顶点的先期流程。上一排是概念,下一排是OpenGL的视图

 %title插图%num

 

在ES 1.1中,图2.4中的流程是固定的,每一个顶点都必须经过这些流程。在ES2.0中,这取决于你,在进入裁剪空间前,你可以进行任何的变换。但常常你也是进行与此相同的变换而已。

 

裁剪过后,就进入到透视变换了。我们会把坐标标准化到[-1, +1],术语叫着设备坐标标准化。图2.5描述了其变换过程。与图2.4不同的是,这些流程在ES1.1与ES2.0中都是一样的。

 

图2.5光珊化前顶点的*后三个流程

 %title插图%num

光珊化前前*后一步是视口变换,它需要一些该应中当中设定的值。你可以还认得在GLViw.mm中有这样一行代码:

glViewport(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));

 

glViewport的四个参数分别是:left,bottom,with,height。对于iPhone,往往让width与height的值为320与480,但是为了兼容以后的苹果设备(与其它平台)与避免硬编码,我们需要在运行时获取正确的长度与高度,正如我们在HelloArrow中所写的代码。

 

glViewport控制x与y变换到窗口空间(也可叫着移动设备,非全屏的情况少之又少)。控制z变换到窗口空间是由另一个方法实现的:

glDepthRangef(near, far);

 

实际开发中,这个方法很少用, 它的默认值是near为0,far为1,我们用它的默认值即可。

 

现在,你明白顶点位置变化的基本流程了吧,但是我们还没有介绍颜色。当灯点禁止(默认禁止)的时候,颜色是不经过变换而直接传递。当开启的时候,颜色就与变换有密切关系。我们将在第四章,深度与真实性介绍。

 

摄影抽象

线装线的抽象让我们明白了OpenGL的后台工作原理,但是对于理解一个3D应用的工作流程,摄影抽象更加有用。当我太太精心准备了印度晚餐,他就会要求我拍摄一些相片用于它的私人博客。我常常会做下面的流程来完成太太的要求:

1.    放置各种餐盘。

2.    放置灯光。

3.    放置相机。

4.    将相机对准食物。

5.    设整焦距。

6.    控快门拍相。

 

It turns out that each of these actions haveanalogues in OpenGL, although they typically occur in a different order.Setting aside the issue of lighting (which we’ll address in a future chapter),an OpenGL program performs the following actions:

 

你可能已发现,每一步都与OpenGL中有相类之处,尽管有的顺序不同。先把灯光部份放一边(这部份内容在后面章节),OpenGL的的步骤如下:

1.    调整相机的视角, 投影矩阵起作用。

2.    放置相机位置并设置朝向,视图矩阵起作用

3.    对于*个对象

a.    缩放,旋转,移动,模型矩阵起作用。

b.    渲染对象。

 

模型矩阵与视图矩阵的合体叫着模型-视图矩阵。在OpenGLES 1.1中,所有的顶点都先经过模型-视图矩阵作用,然后再由投影矩阵作用。而OpenGL ES 2.0中, 你可以任意变换, 但是常常也是按照模形-视图/投影的过程变换,至少得差不多。

在后面我们会详细介绍三种变换,现在来学一些预备识知。无论如何用,OpenGL有一个通用的方法来处理所有的变换。在ES1.1中,当前变换可以用矩阵来表达,如下:

     float projection[16] = { … };

float modelview[16] = { … };

 

glMatrixMode(GL_PROJECTION);

glLoadMatrixf(projection);

 

glMatrixMode(GL_MODELVIEW);

glLoadMatrixf(modelview);

 

在ES2.0中,并没有模形-视图矩阵,也没有glMatrixMode与glLoadMatrixf这样的方法。取而代之的是shaders中的uniform变量。在后面我们会学到,uniforms是一种shader中用到的类型,我们可以简单的把它理解为shader不能改变的常量。加载方式如下:

    float projection[16] = { … };

float modelview[16] = { … };

 

GLint projectionUniform = glGetUniformLocation(program, “Projection”);

glUniformMatrix4fv(projectionUniform, 1, 0, projection);

 

GLint modelviewUniform = glGetUniformLocation(program, “Modelview”);

glUniformMatrix4fv(modelviewUniform, 1, 0, modelview);

 

现在是不是想知道为何OpenGL中的好多方法都由f或fv结尾。许多方法(如glUniform*)可以是浮点-指针参数的方法,可以是整形参数的方法,可是以其它类型参数的方法。OpenGL是C型的API,而C又不支持方法重载,所以每个方法得用不同的名字加以区分。表2.4 “OpenGL ES 方法后缀”,是方法的后缀的解释。随便说一下,v表示是一个指针型参数。

表2.4 OpenGL ES方法后缀

后缀

类型

i

32位整形

x

16位定点

f

32位浮点

ub

8位无符号byte

ui

32位无符号整形

 

ES 1.1提供了另外一个方法,可以使矩阵相乘,而ES2.0中没有这种方法。下面的代码首先加载了一个单位矩阵,然后再与其它两个矩阵相乘。

    float view[16] = { … };

float model[16] = { … };

 

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

glMultMatrixf(view);

glMultMatrixf(model);

 

模型-视图与投影矩阵默认是单位矩阵。单位矩阵用于恒等变换,即变换不起作用。见方式程2.2  恒等变换。

 

方程式 2.2 恒等变换

 

注意

关于矩阵与向量,矩阵与矩阵相乘,请参看附录A,C++向量库

 

本书中一律用行向量进行计算。方程式2.2中,左边的(vx vy vz 1)与右边的(vx*1 vy*1 vz*1 1) 都是四维向量。该方式程可以用列向量表示为:

 

很多情况下,将4维的行向量想像成1*4的矩阵,或把4维的列向量想像成4*1的矩阵会更容理解。(n*m表示矩阵的维数,其中n表示有多少行,m表示有多少列。)

图2.6 “矩阵相乘”展示了两个矩阵相乘的条件:中间两个维数一定要相等。外面的两个数字决定矩阵相乘的结果维数。利用这条规则,我们来验证方程式2.2中的合法性。*号右边的四维行向量(等价于1*4的矩阵)与右边的4*4的矩阵相乘的结果应是1*4的矩阵(同样的适用于四维列向量)。

 

图2.6矩阵相乘

 %title插图%num

从编码的角度来说,我发现行向量比列向理更理想,因行向量更像c语言中的数组。当然,只发你愿 意,你也可以用列向量,但是如果用列向量的话,你的变换顺序将要颠倒顺序才行。由于矩阵相乘不具有交换性,所以顺序很重要。

例如ES 1.1的代码:

      glLoadIdentity();

glMultMatrix(A);

glMultMatrix(B);

glMultMatrix(C);

glDrawArrays(…);

 

如果用行向量的方式,你可以把每次变换看成当前变换的pre-multiplied。那么上面的代码等效于:

%title插图%num

如果用列向量的方式,每次变换是post-multiplied。代码等效于:

%title插图%num

无论你用的是行向量还是列向量的方式,我们只需要记住一点,就是代码中*后的变换是*先作用于顶点变换的。为了更明确,那么将上面的列向量变换方式,用加括号的方式显示的展示变换的作用顺度。

%title插图%num

 

由于OpenGL的反向作用的特性,便用行向量会使其展现得更明显,这也是我为何喜欢用它的另一个原因。

关于数学方面的就介绍到此,现在回到摄影抽象,看看它是如何对应到OpenGL中来的。OpenGL ES 1.1提供了方法来生成矩阵,并在其当前矩阵中乘以新的变化矩阵一步完成新的变化。在后面小节中会介绍每一个方法。而ES 2.0没有这些方法,但是我会说明它的背后原理,让你自己实现这方法。

回忆一下OpenGL中用到的三个矩阵

1.   调整视角与视野,由投影矩阵作用。

2.   设置相机位置与朝向,由视图矩阵作用。

3.   缩放,旋转,移动每个对象,由模形矩阵作用。

我们将逐一介绍这三种变换,并完成一个*简单的变换流程。

设置模型矩阵

 

将一个对象放于场景中通常需要经过缩放,旋转,移动处理。

缩放

内置API是glScalef

    float scale[16] = { sx, 0,  0,  0,

0,  sy, 0,  0,

0,  0,  sz, 0

0,  0,  0,  1 };

 

// The following two statements are equivalent.下面两种方法等效

glMultMatrixf(scale);

glScalef(sx, sy, sz);

 

缩放矩阵与数学理论见方程式2.3

方程式2.3 缩放变换

%title插图%num

 

图2.7展示了 sx = sy = 0.5时的缩放变换

图2.7缩放变换

 %title插图%num

警告

当缩放因子x,y,z三个都不相等的情况,我们称之为非均匀缩放。这种方式的缩放是被完全允许的,但是大多数情况下会影响效率。因为一旦有非均匀缩放,OpenGL就会进行大量的灯光计算。

 

移动

 

glTranslatef可以轻松实现移动,将对象移动因定长度:

    float translation[16] = { 1,  0,  0,  0,

0,  1,  0,  0,

0,  0,  1,  0,

tx, ty, tz, 1 };

 

// The following two statements are equivalent.下面两种方法等效

glMultMatrixf(translation);

glTranslatef(tx, ty, tz);

 

简单的说,移动就是用加法实现的,要记住在齐次坐标中,我们可以用矩阵相乘的方式表达所有的变换,参看方程式2.4

 

 

方程式2.4 移动变换

 %title插图%num

 

图2.8描绘不当tx = 0.25and ty = 0.5时的移动变换

 

图2.8移动变换

 %title插图%num

旋转

 

还记得HelloArrow示例中,固定渲染通道(ES 1.1)下的移动吗?

 

glRotatef(m_currentAngle, 0, 0, 1);

 

这样就会绕着z轴逆时针旋转m_currentAngle度。*个参数表示旋转角度,后面三个参数表示旋转轴。在ES2.0的实现中,旋转就有点复杂,因为它是手工计算矩阵的:

 

    #include <cmath>

float radians = m_currentAngle * Pi / 180.0f;

float s = std::sin(radians);

float c = std::cos(radians);

float zRotation[16] = { c, s, 0, 0,

-s, c, 0, 0,

0, 0, 1, 0,

0, 0, 0, 1 };

 

GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, “Modelview”);

glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);

 

图2.9 描绘了旋转45度的变换

 

 

图2.9 旋转变换

 %title插图%num

        绕着z轴旋转是非常简单的,但是如果绕着任意轴旋转就需要一复杂的矩阵。对于ES1.1, glRotatef可以帮我们自动生成矩阵,所以不必过多关心相关的概念。对于ES2.0,参看附录A, C++向量库,窥探其实现。

 

glRotatef只能通过其原点旋转,如果你想绕任意点旋转,你可以通过下面三步实现:

1.    移动-p。

2.    旋转。

3.    移动+p。

如果想改HelloArrow在(0, 1)点绕z轴旋转,那么可以如下修改:

 

     glTranslatef(0, +1, 0);

glRotatef(m_currentAngle, 0, 0, 1);

glTranslatef(0, -1, 0);

glDrawArrays(…);

 

记住,代码中*后的变换,在实现作用的时候是*先起效的!

 

设置视图变换

 

设置视图矩阵*简单的方法就是用LookAt方法,它并不是OpenGL ES的内置函数,但是可以自已快速实现。它有三个参数:相机位置,目标位置,一个”up”向量表示相机朝向(见图2.10 “LookAt 变换”)。

图2.10 LookAt 变换

 %title插图%num

        通过三个向量的传入,LookAt就可以生成一个变换矩阵,否则就得用基本的变换(缩放,移动,旋转)来生成。示例2.1 是LookAt的实现。

 

示例2.1 LookAt

mat4 LookAt(const vec3& eye, const vec3& target, const vec3& up)

{

vec3 z = (eye – target).Normalized();

vec3 x = up.Cross(z).Normalized();

vec3 y = z.Cross(x).Normalized();

 

mat4 m;

m.x = vec4(x, 0);

m.y = vec4(y, 0);

m.z = vec4(z, 0);

m.w = vec4(0, 0, 0, 1);

 

vec4 eyePrime = m * -eye;

m = m.Transposed();

m.w = eyePrime;

 

return m;

}

 

注意,示例2.1中用了自定义类型,如vec3,vec4,mat4。关非伪代码,而是用到了附录A,C++向量库中的代码。本章后面内容会详细介绍这个库。

 

设置投影变换

 

到此为止,我们已能修改模型-视图的变换。对于ES1.1我们可以用glRotatef与glTranslatef来影响当前矩阵,也可以用glMatrixMode在任意时刻来修改矩阵。初始化选中的是GL_MODELVIEW模式。

 

到底设影矩阵与模形-视图矩阵的区别是什么?对于OpenGL开发新手,会把投影想像为”camera matrix”,这种想法即使不算错,也是过于简单了,因为相机的位置与朝向是由模型-视图矩阵标识的。我更喜欢把投影想像成相机的“聚焦”,因为它可以控制视野。

 

警告

相机的位置与朝向是由模型-视图矩阵决定的,并非投影矩阵决定。在OpenGL ES 1.1中灯光计算的时候会用到这些数据。

 

在计算机图形学中有两种类型的投影方式:透视投影与正交投影。采用透视投影,物体越远越小,这样更接具真实性。图2.11“投影类型” 中可以看到它们的区别。

 

图2.11 投影类型

 %title插图%num

        正交投影往往用于2D绘制,所以在Hello Arrow中用了它:

 

const float maxX = 2;

const float maxY = 3;

glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1);

 

glOrthof的六个参数表示六面体每一面到原点的矩离,分别是:前,后,左,右,上,上。示例中参数的比例是2:3,这是因为iPhone的屏是320*480。 而ES 2.0 生成正交投影矩阵的方法是:

 

float a = 1.0f / maxX;

float b = 1.0f / maxY;

float ortho[16] = {

a, 0,  0, 0,

0, b,  0, 0,

0, 0, -1, 0,

0, 0,  0, 1

};

 

当正交投影的中心点位于原点的时候, 生成的投影矩阵类似于缩放矩阵,关于缩放矩阵,前面已介绍过。

 

sx = 1.0f / maxX

sy = 1.0f / maxY

sz = -1

 

float scale[16] = { sx, 0,  0,  0,

0,  sy, 0,  0,

0,  0,  sz, 0

0,  0,  0,  1 };

 

由于Hello Cone(本章示例,后面将看到)是绘制的3D图形,于是我们用glFrustumf来设置一个投影矩阵,这样写:

 

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glFrustumf的参数与glOrthof的一样。由于glFrustum在ES 2.0中不存在, 所以Hello Cone的ES2.0的实现就得自己计算矩阵,方法如下:

 

void ApplyFrustum(float left, float right, float bottom,

float top, float near, float far)

{

float a = 2 * near / (right – left);

float b = 2 * near / (top – bottom);

float c = (right + left) / (right – left);

float d = (top + bottom) / (top – bottom);

float e = – (far + near) / (far – near);

float f = -2 * far * near / (far – near);

 

mat4 m;

m.x.x = a; m.x.y = 0; m.x.z = 0; m.x.w = 0;

m.y.x = 0; m.y.y = b; m.y.z = 0; m.y.w = 0;

m.z.x = c; m.z.y = d; m.z.z = e; m.z.w = -1;

m.w.x = 0; m.w.y = 0; m.w.z = f; m.w.w = 1;

 

glUniformMatrix4fv(projectionUniform, 1, 0, m.Pointer());

}

 

一旦设置了设影矩阵, 就设定了视野。视锥表示眼在金字塔顶部的一个锥体(参看图2.12 视锥)

 

2.12 视锥

 %title插图%num

 

基于金字塔的顶点(称为视野)的角度,可以计算一个视锥。开发者认为这样比指定六面更加直观。示例2.2中方法有四个参数:视角,金字塔宽高比,远与近裁剪面。

 

示例 2.2 VerticalFieldOfView

void VerticalFieldOfView(float degrees, float aspectRatio,

float near, float far)

{

float top = near * std::tan(degrees * Pi / 360.0f);

float bottom = -top;

float left = bottom * aspectRatio;

float right = top * aspectRatio;

 

glFrustum(left, right, bottom, top, near, far);

}

告诫

设置投影的时候,应避免把远近裁剪面设为0或负数。数学上不支持这种工作方式。

 

用矩阵栈存取变换

还记得在用ES1.1实现HelloArrow的时候用glPushMatrix与glPopMatrix来存取变换的状态吗?

 

void RenderingEngine::Render()

{

glPushMatrix();

glDrawArrays(GL_TRIANGLES, 0, vertexCount);

glPopMatrix();

}

 

用Push/Pop这样的方式来实现Render是非常普遍日,因为这样的好外是可以阻止帧与帧这间变换的累积。

 

上面的示例,栈没有超过两层,iPhone允许嵌套6层栈。这样使复杂变化变得简单,比如渲染图2.13 “机器人手臂”这种有关节的对象,或者是会层次的模型。在用push/pop写代码的时候,*好有相应的缩进,如示例2.3“分层变换”

 

示例2.3 分层变换

void DrawRobotArm()

{

glPushMatrix();

glRotatef(shoulderAngle, 0, 0, 1);

glDrawArrays( … ); // upper arm

glTranslatef(upperArmLength, 0, 0);

glRotatef(elbowAngle, 0, 0, 1);

glDrawArrays( … ); // forearm

glTranslatef(forearmLength, 0, 0);

glPushMatrix();

glRotatef(finger0Angle, 0, 0, 1);

glDrawArrays( … ); // finger 0

glPopMatrix();

glPushMatrix();

glRotatef(-finger1Angle, 0, 0, 1);

glDrawArrays( … ); // finger 1

glPopMatrix();

glPopMatrix();

}

 

图2.13 机器人手臂

 %title插图%num

        每一个矩阵模式都有自己的栈,如图2.14“矩阵栈”,用得*多的是GL_MODELView。对于GL_TEXTURE模式的栈,我们会在另一章节中介绍。先前说过,OpenGL中的每一个项点位置变换都由当前的模型-视图矩阵与投影矩阵决定,也就是说在它们各自的栈中,它们位于栈顶。用glMatrixMode实现从一个栈模式到另一个模式。

 

 

图2.14 矩阵栈

%title插图%num

        在ES 2.0中不存在矩阵栈,如果你需要,你可以在你自已应用中加代码实现,也可用自己的数学库。这样是不是觉得ES2.0更难呀? 但你得记住ES 2.0 是一种”closerto te metal”的API, 利用shader它可以让你更自由更充分的操控图形。

 

动画

到现在,我们已看到了OpenGL执行背后的数学支持。由于OpenGL是一个低级别的图形API,并不是动画API。幸运的是,对于动画所需的数学非常简单。

 

用五个字来总结它:animationis all about interpolation(动画与插值相关)。一个应用程序的动画系统往往需要艺术家,用户或算法设定一些关键帧。然后在运行的时候,计算这些关键帧间的值。被当做关帧的数据可以是任意类型,常规是颜色,位置,角度。

 

插值技术

 

计算两关键帧中间帧的过程叫着补间。如果你将流逝时间除以动画时间,你就可以得到一个[0,1]的权值。如图2.15中所描绘 “缓动方式:线性,二次缓进,二次缓进-缓出”, 我们会讨论三种缓动方程式。对于补间值t,可以用如下方式计算插值:

 

float LinearTween(float t, float start, float end)

{

return t * start + (1 – t) * end;

}

 

某些类型的动画,不能用线性补间的方式实现,用Robert Penner的缓动方程可以让动画更加直实。该缓进的计算是相当简单:

 

float QuadraticEaseIn(float t, float start, float end)

{

return LinearTween(t * t, start, end);

}

 

Penner的 “二次缓进-缓出”方式有点复杂,但是把它分拆分开就变得简单了,见示例2.4。

 

示例2.4 二次缓进-缓出

 

float QuadraticEaseInOut(float t, float start, float end)

{

float middle = (start + end) / 2;

t = 2 * t;

if (t <= 1)

return LinearTween(t * t, start, middle);

t -= 1;

return LinearTween(t * t, middle, end);

}

 

图2.15缓动方式:线性,二次缓进,二次缓进-缓出

 %title插图%num

旋转动画与四元数

 

对于位置与颜色的关键帧,它们很容易插值:对于xyz或rgb分量,分别调用上面的的补间方法求补间值。角度也应一样处理,求角度的补间值而已。但是对于旋转轴不同的情况,如何计算这两个朝向的补间值呢?

 

在图2.3中,这个例子是在一个平面上(泽注:只能绕Z轴旋转),如果你的需要是每个节点是一个球(泽注:可以360度旋转)。那么每一个节点只存旋转角度是不够的,还要存旋转轴。我们将它标记为轴-角度,于是对于每一个节点需要4个浮点值。

 

原来有一个更简单的方法来表示一个任意旋转,与轴-角度的一样需要4个分量,这种方法更适合又插值。这个方法就是用四维向量组成的四元数,它于1843年被设想出来的。在现在矢量代数中,四元数的点被忽视,但经历计算机图形的发展,它得于复兴。 Ken Shoemake 是20世纪80年代末著名slerp方程的推广之一,而slerp方程可以计算两个四元数补间值。

 

 

知道

Shoemake的方程只是众多四元数插值的方法中的一种,但是它是*出名的,并在我们的向量库中所采用。其它的方法,如normalized quaternion lerp, log-quaternion lerp, 有时候在性能方面更理想。

 

说得差不多了,但你得明确,四元数并不是处理动画的*好的方式。有些时候,只需要简单的计算两个向量的夹角,找出一个旋转轴,并计算角度的插值即可。但是四元数解决了稍微复杂的问题,它不再是两个向时间的插值,而变成两个朝向间的插值。这样看起来更加迂腐,但是它有很重要的区别的。将你的手臂伸直向前,掌心向上。弯曲你的胳膊并旋转你的手,这样你就模仿了两个四元数的插值。

 

在我们的示例代码中用到了许多“轨迹球”旋转,用四元数来完成再合适不过了。在此我不会涉及大量枯燥的方程式,你可以到附录A,C++向量库去看四元数的实现。在HelloCone示例中与下一章中的wireframe view示例中,将会用到这个向量库。

 

用C++优化向量

在Hello Arrow中的顶点数据结构是:

struct Vertex {

float Position[2];

float Color[4];

};

 

如果我们继续沿用C数组的方式贯穿全书,你将会发现生活是多么的糟糕! 我们真正想要的应是这样:

 

struct Vertex {

vec2 Position;

vec4 Color;

};

这正是C++运算符重载与类模版强大的地方。运用C++可以让你写一个简单的库(其实,很简单)并使你应用的代码像是基于向量的一种语言开发。其实本书中的示例就是这样的。我们的库只包括了三个头文件,没有一个cpp文件:

Vector.hpp

定义了一套三维,三维,四维向量,可以是浮点也可以是整型。并没有依附任何头文件。

Matrix.hpp

定义了2×2, 3×3, 与 4×4矩阵类,只依附了Vector.hpp。

Quaternion.hpp

定义了四元数的类,并提供了构造与插值的方法,依附Matrix.hpp。

 

在附录A,C++向量库中包括了这些文件,但是还是向你展示一下本库是如何构成的,示例2.5是Vector.hpp的一部份。

示例 2.5 Vector.hpp

#pragma once

#include <cmath>

 

template <typename T>

struct Vector2 {

Vector2() {}

Vector2(T x, T y) : x(x), y(y) {}

T x;

T y;

};

 

template <typename T>

struct Vector3 {

Vector3() {}

Vector3(T x, T y, T z) : x(x), y(y), z(z) {}

void Normalize()

{

float length = std::sqrt(x * x + y * y + z * z);

x /= length;

y /= length;

z /= length;

}

Vector3 Normalized() const

{

Vector3 v = *this;

v.Normalize();

return v;

}

Vector3 Cross(const Vector3& v) const

{

return Vector3(y * v.z – z * v.y,

z * v.x – x * v.z,

x * v.y – y * v.x);

}

T Dot(const Vector3& v) const

{

return x * v.x + y * v.y + z * v.z;

}

Vector3 operator-() const

{

return Vector3(-x, -y, -z);

}

bool operator==(const Vector3& v) const

{

return x == v.x && y == v.y && z == v.z;

}

T x;

T y;

T z;

};

 

template <typename T>

struct Vector4 {

};

 

typedef Vector2<int> ivec2;

typedef Vector3<int> ivec3;

typedef Vector4<int> ivec4;

 

typedef Vector2<float> vec2;

typedef Vector3<float> vec3;

typedef Vector4<float> vec4;

 

我们把向量类型用C++模版的方式参数化了,这样一来就可以用相同代码成生基于浮点与定义的向量了。

虽然2维向量与3维向量有许多共同点,但是我们还是不能共用一套模版。我不能过过将维数参数化的模版来实现,如下面代码:

template <typename T, int Dimension>

struct Vector {

T components[Dimension];

};

 

当设计一个向量库的时候,在通用性与可读性上一定要有一个适当的平衡点。由于在向量类中逻辑相对较少,并很少需要遍历向量成员,分开定义类看起来是一个不错的选择。比如Position.y就比Position[1]更容易让读者理解。

 

由于向量这些类型会被常常用到,所以在示例2.5的底部用typedefs定义了一些缩写的类型。这些小写的名字如vec2,ivec4虽然打破了我们建立的命名规则,但是看起来的感觉就更接近语言本身的原生类型。

 

在我们的向量库中,vec2/ivec2这样的命名是借鉴GLSL中的关键字的。注意区分本书中C++部分与shader部分内容,不要混淆了。

 

提示

在GLSL着色语言中,vec2与mat4这些类型是语言内置类型。我们的C++向量库是模仿着它写的。

 

ES1.1实现Hello Cone

现在我们开始修改HelloArrow为Hello Cone。我们要改的不只是把内容从2D变为3D,我们还要支持两个新的朝向,当设备朝上或朝下。

 

本章示例与上一章的视觉上的变化很大,主要是修改RenderingEngine2.cpp与RenderingEngine2.cpp。由于前面章节中有了良好的接口设计,现在是它发挥作用的时候了。首先来处理ES 1.1 renderer, 即RenderingEngine1.cpp。

 

RenderingEngine 声明

 

表2.5 “HelloArrow与Hello Cone的不同之处” 指出了HelloArrow 与Hello Cone实现的几项差异。

 

表2.5 Hello Arrow与Hello Cone的不同之处

Hello Arrow

Hello Cone

绕着z轴旋转 四元数旋转
一个绘制方法 两个绘制方法,一个绘底,一个绘锥
C数组方式表示向量 用vec3的对像表示向量
三角形的数据小,由代码硬编码 运行时生成三角形的数据
三角形的数据存于C数级中 三角形的数据存于STL 向量中

 

 

我决定在本书示例中运用C++ STL(标准模版库)。运用它可以简化许多工作量,如它提供了可扩展的数组(std::vector)与双向链表(std::list)。许多的开发者都反对在移动设备如iPhone上写有时实性要求的代码时用STL开发。乱用STL的确会使你应用的内存无法控制,但如今,C++编译器对STL代码做了许多译化。同时我们得注意iPhone SDK也提供了一套Objective-C类(如,NSDictionary),这些类类似于STL的一些类,它们的内存占用率与性能都差不多。

 

它们的区别做到了心中有数 如表2.5, 再来看看RenderingEngine1.cpp的项部, 见示例2.6(注意 在这儿定义了新的顶点数据结构,因此你可以移除旧版本的数据结构)。

 

注意

如果你想边看边写代码,那么请在Finder中复制一份HelloArrow的工程目录,并改名为HelloCone。然后用Xcode打开,并在Project菜单中选择Rename,将工程名改为HelloCone。接着把附录A,C++向量库中的Vector.app,Matrix.hpp与Quaternion.hpp添加到工程。RenderingEngine1.cpp是区别*大的地方,打开它删掉里面所有内容,并修改为你将要看到的内容。

 

示例 2.6 RenderingEngine1 类定义

#include <OpenGLES/ES1/gl.h>

#include <OpenGLES/ES1/glext.h>

#include “IRenderingEngine.hpp”

#include “Quaternion.hpp”

#include <vector>

 

static const float AnimationDuration = 0.25f;

 

using namespace std;

 

struct Vertex {

vec3 Position;

vec4 Color;

};

 

struct Animation {    //[1]

Quaternion Start;

Quaternion End;

Quaternion Current;

float Elapsed;

float Duration;

};

 

class RenderingEngine1 : public IRenderingEngine {

public:

RenderingEngine1();

void Initialize(int width, int height);

void Render() const;

void UpdateAnimation(float timeStep);

void OnRotate(DeviceOrientation newOrientation);

private:

vector<Vertex> m_cone;     //[2]

vector<Vertex> m_disk;     //[3]

Animation m_animation;

GLuint m_framebuffer;

GLuint m_colorRenderbuffer;

GLuint m_depthRenderbuffer;

};

 

1.     动画结构,用于生成平滑的三维动画。包括三个表示方向的四元数:开始,当前插值,结束;还有两个时间跨度:经过的与持继时间,都是以秒为单位。它们是用来计算[0,1]的。

2.     三角形数据用两个STL容器保存,分别是m_cone与m_disk。向量容器是正确的选择,因为我们知道它有多大,它还能保证空间是连继的。储存顶点的空间必须是连继的,这是OpenGL所要求的。

3.     与Hello Arrow的不同外,这儿需要两个renderbuffers。Hello Arrow是二维的,所以只需要一个颜色renderbuffer。Hello Cone需要一个存深度信息的renderbuffer。在后面的章节会学习深度缓冲,在此只需要简单理角为:它是一个特殊的平面图像,用来存放每一个像素z值的结构。

 

OpenGL 初始化与锥体的镶嵌

 

在Hello Arrow中构造方法非常简单:

IRenderingEngine* CreateRenderer1()

{

return new RenderingEngine1();

}

 

RenderingEngine1::RenderingEngine1()

{

// Create & bind the color buffer so that the caller can allocate its space.

glGenRenderbuffersOES(1, &m_renderbuffer);

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffer);

}

 

示例2.7中的Initialize方法,生成了顶点数据并创建了framebuffer。开始处定义了一些锥体的半径,高度与几何精细度。这儿几何精细度是指在垂直方向上锥体的分片数量。生成顶点数据后,初始化了OpenGL的framebuffer与相关变换矩阵。还开启了深度测试,因为这是一个真3D应用,在第四章会介绍更多的深度测试知识。

 

示例2.7 RenderingEngine 中的Initialize

void RenderingEngine1::Initialize(int width, int height)

{

const float coneRadius = 0.5f;     //[1]

const float coneHeight = 1.866f;

const int coneSlices = 40;

 

{

// Generate vertices for the disk.

}

 

{

// Generate vertices for the body of the cone.

}

 

// Create the depth buffer.

glGenRenderbuffersOES(1, &m_depthRenderbuffer);   //[2]

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_depthRenderbuffer);

glRenderbufferStorageOES(GL_RENDERBUFFER_OES,

GL_DEPTH_COMPONENT16_OES,

width,

height);

 

// Create the framebuffer object; attach the depth and color buffers.

glGenFramebuffersOES(1, &m_framebuffer);     //[3]

glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer);

glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,

GL_COLOR_ATTACHMENT0_OES,

GL_RENDERBUFFER_OES,

m_colorRenderbuffer);

glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,

GL_DEPTH_ATTACHMENT_OES,

GL_RENDERBUFFER_OES,

m_depthRenderbuffer);

 

// Bind the color buffer for rendering.

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);  //[4]

 

glViewport(0, 0, width, height);  //[5]

glEnable(GL_DEPTH_TEST);   //[6]

 

glMatrixMode(GL_PROJECTION);  //[7]

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glMatrixMode(GL_MODELVIEW);

glTranslatef(0, 0, -7);

}

        示例2.7是处理OpenGL的一个标准流程,在以后的内容中你会慢慢明白这一切。现在,简要说明一下:

1.     定义一些常量,用来生成顶点锥底与锥面的顶点数据。

2.     为深度缓冲生成一个id,绑定它,并为之分配存储空间。在在后面的深度缓冲中详细介绍。

3.     为缓冲对象生成id,绑定之,并把深度与颜色缓冲用glFramebufferRenderbufferOES依附于它。

4.     绑定颜色缓冲,后继的绘制将作用于它。

5.     设置viewport的左下角,长,宽属性。

6.     为3D场景开启深度测试

7.     设置投影与模型-视图矩阵

 

示例2.7中,两处生成顶点的地方都用省略号表示,是由于这两个地方值得深入分析。将对象拆分为三角形术语叫三角化,但常常也叫关镶嵌,它关系到多边形填充表面的边界问题。任何一个M.CEscher迷都知道,镶嵌是一个有趣的难题; 后面章节也会有介绍。

 

如图2.16 “HelloCone的镶嵌”,我们将锥面用triangle strip表示,锥底用trianglefan表示。

 

图2.16 Hello Cone的镶嵌

 %title插图%num

 

无论用strip还是fan模式,我们都可以成生锥面,但是fan模式的时候看起来会很奇怪。因为fan模式下,它的中心颜色是不正确的。就算我们为其中心指定一个颜色,在垂直方向上的将有不正确的放射效果,如图2.17 “左:triangle fan模式的锥体,右:triangle strip模式的锥体”

 

图2.17左:trianglefan模式的锥体,右:triangle strip模式的锥体

%title插图%num

        用strip的模式并不是生成锥面的*好方法,因为思维的时候三角形有退化过程(如图2.16中。 译注:上面的顶点不断退化为一个点的时候,就成锥体了)。用GL_TRINGLES的模式可以解决这个问题,但是需要两倍空间的顶点数组。由于OpenGL提供了一种基于索引的机制来解决这个顶点数组重复的问题,所以可以解决空间变大的问题,以后面的章节会介绍。现在我们还是用GL_TRIANGLE_STRIP来实现。生成锥体顶点的代码见示例2.8,生成过程原理见图2.18(将代码放在RenderingEngine::Initialize中//Generatevertices for the body of the cone的后面)。每一个切片需要两个顶点(一个顶点,一个底边弧上的点),还需要附加的切片来结束循环(图2.18)。于是总共的顶点数是(n+1)*2,其中n表示切片数。计算底边弧上点,采用绘制圆的经典算法即可, 如果你还记得三角函数,那对此一定觉得面熟的。

 

图2.18 Hello Cone顶点序列

 %title插图%num

示例 2.8 成生锥顶点

m_cone.resize((coneSlices + 1) * 2);

 

// Initialize the vertices of the triangle strip.

vector<Vertex>::iterator vertex = m_cone.begin();

const float dtheta = TwoPi / coneSlices;

for (float theta = 0; vertex != m_cone.end(); theta += dtheta) {

 

// Grayscale gradient

float brightness = abs(sin(theta));

vec4 color(brightness, brightness, brightness, 1);

 

// Apex vertex

vertex->Position = vec3(0, 1, 0);

vertex->Color = color;

vertex++;

 

// Rim vertex

vertex->Position.x = coneRadius * cos(theta);

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = coneRadius * sin(theta);

vertex->Color = color;

vertex++;

}

 

在此我们用一种简单的方法创建了一个灰度渐变效果,这样可以模拟灯光:

 

float brightness = abs(sin(theta));

vec4 color(brightness, brightness, brightness, 1);

 

在这儿这个方法生成的颜色是固定的,在改变对象方向的时候是不会改变的,虽然有点遗憾,但是足以满足我们的当前需要。这种技术的术语是baked lighting,在第九章优化中会更会详细的介绍。关于更真实的灯光,在第四章中介绍。

 

示例2.9是生成锥底顶点的代码(将这代码放在RenderingEngine1::Initizlize中的//Generate vertices for the disk后面)。由于它用了trianglefan模式,所以总共的顶点数为:n+2, 多于的两个顶点,一个是中心点,一个是循环结束点。

 

示例2.9 生成锥底顶点

// Allocate space for the disk vertices.

m_disk.resize(coneSlices + 2);

 

// Initialize the center vertex of the triangle fan.

vector<Vertex>::iterator vertex = m_disk.begin();

vertex->Color = vec4(0.75, 0.75, 0.75, 1);

vertex->Position.x = 0;

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = 0;

vertex++;

 

// Initialize the rim vertices of the triangle fan.

const float dtheta = TwoPi / coneSlices;

for (float theta = 0; vertex != m_disk.end(); theta += dtheta) {

vertex->Color = vec4(0.75, 0.75, 0.75, 1);

vertex->Position.x = coneRadius * cos(theta);

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = coneRadius * sin(theta);

vertex++;

}

 

3D中平滑旋转

 

为了让动画平滑,在UpdateAnimation中用四元数旋转的时候,引入了Slerp(泽注:插值相关)。当设备朝向发生变化的时候,OnRotate方法就开始新的动画序列。具体参看示例2.10,“UpdateAnimation()与OnRotate()”。

 

示例2.10 UpdateAnimation()与OnRotate()

void RenderingEngine1::UpdateAnimation(float timeStep)

{

if (m_animation.Current == m_animation.End)

return;

 

m_animation.Elapsed += timeStep;

if (m_animation.Elapsed >= AnimationDuration) {

m_animation.Current = m_animation.End;

} else {

float mu = m_animation.Elapsed / AnimationDuration;

m_animation.Current = m_animation.Start.Slerp(mu, m_animation.End);

}

}

 

void RenderingEngine1::OnRotate(DeviceOrientation orientation)

{

vec3 direction;

 

switch (orientation) {

case DeviceOrientationUnknown:

case DeviceOrientationPortrait:

direction = vec3(0, 1, 0);

break;

 

case DeviceOrientationPortraitUpsideDown:

direction = vec3(0, -1, 0);

break;

 

case DeviceOrientationFaceDown:

direction = vec3(0, 0, -1);

break;

 

case DeviceOrientationFaceUp:

direction = vec3(0, 0, 1);

break;

 

case DeviceOrientationLandscapeLeft:

direction = vec3(+1, 0, 0);

break;

 

case DeviceOrientationLandscapeRight:

direction = vec3(-1, 0, 0);

break;

}

 

m_animation.Elapsed = 0;

m_animation.Start = m_animation.Current = m_animation.End;

m_animation.End = Quaternion::CreateFromVectors(vec3(0, 1, 0), direction);

}

Render 方法

 

*后非常重要的是HelloCone的Render这个方法。它与Hello Arrow的方法类似,只不过它调用了两上绘制的方法,而且在glClear加入了深度缓冲的标志。

 

示例 2.11RenderingEngine1::Render()

void RenderingEngine1::Render() const

{

glClearColor(0.5f, 0.5f, 0.5f, 1);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

 

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_COLOR_ARRAY);

 

mat4 rotation(m_animation.Current.ToMatrix());

glMultMatrixf(rotation.Pointer());

 

// Draw the cone.

glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &m_cone[0].Position.x);

glColorPointer(4, GL_FLOAT, sizeof(Vertex),  &m_cone[0].Color.x);

glDrawArrays(GL_TRIANGLE_STRIP, 0, m_cone.size());

 

// Draw the disk that caps off the base of the cone.

glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &m_disk[0].Position.x);

glColorPointer(4, GL_FLOAT, sizeof(Vertex), &m_disk[0].Color.x);

glDrawArrays(GL_TRIANGLE_FAN, 0, m_disk.size());

 

glDisableClientState(GL_VERTEX_ARRAY);

glDisableClientState(GL_COLOR_ARRAY);

 

glPopMatrix();

}

 

注意到rotation.Pointer()这个调用没?在我们的C++向量库中,向量与矩阵都有一个方法Pointer(),用于返回指向指一个元素的指针。 这样将更加方便传递参数到OpenGL。

 

注意

如果我们将用隐式转换的操作代替Pointer(),那么我们不可能使我们的OpenGL代码更加简洁,同样很容易出错,因为编译器具体做了什么,我们也不知道。出于类似的原因,STL中的string才提供c_str()这样的方法返回char*。

 

由于现在我们只实现了ES1.1的相关部份,所以在GLView.mm中得开启ForceES1。 这样你就可以编译运行你的*个真3D应用程序。为了看到新加入的两个朝向功能, 你可以将你iPhone放在头顶看,或放在腰间低头看。图2.19 “从左到右依次为:竖屏,上下颠倒,面向上,面向下,home按键在右的横屏,home按键在左的横屏”。

 

图2.19从左到右依次为:竖屏,上下颠倒,面向上,面向下,home按键在右的横屏,home按键在左的横屏

 %title插图%num

Shader实现的Hello Cone

对于RenderingEngine2.cpp的变化,我们不是将Hello Arrow中的复制过来做一些修改,而是将RenderingEngine1.cpp的内容复制过来,并运用ES2.0的技术来修改,这样会更有学习意义。只需要修改两处, 由于HelloArrow中RenderingEngine2.cpp中的BuildShader与BuildProgram方法仍然需要,于是将它们先保存起来,再修改engine1到engine2。示例2.12 “RenderingEnngine2类声明”是RenderingEngine2.cpp的代码。新加入或是修改的部份用粗体标识。由于一些不需要修改的部分是用…表示的,所以你不能直接复制下面的代码(只需要按粗体进行修改)。

 

示例2.12 RenderingEnngine2类声明

#include <OpenGLES/ES2/gl.h>

#include <OpenGLES/ES2/glext.h>

#include “IRenderingEngine.hpp”

#include “Quaternion.hpp”

#include <vector>

#include <iostream>

 

#define STRINGIFY(A)  #A

#include “../Shaders/Simple.vert”

#include “../Shaders/Simple.frag”

 

static const float AnimationDuration = 0.25f;

 

 

class RenderingEngine2 : public IRenderingEngine {

public:

RenderingEngine2();

void Initialize(int width, int height);

void Render() const;

void UpdateAnimation(float timeStep);

void OnRotate(DeviceOrientation newOrientation);

private:

GLuint BuildShader(const char* source, GLenum shaderType) const;

GLuint BuildProgram(const char* vShader, const char* fShader) const;

vector<Vertex> m_cone;

vector<Vertex> m_disk;

Animation m_animation;

GLuint m_simpleProgram;

GLuint m_framebuffer;

GLuint m_colorRenderbuffer;

GLuint m_depthRenderbuffer;

};

Initialize方法如下,但对于ES2.0不适用。

glMatrixMode(GL_PROJECTION);

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glMatrixMode(GL_MODELVIEW);

glTranslatef(0, 0, -7);

 

把它们改为:

 

m_simpleProgram = BuildProgram(SimpleVertexShader,

SimpleFragmentShader);

glUseProgram(m_simpleProgram);

 

// Set the projection matrix.

GLint projectionUniform = glGetUniformLocation(m_simpleProgram,

“Projection”);

mat4 projectionMatrix = mat4::Frustum(-1.6f, 1.6, -2.4, 2.4, 5, 10);

glUniformMatrix4fv(projectionUniform, 1, 0,

projectionMatrix.Pointer());

 

BuildShader与BuildProgram两个方法与Hello Arrow中的一样,于是在这儿不提供出来了。两个shader也一样,由于这儿是bakedlighting,所以只需要简单的传入颜色值即可。

 

在Render方法中设置模型-视图矩阵,参看示例2.13“RenderingEngine2::Render()”。记住,glUniformMatrix4fv与ES 1.1中的glLoadMatrix扮演的角色是一样的。

 

示例 2.13RenderingEngine2::Render()

void RenderingEngine2::Render() const

{

GLuint positionSlot = glGetAttribLocation(m_simpleProgram,

“Position”);

GLuint colorSlot = glGetAttribLocation(m_simpleProgram,

“SourceColor”);

 

glClearColor(0.5f, 0.5f, 0.5f, 1);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

glEnableVertexAttribArray(positionSlot);

glEnableVertexAttribArray(colorSlot);

 

mat4 rotation(m_animation.Current.ToMatrix());

mat4 translation = mat4::Translate(0, 0, -7);

 

// Set the model-view matrix.

GLint modelviewUniform = glGetUniformLocation(m_simpleProgram,

“Modelview”);

mat4 modelviewMatrix = rotation * translation;

glUniformMatrix4fv(modelviewUniform, 1, 0, modelviewMatrix.Pointer());

 

// Draw the cone.

{

GLsizei stride = sizeof(Vertex);

const GLvoid* pCoords = &m_cone[0].Position.x;

const GLvoid* pColors = &m_cone[0].Color.x;

glVertexAttribPointer(positionSlot, 3, GL_FLOAT,

GL_FALSE, stride, pCoords);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT,

GL_FALSE, stride, pColors);

glDrawArrays(GL_TRIANGLE_STRIP, 0, m_cone.size());

}

 

// Draw the disk that caps off the base of the cone.

{

GLsizei stride = sizeof(Vertex);

const GLvoid* pCoords = &m_disk[0].Position.x;

const GLvoid* pColors = &m_disk[0].Color.x;

glVertexAttribPointer(positionSlot, 3, GL_FLOAT,

GL_FALSE, stride, pCoords);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT,

GL_FALSE, stride, pColors);

glDrawArrays(GL_TRIANGLE_FAN, 0, m_disk.size());

}

 

glDisableVertexAttribArray(positionSlot);

glDisableVertexAttribArray(colorSlot);

}

 

示例2.13与示例2.11流程都差不多,只有细节不同。

 

接着,我们将该文件中所有RenderingEngine1修改为RenderingEngine2,包括工厂方法(修改为CreateRenderer2)。同样要去掉所有的_OES与OES。关闭GLView.mm中的ForceES1,这样基于shader 的Hello Cone就修改完成了。这样ES2.0的支持完成了,并没有添加任何酷的shader效果,让我们学到了两种不同API的区别。

 

结束语

本章是本书术语*多的一章,我们学习了一些基础图形学概念,交澄清了*章示例代码中掩盖的技术细节。

 

变换部份可能是*验理解的,也是OpenGL新手*攻克*关键的部份。我希望你能用Hello Cone来做实验,以便你更好的了解其工作原理。比如,硬编码旋转与移动,并观察顺序对渲染结果的影响。

在下一章你会学到用OpenGL绘制更复杂的图形,并初步涉及到iPhone触摸屏相关知识。

ios高效开源类库

MBProgressHUD(进展指示符库)
地址:https://github.com/jdg/MBProgressHUD
苹果的应用程序一般都会用一种优雅的,半透明的进度显示效果,不过这个API是不公开的,因此你要是用了,很可能被清除出AppStore。而 MBProgressHUD提供了一个替代方案,而且在用户角度上,实现的效果根本看不出和官方程序有什么差别。同时还提供了其他附加功能,比如虚拟进展 指示符,以及完成提示信息。整合到项目里也很容易,这里不细谈了。
ASIHttpRequest(HTTP Network库)
地址:http://allseeing-i.com/ASIHTTPRequest/
iPhone当然也有自己的HTTP Network API,那为什么要用ASIHttpRequest呢?因为官方的API简直跟话痨似的,太罗嗦了!ASIHttpRequest库*大的简化了网络通 信,提供更先进的工具,什么文件上传工具,重定向处理工具、验证工具、等等。只要你手头的东西跟HTTP有关,用这个*对能让你感觉道生活有美好!先看一 段代码就体会到了。
    (void) loadAppDevMag
    {
      NSURL *url = [NSURL URLWithString:@”http://www.appdevmag.com”];
      ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
      [request setDelegate:self];
      [request startAsynchronous];
    }
    – (void)requestFinished:(ASIHTTPRequest *)request
    {
      // Use when fetching text data
      NSString *responseString = [request responseString];
    }
JSON Framework(JSON支持)
地址:http://stig.github.com/json-framework/
如果你做的应用和网站服务器有交互,那就得用到JSON了。但事实上,iOS平台的原生类库根本就不支持JSON,这就略犀利了吧?不过JSON框 架满足了你的所有需求,包括一个解析器将JSON字符串解析成对象;以及一个生成器从对象生成字符串。这个库根本就是太流行了,JSON提过很多次了,具 体特点就不多讲了,所谓“一段代码胜千言”,下面用一段代码演示一下吧。
    // JSON string -> NSDictionary
    NSString *jsonString = @”{\”foo\”: \”bar\”}”;
    NSDictionary *dictionary = [jsonString JSONValue];
    NSLog(@”Dictionary value for \”foo\” is \”%@\””, [dictionary objectForKey:@”foo”]);
    // NSDictionary -> JSON string
    NSString *newJsonString = [dictionary JSONRepresentation];
Flurry(详尽的使用统计)
%title插图%num
地址:http://www.flurry.com/product/analytics/index.html
通过Furry你可以得到应用的用户人数,用户活跃度,用户来源等统计信息。但是他*厉害的地方是,你可以追踪应用本身的事件和错误记录,所有这些 数据都会在一个类似Google Analytics的界面上显示,这样就很容易掌握用户的行为和出现的问题。当然,这个星球上很多统计工具,但是这款是作者个人比较推崇的解决方案。
RegexKitLite(正则表达式支持)
地址:http://regexkit.sourceforge.net/RegexKitLite/
正则表达式大家都知道。但是iPhone SDK居然当他不存在?这怎么能忍啊!果断用RegexKitLite。虽然叫的是Lite,但是功能很full。示例代码。
    // finds phone number in format nnn-nnn-nnnn
    NSString *regEx = @”[0-9]{3}-[0-9]{3}-[0-9]{4}”;
    for(NSString *match in [textView.text componentsMatchedByRegex:regEx]) {
        NSLog(@”Phone number is %@”, match);
    }
Facebook iOS SDK(Facebook API类库)
%title插图%num
地址:https://github.com/facebook/facebook-ios-sdk
大体来讲就是iPhone上的Facebook login,完全支持Facebook Graph API和the older REST api。如果你的应用跟Facebook有关,相信我,用这个吧。
SDWebImage(简化网络图片处理)
地址:https://github.com/rs/SDWebImage
用SDWebImage调用网站上的图片,跟本地调用内置在应用包里的图片一样简单。操作也很简单,举例说明
[imageView setImageWithURL:[NSURL URLWithString:@”http://example.com/image.png”]];
类似的功能在Three20里也有,这个过会再说。相比而言,SDWebImage主要是提供一个小而精的简捷方便的解决方案
GData client(iPhone上所有Google相关服务的类库)
地址:http://code.google.com/p/gdata-objectivec-client/
名字就说明一切了。跟Google相关的,值得一提的是,这个项目很开放。有很多示例程序供下载。
CorePlot(2D图形绘图仪)
%title插图%num
地址:http://code.google.com/p/core-plot/
CorePlot有很多解决方案将你的数据可视。,同时也会提供各种迷人的图形效果,比如棒状图、饼状图、线状图等等,在他们网站上也提供了大量的范例图形,很多股票价格应用,游戏分数,个人财务管理都在用。
Three20(通用iOS库)
%title插图%num
地址:https://github.com/facebook/three20
Three20类库是Facebook自己做的,大而全是他*大的特色。把他整合到已有的项目中可能得费点周折,不过如果一开始你就用上了Three20,尤其是牵扯到很多web相关的项目的时候,你就能深刻体会到神马叫给力了。

KissXml——xml解析库
相关教程:http://www.iteye.com/topic/625849

http://sencho.blog.163.com/blog/static/83056228201151743110540/

很方便的一个xml解析器,支持Xpath查询。

 

skpsmtpmessage——Quick SMTP邮件发送
svn checkout http://skpsmtpmessage.googlecode.com/svn/trunk/ skpsmtpmessage-read-only
github:       git clone https://github.com/kailoa/iphone-smtp.git
相关教程:http://disanji.net/2011/01/28/skpsmtpmessage-open-source-framework/
skpsmtpmessage 是由Skorpiostech, Inc.为我们带来的一个SMTP协议的开源实现,使用Objective-c 实现,iOS系统的项目可以直接调用。

zxing——二维码扫描库
支持条形码/二维码扫描的图形处理库,这是一个java库,在android上的功能比较完整。同时该库也支持ios,但只能支持二位条形码的扫描。

 

kal——iPhone日历控件
一个类似于ios系统默认日历开源日历库,支持添加事件,自定义日历样式等功能。

shareKit——分享库
相关demo:http://www.cocoachina.com/bbs/read.php?tid-71760.html
分享到开心,豆瓣,腾讯,新浪微博的api所用到的强大的分享库。

 

FMDatabase——SQLite的Objective-C封装
是SQLite的C API對初學者來說實在太麻煩太瑣碎,難度太高。FMDB說穿了其實只是把C API包裝成簡單易用的Objective-C类。對于SQLite初學者來說,大大減低了上手的難度。有了FMDB,寫程式時只要專心在SQLite的語法上,而不用去理那堆有看沒有懂的C API,實在是件快樂的事情。

 

Panoramagl —— 720全景展示

Panorama viewer library for iPhone, iPad and iPod touch

iCarousel  —— 效果很酷的分页排列

内容类似的页面需要并排列出来,供用户选择。iCarousel具有非常酷的3D效果,比如经典的CoverFlow, TimeMachine。另外还具有线性,圆柱状等其它效果。可用于图片选择,书籍选择,网页选择等。

EGOTableViewPullRefresh —— 下拉列表刷新

使用这个库,很容易就可以实现下拉刷新效果。微博,RSS阅读器之类的软件经常使用。

CMPopTipView —— 泡泡风格的提示界面

一个泡泡风格的提示框开源控件, 继承自UIView。iPad,iPhone通用。

HMGLTransitions —— 视图切换动画

提供一些UIView或UIViewController切换时候的3D动画效果。比如翻转,开门,撕纸等。

QuickDialog —— 表格风格的配置界面

在iphone, 通常使用UITableView来创建一些配置,登录界面,创建这些界面通常很机械很烦人。QuickDialog可以快速地在表格项中放置开关 On/Off控件、日期控件、Sliders、单选按钮编辑框等等。这样就不用使用低级的UITableView。
 

JMTabView  —— 自定义标签栏和Tabbar

JMTabView是一个iOS自定义的标签栏开源控件,界面效果完全使用Core Graphics绘制,而没有用图片,所以内容很容易改为你需要的。
 

SBTableAlert —— 带表格视图的消息对话框

SBTableAlert对话框中提供了一种方式,在UIAlertView视图中包含了UITableView的效果,从而可以实现在UIAlertView中进行表格多选。使用简单。
 

EasyTableView —— 可水平或垂直滚动的TableView

原始的UITableView只可以垂直滚动。EasyTableView可以很方便的实现TableView的水平滚动, 并可重复实现用户自定义的View,就类似重复使用UITableViewCell, 这对于提高效率很有帮助。
 

MTStatusBarOverlay  —— 在状态栏上显示自定义的View

iOS程序通常会在*上面出现一个状态栏。使用这个库,可以很方便的在状态栏上显示一些信息,比如下载进度等。

 

 iOS-MagnifyingGlass  ——IOS放大镜效果

可以选择放大镜的样式,和放大倍数

Openear—— 语音识别和TTS

提供了语音识别和Text-to-speach 的接口

Google Toolbox for Mac(GTM)—— 从不同Google项目收集的代码

包含各种的工具类。比如字符串的base64及二进制编码解码, 系统版本号比较, 路径查找等等。每个工具类都比较独立, 可单独抽出来使用。

 

 

SFHFKeychainUtils(scifihifi-iphone)—— 保存用户密码到keychain中

为了用户安全,可以使用钥匙串Keychain来保存用户密码。SFHFKeychainUtils封装了钥匙串的访问, 读写,使用起来很方便。

 

MKStoreKit —— 程序内购买

程序内购买的流程的封装。

 

GLGestureRecognizer——手势识别器

封装了多种手势的识别器,例如三角形,长方形,圆形,五角星形等

 

扫描wifi信息:

http://code.google.com/p/uwecaugmentedrealityproject/

http://code.google.com/p/iphone-wireless/

tcp/ip的通讯协议:

http://code.google.com/p/cocoaasyncsocket/

 

voip/sip:

http://code.google.com/p/siphon/

http://code.google.com/p/asterisk-voicemail-for-iphone/

http://code.google.com/p/voiphone/

jabber client

http://code.google.com/p/ichabber/

 

PLBlocks

http://code.google.com/p/plblocks/

 

image processing

http://code.google.com/p/simple-iphone-image-processing/

 

base64编码解码:http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/?r=87

xml解析:https://github.com/schwa/TouchXML

 

异步加载图片并缓存代码:http://www.markj.net/iphone-asynchronous-table-image/

iphone TTS:https://bitbucket.org/sfoster/iphone-tts

iphone cook book 源码:https://github.com/erica/iphone-3.0-cookbook-

OAuth认证:  http://code.google.com/p/oauth/
http://code.google.com/p/oauthconsumer/

蓝牙协议栈:http://code.google.com/p/btstack/

语音识别:http://www.politepix.com/openears/

 

地球显示信息:http://code.google.com/p/whirlyglobe/

原文地址: http://www.cocoachina.com/iphonedev/toolthain/2011/1109/3480.html

 

如何用Facebook graphic api上传视频:

http://developers.facebook.com/blog/post/532/

对焦功能的实现:

http://www.clingmarks.com/?p=612

自定义圆角Switch按件:

https://github.com/domesticcatsoftware/DCRoundSwitch

弹出窗口For iphone and ipad:

https://github.com/chrismiles/CMPopTipView

KVO详解:

http://nachbaur.com/blog/back-to-basics-using-kvo

图片浏览:

https://github.com/bdewey/Pholio

Dropbox实例:

https://github.com/bdewey/dropvault

当地天气预报实例:

https://github.com/aspitz/WxHere

可伸缩的toolBar实例:

https://github.com/aspitz/ToolDrawer

app资源保护相关:

http://aptogo.co.uk/2010/07/protecting-resources/

cocos2d中也可用UIScrollView,UITableView,UIGestureRecognizers

https://github.com/jerrodputman/CCKit
http://www.tinytimgames.com/2011/08/05/introducing-cckit/

 

iOS文档导入导出:

http://mobiforge.com/developing/story/importing-exporting-documents-ios

 

CoreAnimation Demo:

https://github.com/bobmccune/Core-Animation-Demos

CoreAnimation Dev:

Part 1 – Frame By Frame Sprites With Core Animation

Part 2 – Space Time

Part 3 – Scrolling Hell

Part 4 – Parallax Scrolling

iOS jabber聊天应用开发:客户端开发

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-interface-setup/

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-custom-chat-view-and-emoticons/

iOS jabber聊天应用开发:服务器搭建

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-server-setup/

 

KeyChain封装,安全存数据:

http://developer.apple.com/library/mac/#documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html
http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007797-Intro-DontLinkElementID_2

声音相关:

http://purplelilgirl.tumblr.com/post/9377269385/making-that-talking-app
http://dirac.dspdimension.com/Dirac3_Technology_Home_Page/Dirac3_Technology.html

弹珠游戏:

http://www.crowsoft.com.ar/wordpress/?p=19

spring boardUI:

https://github.com/rigoneri/myLauncher

MacOS&iOS upnp:

http://code.google.com/p/upnpx

ios block learn:

http://ios-blog.co.uk/iphone-development-tutorials/programming-with-blocks-an-overview/
https://github.com/zwaldowski/BlocksKit

弹出框中输入用户名与密码:

https://github.com/enormego/EGOTextFieldAlertView

 

jailbreak iphone发送sms:

http://code.google.com/p/iphone-sms/

搜索ituneappurl scheme:

https://github.com/Zwapp/schemes-scanner

横竖屏切换自动调整位置:

https://github.com/michaeltyson/TPMultiLayoutViewController

键盘出现与消失view自动移动避免遮挡:

https://github.com/michaeltyson/TPKeyboardAvoiding
http://atastypixel.com/blog/a-drop-in-universal-solution-for-moving-text-fields-out-of-the-way-of-the-keyboard/

iOS类似firebugweb调试工具:

http://phonegap.github.com/weinre/

一个UI开源库tapkulibrary,集成了calendar,coverflow

https://github.com/devinross/tapkulibrary

Tapku: An Amazing Open Source iOS Interface Library

多列的TableView

https://github.com/Xenofex/MultiColumnTableViewForiOS

mac的一个桌面程序,开源的

http://homepage.mac.com/nathan_day/pages/popup_dock.xml

PSTreeGraph for iPad

https://github.com/epreston/PSTreeGraph

文件预览like QLPreviewController

https://github.com/rob-brown/RBFilePreviewer

Interface Builder中用自定义字体解决方案

https://github.com/0xced/FontReplacer

shaderUILabe

https://github.com/nicklockwood/FXLabel

GCD学习

Cocoa Touch Tutorial: Using Grand Central Dispatch for Asynchronous Table View Cells

http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial

https://github.com/SlaunchaMan/GCDExample

UITableView中有search功能教程

How To Search Option Enable In TableView In iPhone

iPad阅读器开发

http://mobile.tutsplus.com/tutorials/iphone/building-an-ipad-reader-for-war-of-the-worlds/

http://mobile.tutsplus.com/tutorials/iphone/ios-sdk-using-a-slider-to-scrub-a-pdf-reader/

http://mobile.tutsplus.com/tutorials/iphone/ios-sdk-adding-a-table-of-contents-to-an-ipad-reader/

ipad UI 24个免费资源

http://www.cocoachina.com/applenews/devnews/2011/0915/3237.html

TableView的扩展

https://github.com/OliverLetterer/UIExpandableTableView

Orge3D for iOS

http://code.google.com/p/gamekit/

http://www.tonybhimani.com/2011/07/09/ogre3d-sdk-1-7-3-for-apple-iphone-ios-howto/

文档比Three20更全的类Three20

https://github.com/jverkoey/nimbus

iOS Boilerplate一个库集合,方便开发

http://iosboilerplate.com/

https://github.com/gimenete/iOS-boilerplate

openCV for iOS

http://code.google.com/p/edgy-camera-ios/

https://github.com/BloodAxe/opencv-ios-template-project

https://github.com/BloodAxe/OpenCV-iOS-build-script

http://computer-vision-talks.com/2011/02/building-opencv-for-iphone-in-one-click/

http://computer-vision-talks.com/2011/01/using-opencv-in-objective-c-code/

http://computer-vision-talks.com/2011/08/a-complete-ios-opencv-sample-project/

PageCurl for iOS

https://github.com/xissburg/XBPageCurl

https://github.com/raweng/FlipView

https://github.com/Split82/HMGLTransitions

http://api.mutado.com/mobile/paperstack/

iOS PDF实例

http://www.cocoachina.com/bbs/read.php?tid=75173

https://github.com/vfr/Reader

Core Animation

http://nachbaur.com/blog/core-animation-part-1

http://nachbaur.com/blog/core-animation-part-2

http://nachbaur.com/blog/core-animation-part-3

http://nachbaur.com/blog/core-animation-part-4

Core Data注意的地方

http://nachbaur.com/blog/smarter-core-data

http://iphonedevelopment.blogspot.com/2009/09/core-data-migration-problems.html

GCD

http://nachbaur.com/blog/using-gcd-and-blocks-effectively

http://deusty.blogspot.com/2011/01/multi-core-ios-devices-are-coming-are.html

MKMapView zoom level

http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/

http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/

HTML parser

Taming HTML Parsing with libxml (1)

https://github.com/topfunky/hpple

https://github.com/zootreeves/Objective-C-HMTL-Parser

openGLES

http://www.ityran.com/portal.php

Charts绘制开源库

http://code.google.com/p/core-plot/

https://github.com/ReetuRaj/MIMChart-Library   说明文档

apple 私有api文档

http://hexorcist.com/private_frameworks/html/main.html

safari的切换页面库

https://github.com/100grams/HGPageScrollView

自定义Slider组件

https://github.com/buildmobile/iosrangeslider

iOS Range Slider Part 1
iOS Range Slider Part 2

一些自定义组件:

自定义UIAlertView

自定义BadgeView

自定义数字键盘

QR Encoder二维码识别

https://github.com/jverkoey/ObjQREncoder

xml解析库

https://github.com/ZaBlanc/RaptureXML

wapper map for iOS

https://github.com/yinkou/OCMapView

iOS unitity

https://github.com/ZaBlanc/iBoost

https://github.com/escoz/QuickDialog/

socket

http://code.google.com/p/cocoaasyncsocket/

custom camera view

https://github.com/pmark/Helpful-iPhone-Utilities

http://www.codza.com/custom-uiimagepickercontroller-camera-view

本地天气demo

http://www.cocoachina.com/bbs/read.php?tid-72558-fpage-3.html

浏览器飞行动画

http://www.cocoachina.com/downloads/video/2011/1002/3313.html

切换动画demo

http://www.cocoachina.com/bbs/read.php?tid-76431-page-1.html

Automatic Reference Counting

http://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html

voip for ios development

http://trac.pjsip.org/repos/wiki/Getting-Started/iPhone

http://www.piemontewireless.net/PJSip155_and_iPhoneSDK312

http://code.google.com/p/siphon/

图像处理

http://www.cocoachina.com/downloads/code/2011/1009/3335.html

脚本自动生成push notification所需证书

https://github.com/jprichardson/GeneratePushCerts

自定义ActivityIndicator

https://github.com/hezi/HZActivityIndicatorView

开源库for ios

boost for iphone

ffmpeg for iphone

opencore amr for iphone

iOS网络相关

bonjour

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速