Core Data 是 Cocoa 里面一套非常受欢迎的框架,从 Mac OS X 10.4 提供以来,在 10.5 中引入了完善的 schema 迁移机制,再到 iPhone OS 3.0 时被引入 Cocoa Touch,这套完善的框架都被认为是管理大量结构化数据所首选的 Cocoa 框架,尤其是因为使用 Core Data 能大大减少需要手工编写的代码量,就使它更受开发者欢迎了。
不过最近却出现了一些不同的声音,先是传出消息说 Aperture 3.0 抛弃了 Core Data,改为直接操作 SQLite 数据库 (大家联想到 Apple Mail 3.0 也是直接用 SQLite,没有用 Core Data),但因为都是 Apple 内部的决定,大家只能凭空猜测理由;接下来,NetNewsWire 的开发者 Brent Simmons 也说在NetNewsWire for iPhone 里从 Core Data 转向用 FMDB 来操作 SQLite 数据库 (FMDB 是 Gus Mueller 编写的一层很薄的 SQLite 在 Objective-C 下的封装),Brent 给的理由就很充分了:他要做的很多操作都是对数据批量进行的,其实不需要把所有数据都保存在内存里遍历执行,那样更慢,直接交给数据库,往往一条语句就搞定了,简洁而且快速。
然后 Jonathan ‘Wolf’ Rentzsch 也对此深表赞同,并推荐 Aaron Hillegass的 BNRPersistence 框架,这个框架用 Tokyo Cabinet 提供了一个类似 Core Data 接口的数据持久化方案,最大的优点是比 Core Data 快得多,根据 Aaron 自己的测试,常见的操作都要快 10 ~ 20 倍。其实快这么多也可以理解,毕竟 BNRPersistence 要比 Core Data 轻量得多,支持的功能也少很多,加上 Tokyo Cabinet 这样的 Key-value 数据库在处理适合它的操作时,多数要比 SQLite 这样的关系型数据库要快。
所以突然 Core Data 就有点被墙倒众人推的意思,好像以前大家都知道它不好用,但都不好意思说,直到突然有经验足够丰富的开发者开头,就一涌而上开始骂了。我个人的观感是 Core Data 作为官方方案,给开发提供的许多便利还是不可小视的,但考虑学起来确实也不容易 (所以才有人专门写本书讲 Core Data),所以新上手的 Cocoa 程序员不妨先考虑一下 BNRPersistence, FMDB 这样的方案。
那是否 Core Data 就该被抛弃呢?目前的争议其实有点像 Web 开发里到底该不该用 ORM,区别是大多数 Web 开发者因为历史原因,对直接进行数据库操作有偏好 (或者说,本能地反感 ORM),而多数 Cocoa 开发者则坚定支持用对象操作数据,所以长远来看,数据持久化方案在 Cocoa (Touch) 开发里少不了,唯一的疑问是 Apple 会不会继续改进 Core Data 的性能和接口,以拉近与第三方方案的差距,只要 Apple 还在不断改进,Core Data 就有学习的必要。
我个人对 Core Data 的怨念主要是在要写的“胶水代码”上:有的时候为了方便与界面的 bindings 操作,不得不给 Model 写大量重复的胶水代码,所以如果第三方的方案如果在这方面有简化,我很乐意改用。
转载:http://blog.jjgod.org/2010/02/28/core-data-or-not/
处理GET请求:
StringBuffer sb = new StringBuffer(); HttpClient httpClient = new DefaultHttpClient(); HttpParams httpParams = httpClient.getParams(); HttpConnectionParams.setConnectionTimeout(httpParams, 5000); HttpConnectionParams.setSoTimeout(httpParams, 5000); //当参数值包含中文时,需要用URLEncoder类对参数值进行编码处理 HttpResponse response = httpClient.execute(new HttpGet(url)); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ HttpEntity entity = response.getEntity(); if(entity!=null){ BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent())); String line = null; while((line=reader.readLine())!=null){ sb.append(line + "\n"); } reader.close(); } }
处理Post请求:
StringBuffer sb = new StringBuffer(); HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); //post方式时,需要用NameValuePair数组传递参数 List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(); nameValuePairs.add(new BasicNameValuePair("username", "cjm陈")); nameValuePairs.add(new BasicNameValuePair("password", "123")); httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8")); HttpResponse response = httpClient.execute(httpPost); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ HttpEntity entity = response.getEntity(); if(entity!=null){ BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent())); String line = null; while((line=reader.readLine())!=null){ sb.append(line + "\n"); } reader.close(); } }
它的输入是一个LIST 数据集合;它的输出也是一个LIST数据集合。
该函数中常常是调用内置emit函数,emit函数通常有两个参数(key,value),
map函数最终执行结果是输出一个LIST数据集合,其中每条数据格式必定是emit函数的参数key,value所对应的数据(key,value)。
综合而言,map函数的作用是生成某类doc数据的索引视图,视图中的key 和value 完全可以用emit函数的两个参数来约定。例如:可以用doc.type来指定需要关注的doc数据的类型,然后用doc.id,doc.descript分别作为emit函数的两个参数。那么这个map函数所输出的LIST数据集合中所有的数据的key就是doc.id,value就是doc.descript。
这里需要注意的是:emit函数的两个参数 key 和 value 并不是一定是doc数据的某个属性,可以是任意属性的组合例如:
1、key 可以是{ "model": doc.model},如果 key 被指定为 doc.id以外的属性的话,其实起着类似SQL中的分组的作用。
2、value 可以是{ "make": doc.make, "model": doc.model, "year":doc.year }。
通俗说 map函数 生成指定类型的文档数据集合所对应的视图,视图的索引ID和其他所需的属性可以在emit函数的参数中指定。
reduce函数:
它的输入有可能是一个LIST 数据集合(来自map函数的输出);如果客户端没有特别要求(不指定group=true),它的输出一定是一条数据。所以说该函数的作用是对数据集做聚集处理。例如总计或求平均值等这样的操作。
这个函数有三个参数:(key, values, rereduce),
根据输入的数据集合的大小,couchDB 在执行reduce函数的时候可以对输入数据集合做分组计算,如果是这样的话,在做分组计算的时候,couchDB会把reduce函数的参数rereduce先设定为false,所有的分组计算完毕后,couchDB会把参数rereduce被设定为true,然后把计算的中间结果作为参数values再次传递给reduce函数计算。
所以,需要注意的是,reduce函数的实际执行过程有以下两种方式:
方式一:在参数rereduce为false的时候,reduce函数的参数(key, values)和map函数的输出是一样的。
方式二:在参数rereduce为true的时候,这时候reduce函数的参数(key, values)不是map函数的输出,具体是这样的:其中:key是null,而values,是上次reduce函数被执行后的输出(分组计算结果)。也就是说只有当reduce函数被调用过至少一次(先做分组运算)的场合,才会发生参数变化的现象。
总结:有的时候,无论参数rereduce为true或是false,最终的结果是一样的。
例如以下数据为map函数的输出
1:1
1:4
1:5
2:3
2:4
2:8
3:9
3:2
3:5
以上数据列可以按照key值,分成三个组(三个组的key可以理解为:1、2、3):
Group A: 1, 4, 5
Group B: 3, 4, 8
Group C: 9, 2, 5
那么,couchDB视图引擎首先在reduce函数
function(key, values, rereduce) {
return sum(values);
}
中执行分组计算(参数rereduce=false)执行,计算结果如下:
1:10
2:15
3:16
然后reduce函数再次被调用(注意:参数rereduce被设定为true)
,reduce函数的最终结果=41。
事实上无论参数rereduce为true或是false,对于这个特殊的reduce函数而言,最终的结果都是41。
特别要说明的是:如果你是用 couchDB的Futon管理工具执行这样的may/reduce函数的时候,执行结果是(因为Futon自动向couchDB发出带有group=true检索请求):
1:10
2:15
3:16
但是如果你是使用cURL工具的话(不指定group=true),执行的结果是:
41
对于这个特定的求和函数而言,我们期望的结果不是
1:10
2:15
3:16
而是41。
couchDB map/reduce函数,reduce函数是可选的,而且在实际应用中我们可以控制让reduce函数循环被执行(返回一条数据),也可以只让它执行一次(返回多条数据)。这完全取决我们实际业务需求。