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: 来取值。

多 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.