2013 年中总结

2013 年上半年小结。

工作

年初计划今年能加强一下服务端开发学习,得到了公司的大力支持,从三月份开始跟进,到现在基本能独立负责一个线上服务的维护和新需求开发,算是有一点点进步。当然,作为服务端开发新手,有着非常多的不足。

  1. 开发进度慢,工期预估偏差太大。作为一个 rookie,很多东西是有听说但完全没有经验,比如 PostgreSQL,直到最近一个项目中直接接触才知道原来 SQL 也可以编程,也可以写 FUNCTION,自己之前对数据库的经验只会最简单的 SELECT/INSERT/UPDATE/DELETE。这种经验不足的后果就是做每个东西的时候都有很多要去学习,而服务端开发的一个特点就是知识点特别碎特别多,由于知识点的掌握不足,在预估工期的时候就很容易忽视一些“小”细节,最终拖慢开发进度。
  2. 基础性知识掌握太差。比如 HTTP,比如 Socket,在做应用层的时候感触还不深,知道个大概可能也就够了,但是做服务端偏底层的时候不了解这些东西视野很受限制,包括算法等,这些一直是我的软肋,是时候交学费补习一下。
  3. 技术点不够深入,比如 Redis,知道是个 Key-Value 数据库,但是 Hash/List/Set 各适用于哪些场景,LPOP 和 BLPOP 用哪个命令更合适,等等问题,这些都需要在后续的开发中多深入学习和理解。

当然做服务端也有爽的地方,设计开发需求功能,封装 SDK 再转交客户端去用,这一整套的把控相当有成就感。这种 Full Stack 开发模式对自己在架构层的锻炼很有帮助,比如 API 设计,怎么更好的兼容扩展、方便客户端使用都需要去考虑,这方面自己还有很多要学习。Full Stack 开发需要对业务熟悉,对客户端前端功能了解,对后端开发实现掌握,确实是一种非常有意思的开发模式。

客户端方面上半年仍然是项目把控为主,主要是代码 review,SDK 封装,bug 排查修复等,以保证客户端稳定性。客户端技术方面在 Core Text 方面做了一些实践,整体进步不大,下半年正好可以借着 iOS 7 的上市来跟进新技术的学习。再有就是公司内部其他客户端项目的跟进学习,争取在年底时候能够把内部开发都需要的一些共用功能模块打通,做好 Team Support.

生活

一个人会宅,两个人就变懒。周末活动基本都是逛超市,看电影,然后吃顿海底捞犒劳一下自己,倒也惬意。

三月初两个人报了驾校,这俩月周末基本都是在驾校练车。学车并不是说要买车,只是作一个技能储备,回头有需要的时候可以直接拿来用。练车其实挺累人的,天气又热,精神高度集中的坐上半天绝对比上班还要累。还好整个过程都很顺利,两个人各科目都一次性通过,七月初就能拿到驾照。

四月初跟同事一起办了健身卡,每天下午去跑步,学游泳,就是太笨,到现在蛙泳还很吃力,身体太不协调了。体重一直徘徊在 81kg 左右,相较于冬天瘦了五公斤,但始终不能跌破 80 大关,健身还要继续,为年底的造人计划做准备。

以上。

Perform block after delay

thoughts

搭功夫省钱,花钱省时间。

生活中的事无非这两种情况,所以遇见事情不要慌,总有解决办法的。

Three years in Beijing

2010-2013, three years in Beijing.

莫忘初衷

你好久没说梦想 说到眼睛发亮

不可一世的笑容 连我都被感动

我们说改变世界 却被世界改变

记得你要我提醒 别改变太多

莫忘初衷 莫忘初衷

别忘 那一年 那一天 出发时心中的梦

How FB Build Facebook for iOS

Mobile DevCon New York - How We Built Facebook for iOS

非常值得一看,介绍了 FB 开发 Facebook.app 的工作流。摘要记录几点:

  1. Core Team, support and assist. 主要负责不同应用之间通用库、共用功能的开发,保证应用的稳定性。//这也是目前我在做和努力的方向,接下来是团队内跨应用支持。
  2. Release process. FB 有专门的 release team,效仿 Mozilla 每四周发布流程,如果发布周期有功能还不稳定就通过 #define runtime 屏蔽。//快速迭代。
  3. DON’T BREAK MASTER. git branch 进行功能开发,team code review,然后自动编译测试(CI),通过后合并到 master。 //非常标准的 git workflow,说着容易做到难,尤其是坚持一直这样做。
  4. Phabricator code review and CI. Phabricator 是 FB 开发的 code review 工具,附带 arc lint 代码分析工具,enforce style guidelines, set up rules to catch common mistakes.
  5. Multiple Builds. 不同的 BundleID 来分发测试 Development build/Daily buid/App Store build。 //目前我们的 daily build 还是手动挡,接下来配上 CI 试试自动化。
  6. Testing. 由于 iOS 测试工具链的不成熟和复杂(Data, UI),FB 采用 Xcode 自带的测试,配合丰富的 Logs+view hierarchy. //目前我们在用 Lumberjack log 工具,非常不错。

感叹 FB 如此大规模的公司还能如此敏捷开发,技术驱动,而非行政干预,嗯。

使用 strings 查看静态库字符串

起标题真费劲,意思就是用 OS X 的 strings 命令查看一个静态库是否包含某个字符串。比如 lib.a 是否用到了 uniqueIdentifier (苹果新规用了 UUID 的应用将会被拒)。

strings - find the printable strings in a object, or other binary, file.

用法很简单:

strings lib.a|ag uniqueIdentifier

这个强大的命令可以做很多事情,比如这个

strings manual page.

Update: 查看当前路径下引用了 UUID 的文件:

find . | grep -v .git | grep -v "\.app" | grep "\.a" | xargs grep uniqueIdentifier

tmux 使用笔记

  1. tmux new -s name 新建名字为 name 的会话(session),等同 tmux new-session -s name, 指定名字方便 attach。
  2. tmux rename -t session1 session2 重命名 session1 为 session2,等同 tmux rename-session -t session1 session2
  3. tmux ls 列出所有会话,等同 tmux list-sessions
  4. tmux at -t name attach 名字为 name 的会话。
  5. tmux at -d 重绘窗口,在大小不同屏幕上用 tmux 时候会保持窗口大小为最小尺寸,这个命令就可以重置窗口大小。via
  6. tmux kill-session -t name 干掉指定名字的会话,关闭会话所有窗口自动会关掉会话。
  7. tmux kill-window -t name 关闭指定窗口,很少用,一般都是 Ctrl-b & 关闭本窗口。
  8. Ctrl-b d 脱离会话回到终端。
  9. Ctrl-b [ 进入复制模式,滚屏查看,支持 vim 上下翻页快捷键。
  10. Ctrl-b c 新建窗口。Ctrl-b & 关闭窗口。
  11. set-window-option -g mode-keys vi 设置复制模式中键盘布局为 vi。
  12. Ctrl-b w 列出所有窗口,可用 vim j/k 上下翻页。
  13. Ctrl-b : - rename-window 重命名窗口。
  14. Ctrl-b n/p 切换到下一个/前一个窗口,也可以直接用 Ctrl-b 数字 切换到指定窗口。
  15. Ctrl-b %/" 分割窗口为面板(panel)。Ctrl-b x 关闭面板。
  16. Ctrl-b Alt+方向键 调整面板大小。
  17. Ctrl-b t 很酷的一个时钟。

tmux 支持 ~/.tmux.conf 配置文件,推荐设置 set-option -g base-index 1 让窗口从 1 排序,方便数字键切换。更多设置参考 Wiki 使用tmux

Tmux Plugin Manager,插件管理,推荐 tmux-sensible, tmux-resurrect.

AFNetworking 学习笔记二

AFNetworking 学习笔记 的后续,记录一些 AFN 比较隐蔽的知识点。

AFN 的设计过于理想化

AFN 的架构设计非常棒,使用起来也很简单,但一些设计过于理想化,在实际开发中会有一些条件不能满足,这时候 AFN 就会出现一些“坑”。

1. 缓存策略

NSURLRequest 默认的缓存策略是 NSURLRequestUseProtocolCachePolicy,网络请求是否用缓存是由 HTTP Cache-Control 决定,而在实际开发中由于种种原因(服务端为了简化系统等),接口的缓存时间都设置的非常长或者不准,这种情况下就会出现服务端数据更新但是 AFN 拿到的还是旧数据,因为他直接读的缓存。

得益于 AFN 优秀的架构设计,这个问题也很好解决,继承 AFHTTPClient 然后重写 requestWithMethod:path:parameters::

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters
{
    NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];

    return request;
}

2. Response 类型判断

以 AFJSONRequestOperation 为例,只有 Content-Type 是 @"application/json", @"text/json", @"text/javascript" 或 URL pathExtension 是 json 的才会被认为是 JSON 类型,其他都不认。很多服务端接口都没有 Content-Type 返回或者直接丢一个 text/html,请求也不是 json 结尾,但返回内容确实又是 JSON 数据,这时候 AFN 就很无力。


上面这两个问题的根本原因是服务端由于各种各样的问题不能严格按照 HTTP 要求返回正确格式的内容,造成 AFN 无法按照标准去接收解析。责任虽不在客户端开发,但实际开发中确实存在这种情况,这个时候就需要客户端去迂回解决,好在 AFN 的架构设计很容易扩展。

AFN vs ASI

AFN 已经取代 ASIHTTPRequest(ASI) 成为 iOS 开发中首选的网络库,但不能说 AFN 就完胜 ASI,比如这篇 对比iOS网络组件:AFNetworking VS ASIHTTPRequest,AFN 在易用性上胜出,在性能上并没有 ASI 好(因为 ASI 是直接用 CFNetwork 底层而 AFN 是用 NSURLConnection)。

就我自己实际开发来说,AFN 最大的不便是没有 synchronous 请求方式,只支持异步请求。很多时候我们只是想发一个请求,无需返回处理,这种情况下 AFN 这种自定义 HTTPClient 的方式就过于复杂。

最近发现了一个网络库 STHTTPRequest,基于 NSURLConnection,支持 synchronous+asynchronous blocks,支持文件上传,非常简单轻量的封装,值得一试。

Retain Cycle in Blocks

个人笔记,可能会有理解不够透彻而错误。 @fannheyward

Objective-C 是基于引用计数(retainCount)来做内存管理,ClassA 用到 ClassB 的时候,通过 alloc/retain/copy 等将 objectB.retainCount+1,不需要的时候通过 release/autorelease 将 objectB.retainCount-1. retainCount 归零后对象 objectB 被释放。假如 objectA retain objectB,objectB 反过来也 retain objectA,结果就是两者 retainCount 都无法归零,也就没办法被释放,造成内存泄露。这就是 Retain Cycle。

一般情况下注意避免两个对象互相 retain 就不太会出现 Retain Cycle,但是在用到 Blocks 的时候就要小心,很容易造成 Retain Cycle。这是因为 Blocks 会自动 retain 它引用的对象(block 里的对象),稍不留神就造成 Retain Cycle。文档: Object and Block Variables

When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:

  • If you access an instance variable by reference, a strong reference is made to self;
  • If you access an instance variable by value, a strong reference is made to the variable.

To override this behavior for a particular object variable, you can mark it with the __block storage type modifier.

MRC

一个简单的例子,Xcode 会报 Retain Cycle warning:

UIImageView *imgView = [[UIImageView alloc] initWithFrame:rect];
[imgView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
    // ...
    imgView.image = image; // warning: Capturing 'imgView' strongly in this block is likely to lead to a retain cycle
}];

block 也是一个对象,[imgView setImageWithURL:completed:] 的时候 retain 了这个 block;而 block 又自动的 retain 了 imgView,所以就造成了 Retain Cycle。解决方法就是用 __block 告诉 block 不要 retain 引用的对象:

__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:rect];
[imgView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
    // ...
    imgView.image = image;
}];

还有一种情况,block 里引用的对象是 self 或者 self.property,解决方法同理:

__block MyClass *myClass = self;
operation.completeBlock = ^(NSInteger index) {
    [myClass doOther];
};

self.imgView = [[UIImageView alloc] initWithFrame:rect];
__block UIImageView *tmpView = _imgView;
[_imgView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
    tmpView.image = image;
}];
}

ARC

在 ARC 下不能用 __block 关键字,取而代之的是 __weak 或者 __unsafe_unretained。其中 __weak 只能 iOS 5+ 使用,__unsafe_unretained 支持 iOS 4。如果 App 不需要考虑 4.x 用 __weak 会更好一些,__weak 修饰的对象释放后会被设置为 nil,而 __unsafe_unretained 会继续指向原来的内存。

__block MyClass *myClass = self;              // MRC
__weak MyClass *myClass = self;               // ARC & iOS 5+
__unsafe_unretained MyClass *myClass = self;  // ARC & iOS 4.

一些参考文章: