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 对象生成,毕竟这些对象都要等到循环结束后才会被释放。
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"];
这时候 value1
和 value2
是一样的结果。如果是这样一个 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 并且是最大化展示,效果还不错。
- 打开 Tab 支持,View - Show Tab Bar.
- 双击或点 + 添加一个 Tab.
- 双击新加的 Tab 改名,比如 CONSOLE.
- 激活 Console 显示,View - Debug Aera - Activate Console,或者直接 Command+Shift+C.
- 拖动 Console 区至顶端,整个 Tab 只显示这个 Console.
- Command+, 进入 Preferences - Behaviors, 在 Run Start 里勾选 Show Tab,填 CONSOLE,就是刚才的 Tab 名。
- Done。
再运行项目时会自动的切换到新 Tab 页查看输出结果,然后通过 Command+Shift+[/] 来切换 Tab.