NSDateFormatter 返回一年前时间

NSDateFormatter 的一个小陷阱:

NSString *ds = @"2013-03-01 23:55:56";
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
NSDate *date1 = [formatter dateFromString:ds];
NSLog(@"date1: %@", date1); //date1: 2012-03-01 15:55:56 +0000
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSDate *date2 = [formatter dateFromString:ds];
NSLog(@"date2: %@", date2); //date2: 2013-03-01 15:55:56 +0000

格式化以后是一年前的一个时间点,yyyy 指代的就是常规意义上的年,而 YYYY 是 Week of Year, 具体解释参考 Wikipedia: ISO_week_date

iOS 开发文档有相关提示:

It uses yyyy to specify the year component. A common mistake is to use YYYY. yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar. In most cases, yyyy and YYYY yield the same number, however they may be different. Typically you should use the calendar year.

NSMutableAttributedString Notes

Core Text 针对文本段落支持的样式属性:

typedef CF_ENUM(uint32_t, CTParagraphStyleSpecifier) {
    kCTParagraphStyleSpecifierAlignment = 0,
    kCTParagraphStyleSpecifierFirstLineHeadIndent = 1,
    kCTParagraphStyleSpecifierHeadIndent = 2,
    kCTParagraphStyleSpecifierTailIndent = 3,
    kCTParagraphStyleSpecifierTabStops = 4,
    kCTParagraphStyleSpecifierDefaultTabInterval = 5,
    kCTParagraphStyleSpecifierLineBreakMode = 6,
    kCTParagraphStyleSpecifierLineHeightMultiple = 7,
    kCTParagraphStyleSpecifierMaximumLineHeight = 8,
    kCTParagraphStyleSpecifierMinimumLineHeight = 9,
    kCTParagraphStyleSpecifierLineSpacing = 10, /* deprecated */
    kCTParagraphStyleSpecifierParagraphSpacing = 11,
    kCTParagraphStyleSpecifierParagraphSpacingBefore = 12,
    kCTParagraphStyleSpecifierBaseWritingDirection = 13,
    kCTParagraphStyleSpecifierMaximumLineSpacing = 14,
    kCTParagraphStyleSpecifierMinimumLineSpacing = 15,
    kCTParagraphStyleSpecifierLineSpacingAdjustment = 16,
    kCTParagraphStyleSpecifierLineBoundsOptions = 17,

    kCTParagraphStyleSpecifierCount
};

使用方法:新建一个样式 CTParagraphStyleSetting,设置样式属性和相关值,然后添加到 NSAttributedString.

NSMutableAttributedString *attriStr = [[NSMutableAttributedString alloc] initWithString:string];
// 样式1: 两端对齐
CTTextAlignment alignment = kCTJustifiedTextAlignment;
CTParagraphStyleSetting alignmentStyle;
alignmentStyle.spec = kCTParagraphStyleSpecifierAlignment;//对齐属性
alignmentStyle.valueSize = sizeof(alignment);
alignmentStyle.value = &alignment;

// 样式2:行间距
CGFloat lineSpaceMax = 4.0f;
CTParagraphStyleSetting lineSpaceStyleMax;
lineSpaceStyleMax.spec = kCTParagraphStyleSpecifierMaximumLineSpacing;//最大行间距属性
lineSpaceStyleMax.valueSize = sizeof(lineSpaceMax);
lineSpaceStyleMax.value = &lineSpaceMax;

CGFloat lineSpaceMin = 1.0f;
CTParagraphStyleSetting lineSpaceStyleMin;
lineSpaceStyleMin.spec = kCTParagraphStyleSpecifierMinimumLineSpacing;//最小行间距属性
lineSpaceStyleMin.valueSize = sizeof(lineSpaceMin);
lineSpaceStyleMin.value = &lineSpaceMin;

CGFloat lineSpaceAdjust = 2.0f;
CTParagraphStyleSetting lineSpaceStyleAdjust;
lineSpaceStyleAdjust.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
lineSpaceStyleAdjust.valueSize = sizeof(lineSpaceAdjust);
lineSpaceStyleAdjust.value = &lineSpaceAdjust;

// 样式3:最大行高
CGFloat lineHeightMax = 18.0f;
CTParagraphStyleSetting lineHeightMaxStyle;
lineHeightMaxStyle.spec = kCTParagraphStyleSpecifierMaximumLineHeight;//最大行高属性
lineHeightMaxStyle.valueSize = sizeof(lineHeightMax);
lineHeightMaxStyle.value = &lineHeightMax;

// 样式数组
CTParagraphStyleSetting settings[]={
    alignmentStyle, lineSpaceStyleMax, lineSpaceStyleMin, lineSpaceStyleAdjust, lineHeightMaxStyle
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, 5);
[attriStr addAttribute:(id)kCTParagraphStyleAttributeName
                 value:(__bridge id)paragraphStyle
                 range:NSMakeRange(0, [attriStr length])];
CFRelease(paragraphStyle);

// Emoji、中文、英文混排
NSDictionary *fontAttributes = @{
    (id)kCTFontFamilyNameAttribute  : @"Helvetica",
    (id)kCTFontCascadeListAttribute : @[
        (__bridge id)CTFontDescriptorCreateWithNameAndSize(CFSTR("AppleColorEmoji"), 0),
        (__bridge id)CTFontDescriptorCreateWithNameAndSize(CFSTR("ZapfDingbatsITC"), 0),
    ]
};
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)(fontAttributes));
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, FONT_SIZE, 0);

[attriStr addAttribute:(id)kCTFontAttributeName
                 value:(__bridge id)font
                 range:NSMakeRange(0, [attriStr length])];
CFRelease(font);

// 字体颜色
[attriStr addAttribute:(id)kCTForegroundColorAttributeName
                 value:(id)FONT_COLOR.CGColor
                 range:NSMakeRange(0, [attriStr length])];

OS X 下用 IPFW 作端口转发

用 ipfw 监听本地 80 端口然后转发到 8080 等端口,方便本地开发时调试操作。ipfw 是 OS X 自带的防火墙程序,类似 Linux 下的 iptables。

  1. 查看当前 ipfw 规则:

sudo ipfw show

  1. port 80 to 8080 forward:

sudo ipfw add 100 fwd 127.0.0.1,8080 tcp from any to any 80 in

  1. 清除 ipfw 规则

sudo ipfw flush

Done.

via Port Forwarding (80 to 8080 for Tomcat) Using IPFW on Mac OSX

[self review:2012];

2012

2012 年度个人总结。先对一下去年计划:

工作

加强 iOS 开发,尤其是新技术的学习和整理,比如 ARC。

Done.

尝试 iPad 开发,这可不是简单的界面放大,整个用户交互都需要重新学习理解。

Done.

服务端开发,至少是应用级别要跟上,已经落户一步,不能拖太久。

FAIL

继续加强产品能力,尤其是整体把握。

Fail.

生活

完婚。

DONE.

给家里更多的帮助,让爸妈轻松一点。

Done.

希望今年能有一次旅游,厦门?

Done. 上半年的张家界+年底的版纳旅游。

每项按十五分的话刚刚过 70,及格分。


工作

今年在工作上做了一些侧重,尤其是下半年,更多偏向于技术开发。自己在产品、交互等方面并不特别擅长,可能偶尔会有一些灵感飞过,但整体上的把控力还很欠缺,细节不够严谨。所以从下半年开始,更多的重心放在技术上,code review,产品代码质量把控上。我不知道这种侧重好还是不好,对自己以后的职业走向是利还是弊,暂时来说我想先把技术能力再提高一下。团队也有很多参与产品的机会,所以产品方面还可以继续锻炼。

侧重技术并不是自己技术有多牛,相反,越做越发现自己还有太多太多不懂的东西。有不懂才会有收获,今年工作上最多的收获来自 iOS 开发有了一些新的技术学习和积累。iOS 开发是一个进化非常快的领域,保持积极的学习状态很重要。ARC、GCD、Blocks、Core Data、AFNetworking、CocoaPods、UITableView 以及 App 的性能优化等等,这些技术点都遇到了很多坑,交了学费,但是对自己的技术成长很有好处。问题的解决和技术学习都留了一些笔记,好记性不如烂笔头。

相较于客户端开发有了一些积累,服务端开发今年没有任何进步,这一点很失望。主要原因是自己太懒散,缺乏动手去做,没有实践就不能发现问题,没有问题连学费都没得交,怎么可能有进步。所以新的一年里服务端开发是自己要着重加强的,不管什么一定要动手去做。

生活

我们结婚了,这就是今年的最大的成就。罗列好需要做的工作,然后两个人一起努力各个击破,这种感觉很不错,不啃老,我们做到了。爸妈牵挂我们在外的漂泊,我们更心疼他们在家的辛苦。让爸妈的压力不那么大是我们最想要的,为此我们会尽自己最大的努力去分担。

我还在坚持着,努力着,在外漂泊的不安定感却一直都在。这种感觉从 12 岁出去念初中开始,只有宿舍没有家。还好,我们现在两个人在一起,我不坚强的时候有你,你不坚强的时候有我,该哭的时候哭了,该笑的时候笑了。

2013

工作

  1. iDev 深入,比如 runtime,自动化测试等,尝试一下 OS X 开发。
  2. 服务端开发学习。
  3. 学一门新语言,Lua/Go。

生活

  1. 学车考驾照。
  2. 健身锻炼。

Hidden Features in osx.plugin.zsh

oh-my-zsh 自带了很多插件,其中 osx.plugin.zsh 里有不少好东西。

  1. cdf 快速在终端打开当前 Finder 所在目录。之前的方法是用 cdto,缺点是会另外开一个终端窗口;或者鼠标拖动目录到终端再 cd。cdf 就省力很多,也是这个插件最喜欢的一个命令。
  2. trash 替换 rm,文件被移动到废纸篓而不是真正删除,避免误操作。alias rm='trash'
  3. pfd/pfs 打印当前 Finder 所在目录,一般配合 cdf 来用。
  4. pushdf pushd 寄存当前 Finder 所在目录。
  5. quick-look 调用 QuickLook 查看文件,配合 QLMarkdown.qlgenerator 快速预览 Markdown 文件。
  6. man-preview 把 man 信息生成 pdf 然后用预览打开。
  7. tab/split_tab/vsplit_tab 新建、切割终端 tab,不太习惯切割终端,这个用的很少。

Setup Octopress from existing repo

从已有的 Octopress repository 重新配置 GitHub Pages 托管博客,比如换了电脑却没有备份原来的设置。要求 source 分支已 push。

git clone [email protected]:fannheyward/fannheyward.github.com.git blog
cd blog
git checkout --track origin/source
# setup ruby with rbevn or rvm
gem install bundler
bundle install
rake gen_deploy # in order to create _deploy dir
# setup blog branch
cd _deploy/
git init
git add .
git commit -m "new setup."
git remote add origin [email protected]:fannheyward/fannheyward.github.com.git
cd ..
rake deploy

其实就是做了一系列的 git 操作,设置 repo,branch 等,熟悉 git 很容易搞定。

New Mac Setup

换了 SSD,重新配置了开发环境,简单留个笔记。

  1. Mac App Store 下载 Xcode,安装 Command Line Tools 方便编译。MAS 下载的一个好处就是后续可以增量更新。
  2. 安装 Homebrew,通过 brew 安装管理其他工具 git,zsh,MacVim,redis,PostgreSQL 等。
  3. 配置 oh-my-zsh,懒人必备。
  4. clone back dotfils from GitHub. 每个人都应该在 GitHub 等托管一份自己的配置文件,DRY。
  5. brew install rbenv ruby-build,主要是给 Octopress 用,之前用 rvm 过于庞大复杂,rbenv 就简单不少。
  6. 配置 Python virtualenv 环境,easy_install pip and use pip to install others.

换硬盘就显示了云存储的方便。Firefox Sync 很快就是自己顺手的浏览器,Dropbox 同步 nvALT 笔记,Alfred 等软件配置,只需一个账号你的数据、习惯随手就来,这也就是 Chromoe OS 带来的未来。

TP-Link WR941N 刷 OpenWrt

硬件版本 TP-Link WR941N V4/V5 00000000,软件版本 3.11.7 build 100723,从 OpenWrt trunk 下载对应固件 openwrt-ar71xx-generic-tl-wr941nd-v4-squashfs-factory.bin。其他型号参考 Table of Hardware 下载固件。

登录路由器升级固件,待路由自动重启后 telnet 192.168.1.1 连上路由器,passwd 设置密码,之后就可以通过 ssh [email protected] 登录路由器。

OpenWrt 默认没有开启无线网络,参考 TP-Link TL-WR941ND 手动修改 vi /etc/config/wireless: (修改之前最好备份一下配置文件)

config wifi-device  radio0
        option type     mac80211
        option channel  11
        option hwmode   11ng
        option path     'pci0000:00/0000:00:00.0'
        option htmode   HT20
        list ht_capab   SHORT-GI-40
        list ht_capab   TX-STBC
        list ht_capab   RX-STBC1
        list ht_capab   DSSS_CCK-40
        # REMOVE THIS LINE TO ENABLE WIFI:
        # option disabled 1 (删除或注释这一行)

config wifi-iface
        option device   radio0
        option network  lan
        option mode     ap
        option ssid     OpenWrt
        option encryption psk (默认没有加密,修改为 psk)
        option key      xxxxxxxxx

重启路由网络:

/etc/init.d/network restart

配置 PPPoE,vi /etc/config/network:

config interface 'wan'
        option ifname 'eth1'
        option proto pppoe
        option username 1234567
        option password 7654321
        option macaddr xx:xx:xx:xx:xx:xx (克隆路由器网卡地址)

再次重启路由网络进行拨号。

安装 LuCI web 界面,这样就可以在浏览器操作路由:

opkg update
opkg install luci
/etc/init.d/uhttpd enable
/etc/init.d/uhttpd start

现在就可以通过 http://192.168.1.1 修改路由器配置。

如果网络修改失败不能 ssh 登录,可以进入安全模式恢复:

路由上电时,灯会全亮一下,这时你要全神贯注了,当sys灯再次亮时,要立刻按reset2-3秒,然后你就会发现sys灯快闪了。这说明,安全模式成功了!

本机 IP 改为 192.168.1.5,telnet 192.168.1.1 连上,然后 firstboot 恢复。

Get declared property for object

获取对象的 property 属性列表:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

sample, via Declared Properties:

#import <objc/runtime.h>

id LenderClass = objc_getClass("Lender");

unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

for (i = 0; i < outCount; i++)
{
    objc_property_t property = properties[i];
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
free(properties);

拿到 property 列表就可以很方便做一些东西,比如 autodescribe,根据列表取值然后组装成对象 description。再比如配合 NSCoder 做 NSObject 的序列化

- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init]
    if (self)
    {
        Class clazz = [self class];
        NSUInteger count;

        objc_property_t *properties = class_copyPropertyList(clazz, &count);
        NSMutableArray *propertyArray = [NSMutableArray arrayWithCapacity:count];
        for (int i = 0; i < count ; i++)
        {
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
        }
        free(properties);

        for (NSString *name in propertyArray)
        {
            id value = [decoder decodeObjectForKey:name];
            [self setValue:value forKey:name];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
    Class clazz = [self class];
    NSUInteger count;

    objc_property_t *properties = class_copyPropertyList(clazz, &count);
    NSMutableArray *propertyArray = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count ; i++)
    {
        objc_property_t property = properties[i];
        const char *propertyName = property_getName(property);
        [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
    }
    free(properties);

    for (NSString *name in propertyArray)
    {
        id value = [self valueForKey:name];
        [coder encodeObject:value forKey:name];
    }
}

也可以拿到成员变量列表: class_copyIvarList(Class cls, unsigned int *outCount). Objective-C Runtime 有很多东西可以学习的。

Modern iOS Development

iOS 开发是一个进化非常快的技术领域,每年一更新的 iOS SDK 都会带来很多新东西,所以如果你现在用着和一年前一样的 code 做产品,虽然功能上没有差别,但是从技术上来说自身的成长进步非常有限。简单总结一下现在比较 modern 的开发方式。(截至 2012 年底)

ARC

iOS SDK 5 引进来的 ARC 已经非常成熟,是时候用了。ARC 可以大大减少各种不小心造成的内存泄漏,减少各种费脑子的内存问题 debug,这时候再手动内存管理完全是给自己增加工作。现在主流第三方库都已经 ARC ready 了,迁移成本很小。

Blocks

并不是说 delegate 有多不好,用 blocks 封装的接口使用起来非常轻便,尤其是网络请求等需要异步操作的时候,简单明了。

New Objective-C Literals

参考 New Objective-C Literals:

NSInteger _appid = 12345;
NSArray *array = @[ @"title", @(_appid)];

NSString *title1 = array[0];
array[0] = @"newTitle";

NSDictionary *dict = @{
    @"appid" : @(_appid),
    @"title" : _title,
};

NSString *title2 = dict[@"title"];

NSNumber *intNum = @123;
NSNumber *floatNum = @1.23f;
NSNumber *boolNum = @YES;

掌握新语法并不能说明技术能力有多高,但可以减少很多体力劳动,不需要敲很多 objectAtIndex: objectForKey:,代码结构也更为清晰。

@Synthesize by Default

以前:

@interface Person : NSObject
{
    NSString *_name;
}

@property (nonatomic, strong) NSString *name;

@end

@implementation Person

@synthesize name = _name;

@end

现在:

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;

@end

@implementation Person

@end

Xcode 4.4+ 会自动做 @synthesize,成员变量都可以不用手动声明,直接下划线开头 _var 形式。一来节省代码量,二来鼓励用 property,Always use accessor methods, Except in initializer methods and dealloc.,保证健壮性。

Modern Library and Tools

iDev 免不了要用到很多第三方库,这时候最好选用那些较新且成熟的库,是否支持 ARC 等。比如 AFN vs ASI,我个人非常喜欢 AFN 的设计,简单方便易扩展。

第三方库多了管理就是问题,现在有了 CocoaPods 一切变得都很简单,团队之间的分享协作也会方便很多,不会出现两边因为公共库版本不一致带来 bug 问题。

参考:

  • WWDC 2012 Session 405 - Modern Objective-C
  • WWDC 2012 Session 413 - Migrating to Modern Objective-C.