iPad OS version history

iPad 的系统版本历史 (via iOS version history):

  1. 3.2/3.2.1/3.2.2,iPad Only.
  2. 4.2/4.2.1
  3. 4.3.x
  4. 5.x
  5. 6.x

iPad 的系统版本并不是连续的,3.2 是上市时的版本,iPad 独享。iOS 里程碑 4.0 刚开始并不支持 iPad,直到 4.2 才支持。4.3 随 iPad 2 发布,之后的 iOS 都是全设备支持。

所以做 iPad App 时系统要求可以从 4.2 起步,为了兼容 3.2 的用户需要做很多处理,总体来说不值当。

iPhone 数据存储

稍微总结一下 iPhone 的数据存储。常见的方式有这么几种:

  1. NSUserDefaults
  2. SQLite
  3. Core Data
  4. iCloud
  5. NSKeyedArchiver/NSKeyedUnarchiver
  6. Keychain
  7. UIPasteboard
  8. writeToFile:

NSUserdefaults

NSUserdefaults 是最为常见的方式,通常用来保存简单的数据,比如 App 设置等。数据保存在 $App/Library/Preferences/$BundleID.plist

// 存
[[NSUserDefaults standardUserDefaults] setInteger:100 forKey:@"MaxCount"];
[[NSUserDefaults standardUserDefaults] synchronize];

// 取
[[NSUserDefaults standardUserDefaults] stringForKey:@"StringKey"];

存数据时候最好 [[NSUserDefaults standardUserDefaults] synchronize]; 来及时保存。

SQLite

iPhone 自带了 SQLite 数据库,可以用来存储大数据量的持久化数据,比如 Google Reader 类阅读器缓存的文章内容。SQLite 直接操作 Api 很复杂,一般都会选用一些开源的 wrapper,比如 FMDB.

Core Data

Core Data 是苹果官方推荐的数据存储方式,底层也是拿 SQLite 做持久化。目前还没有在项目中实践过 Core Data,不多说,官方文档 Introduction to Core Data Programming Guide.

iCloud

iCloud 是 iOS 5 带来的新特性,云端同步是最大的优点,iOS+OS X 通用,可以拿来做一些很神奇的事情,比如 Tweetbot 通过 iCloud 同步 iPhone 和 iPad 上时间线的阅读位置。目前也没有在项目中实践过。

iCloud 可以和 NSUserDefaults 配合使用,比如 MKiCloudSync,sync your NSUserDefaults to iCloud automatically。

NSKeyedArchiver

NSKeyedArchiver 可以将数据 encode 后保存成文件,或者通过 NSUserDefaults 保存;对应 NSKeyedUnarchiver 用来读取数据。适合数据结构复杂(NSArray/NSDictionary)数据量较大但又不需要用 SQLite 做持久化存储的中间缓存,比如 MKNetworkKit 的 freezeOperations 操作,很方便。

// 保存 dict 到 Library 下 Dict 文件
[NSKeyedArchiver archiveRootObject:dict
                            toFile:NIPathForLibraryResource(@"Dict")];
// 读取
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithFile:NIPathForLibraryResource(@"Dict")];

Keychain

Keychain 就是钥匙串,加密保存用户帐号、密码等重要信息。推荐两个 wrapper:SSKeychain, PDKeychainBindingsController

UIPasteboard

UIPasteboard 系统剪贴板是一个非常巧妙的数据存储方式,最大的好处就是可以夸应用数据访问,比如词典应用自动读取翻译剪贴板内容,非常方便。OpenUDID 就是通过一个特殊的剪贴板来保存唯一设备字符串,这样其他 App 就可以用这个唯一标识做设备区分。

writeToFile:

writeToFile: 可以直接将数据写入到指定路径的文件中,NSArray、NSDictionary、NSData 都支持。做数据缓存用的比较多,比如 EGOCache

2012 年中总结

2012 上半年小结。

工作

去年年终总结时候说今年要加强产品锻炼,今年团队也给了这样的机会。第一次作为产品负责人带产品开发,自己却做的很不好,很不及格。产品这一块还有很多很多要学习的地方,分工、协作、沟通、整体、进度等等,在接下来的工作中还要多多学习。

上半年在技术上有了一些新的学习和积累,做了第一个真正意义上的 iPad 应用。大屏幕的体验、使用场景、使用习惯都和 iPhone 有着很大的不同,信息结构有着不一样的处理,对应交互、UI 上有很大的差异,这些差异需要更多的学习理解。开发上由于 iPad 项目是一个全新的项目,尝试了一些新的技术和框架,包括 ARC,新的网络请求方式等。新技术和框架的学习需要一定时间和成本,甚至要交学费,但是从长远来看,这些投入都是值得的,对自己的技术成长很有好处,也有利于团队技术积累。作为开发,如果现在用着和一年前一样的代码做产品,虽然看起来效果一样,但自身的成长非常有限。

作为小团队,机动灵活是自己的特点,也是和大公司竞争的优势,因此在产品开发中要尽快拍板,速度迭代,先简单快速满足 80% 用户的需求,再去精益求精「像素级」地满足剩下 20% 用户。

生活

今年生活中的重头戏就是完婚。结婚是一项庞大的工程,我和妞妞周末忙乎着婚纱照、礼服,爸妈在家忙婚礼安排、婚房装修,有时候还要远程遥控订化妆、摄影。婚礼就是两家人花费忙活,两个人受累上演给别人看得一场秀,表现着家族的威望、实力。如果你能躲过这场秀,那么恭喜你省力很多;如果不能避免,那么何不把这场秀演的更好呢?

上半年陆续有三个兄弟离职回老家工作,称不上「衣锦还乡」,但至少离家近点,毕竟我们都是念家的孩子。对于兄弟们回家工作,我都是双手赞成,虽然我自己还在坚持北漂。有时候我也会问自己到底在坚持什么,那些个梦?那些个理想?那些个自己不愿被这世界改变?我不想等我老了的时候后悔没有拼搏坚持过。

向阳说「成长就是让你从诗人变成世人」。其实,从诗人变成世人,我们都输了。在坚持做自己喜欢的事,还要记住不做让自己讨厌自己的事。最近很多事情让我越来越感受到拒绝比坚持更难。

不知道跟谁学的,现在酒喝多了就满世界打电话,还哭,太差劲。酒量也严重退步,估计结婚的时候该被灌成傻子了。

嗯,以上。

object == nil or nil == object

在 Objective-C 中拿到一个对象后检查对象是否为空,一般有两种写法

一:

if (object == nil) {
    //...
}

二:

if (nil == object) {
    //...
}

这两种写法其实没有任何区别,从代码的可读性上来说第一种 object == nil 方式会好一点。但是推荐用第二种 nil == object 方式,最大的好处就是如果由于笔误 == 写成了 =,编译器会直接报错处理。而 object = nil 不会报错,一旦笔误写成了 object = nil 是很难 debug 查找问题。

via object == nil or nil == object to check whether an object is nil?

2 Years in Beijing

留记。

2010.06.17 到 2012.06.17,很有收获的两年,很有成长的两年。是一个结点,也是新的起点。

postNotificationName with GCD

用 GCD 在后台线程进行下载任务,下载完成后通过 NSNotificationCenter post 一个消息出来,这时候要注意 postNotificationName: 必须要回到主线程进行,不然会引发 crash.

dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:AnyNotification
                                                        object:nil];
});

resignFirstResponder doesn't work on iPad

在 iPad 上,用 modalPresentationStyle = UIModalPresentationFormSheet 方式推出一个 viewController,这时这个 viewController 不会响应 resignFirstResponder,其他样式的 modalPresentationStyle 没有问题。苹果一个开发在开发者论坛说这是个 feature,不是 bug,devforums.apple.com

Was your view by any chance presented with the UIModalPresentationFormSheet style? To avoid frequent in-and-out animations, the keyboard will sometimes remain on-screen even when there is no first responder. This is not a bug.

就算不是 bug 也很恼人,有人给出了解决方法 devforums.apple.com。新建一个 UINavigationController category,禁掉 disablesAutomaticKeyboardDismissal

- (BOOL)disablesAutomaticKeyboardDismissal
{
    return NO;
}

然后把 viewController 挂在 UINavigationController 下即可:

MyViewController *myViewController = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myViewController];
theNavigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:theNavigationController animated:YES];

SO 参考:

隐藏 UITableView 下不需要的分割线

UITableViewStylePlain 样式下的 UITableView 如果显示分割线,就会在 tableView 下显示额外的空白 cell 和分割线。在 SO 上发现一个小技巧来解决这个问题 Eliminate Extra separators below UITableView - in iphone sdk?

UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.width, 1)];
v.backgroundColor = [UIColor whiteColor];
[self.tableView setTableFooterView:v];

@autoreleasepool in loop or loop in @autoreleasepool

如果循环中会生成大量的 autorelease 对象,可以考虑用 autorelease pool 来进行封装。封装时候有两种方式:

1:

while ([rs next]) {
    @autoreleasepool {
        NSDictionary *dict = [self dictFromXX];
        //...
    }
}

2:

@autoreleasepool {
    while ([rs next]) {
        NSDictionary *dict = [self dictFromXX];
        //...
    }
}

第一种,也就是 @autoreleasepool in loop 方式下每次循环都会生成一个 pool,在单次循环结束后被 drain 掉,适用于每次循环都有大量的 autorelease 对象生成,在单次循环结束后可以及时的将资源释放。

第二种,loop in @autoreleasepool 下只有一个 pool,只会在整个循环结束后 drain 掉,也就是说第一次循环时生成的 autorelease 对象也要等到整个循环结束时候才会随着 pool 释放。适用于循环次数不太多,且每次循环只有少量的 autorelease 对象生成,毕竟这些对象都要等到循环结束后才会被释放。

ref @autoreleasepool in loop or loop in @autoreleasepool?

Difference between objectForKey and valueForKey in NSDictionary

从 NSDictionary 取值的时候有两个方法,objectForKey:valueForKey:,这两个方法具体有什么不同呢?

先从 NSDictionary 文档中来看这两个方法的定义:

objectForKey: returns the value associated with aKey, or nil if no value is associated with aKey. 返回指定 key 的 value,若没有这个 key 返回 nil.

valueForKey: returns the value associated with a given key. 同样是返回指定 key 的 value。

直观上看这两个方法好像没有什么区别,但文档里 valueForKey: 有额外一点:

If key does not start with “@”, invokes objectForKey:. If key does start with “@”, strips the “@” and invokes [super valueForKey:] with the rest of the key. via Discussion

一般来说 key 可以是任意字符串组合,如果 key 不是以 @ 符号开头,这时候 valueForKey: 等同于 objectForKey:,如果是以 @ 开头,去掉 key 里的 @ 然后用剩下部分作为 key 执行 [super valueForKey:]

比如:

NSDictionary *dict = [NSDictionary dictionaryWithObject:@"theValue"
                                                 forKey:@"theKey"];
NSString *value1 = [dict objectForKey:@"theKey"];
NSString *value2 = [dict valueForKey:@"theKey"];

这时候 value1value2 是一样的结果。如果是这样一个 dict:

NSDictionary *dict = [NSDictionary dictionaryWithObject:@"theValue"
                                                 forKey:@"@theKey"];// 注意这个 key 是以 @ 开头
NSString *value1 = [dict objectForKey:@"@theKey"];
NSString *value2 = [dict valueForKey:@"@theKey"];

value1 可以正确取值,但是 value2 取值会直接 crash 掉,报错信息:

Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<__NSCFDictionary 0x892fd80> valueForUndefinedKey:]: this class is not key value coding-compliant for the key theKey.’

这是因为 valueForKey:KVC(NSKeyValueCoding) 的方法,在 KVC 里可以通过 property 同名字符串来获取对应的值。比如:

@interface Person : NSObject
@property (nonatomic, retain) NSString *name;
@end

...
Person *person = [[Person alloc] init];
person.name = @"fannheyward";

NSLog(@"name:%@", [person name]);
//name:fannheyward
NSLog(@"name:%@", [person valueForKey:@"name"]);
//name:fannheyward
[person release];

valueForKey: 取值是找和指定 key 同名的 property accessor,没有的时候执行 valueForUndefinedKey:,而 valueForUndefinedKey: 的默认实现是抛出 NSUndefinedKeyException 异常。参考Getting Attribute Values Using Key-Value Coding

回过头来看刚才 crash 的例子, [dict valueForKey:@"@theKey"]; 会把 key 里的 @ 去掉,也就变成了 ` [dict valueForKey:@”theKey”];,而 dict 不存在 theKey 这样的 property,转而执行 [dict valueForUndefinedKey:@”theKey”];,抛出 NSUndefinedKeyException` 异常后 crash 掉。

objectForKey:valueForKey: 在多数情况下都是一样的结果返回,但是如果 key 是以 @ 开头,valueForKey: 就成了一个大坑,建议在 NSDictionary 下只用 objectForKey: 来取值。