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 [email protected], invokes objectForKey:. If key does start with [email protected], strips the [email protected] 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: 来取值。

多 Target 下不同的 Bundle Display Name

真不好用一个标题来概括这个东西。Xcode 4.2+ 在项目多语言包 xx.lproj 里引入了一个叫 InfoPlist.strings 的文件,可以对同一个 App 在不同系统语言下显示不同的 Display Name。比如:

InfoPlist.strings (English) - "CFBundleDisplayName" = "English Name";
InfoPlist.strings (Chinese) - "CFBundleDisplayName" = "中文";

在单 Target 下很容易做,多 Target 的时候就需要做一点额外的处理。在项目目录下新建与 Target 同名的文件夹(同名是为了方便区分),然后将 xx.lproj 文件夹 复制 到各个 Target 下面,目录结构会是这个样子:

./Target1/
          en.lproj/InfoPlist.strings
          zh-Hans.lproj/InfoPlist.strings
./Target2/
          en.lproj/InfoPlist.strings
          zh-Hans.lproj/InfoPlist.strings

复制后保持项目目录下还有 xx.lproj 文件夹,里面保留 Localizable.strings,因为多语言化一般是通用的,没必要针对每一个 Target 做多语言。复制后的 Target1/xx.lproj 下只有 InfoPlist.strings。然后添加到 Xcode 项目里,打开 Xcode - Views - Utilities (Command+Option+0),在 Target Membership 下针对不同的 Target 把对应文件夹下的 InfoPlist.strings 对应连接起来,Done。

最大化 Xcode Debug Console 窗口

参考 How to get back the Console window in XCode4 做了一点点改动,Run 的时候自动切换到 Console Tab 并且是最大化展示,效果还不错。

  1. 打开 Tab 支持,View - Show Tab Bar.
  2. 双击或点 + 添加一个 Tab.
  3. 双击新加的 Tab 改名,比如 CONSOLE.
  4. 激活 Console 显示,View - Debug Aera - Activate Console,或者直接 Command+Shift+C.
  5. 拖动 Console 区至顶端,整个 Tab 只显示这个 Console.
  6. Command+, 进入 Preferences - Behaviors, 在 Run Start 里勾选 Show Tab,填 CONSOLE,就是刚才的 Tab 名。
  7. Done。

再运行项目时会自动的切换到新 Tab 页查看输出结果,然后通过 Command+Shift+[/] 来切换 Tab.

Enable SPDY in Firefox 11

SPDY is a new network protocol developed by Google for faster web. Google Chrome has a build-in support of SPDY long long ago. Now Firefox brings SPDY support in Firefox 11, but is disabled by default. You can enable it as follow:

Open about:config in Firefox, search network.http.spdy.enabled and set to true. via here