Key Value Coding and Key Value Observing Notes

KVO(Key Value Obersving) 的基础是 KVC(Key Value Coding),现在我对 KVC 的理解还非常粗浅,对 KVO 只是使用阶段,下面这些是我的一些笔记,可能会有一些误差,后续有更多理解后会持续更新。

KVC

KVC 的全称是 NSKeyValueCoding,文档:

The NSKeyValueCoding informal protocol defines a mechanism by which you can access the properties of an object indirectly by name (or key), rather than directly through invocation of an accessor method or as instance variables.

KVC 是一种通过 name 或 key 间接访问对象属性(property)的机制。用 setValue:forKey: 设置 key 所指定属性的值,valueForKey: 对应来取值。

Code:

@interface Person : NSObject

@property (nonatomic, retain) NSString *name;
@property (nonatomic) int age;

@end

@implementation Person

@synthesize name;
@synthesize age;

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.name = @"fannheyward";
        // equal to
        //[person setName:@"fannheyward"];
        // equal to
        //[person setValue:@"fannheyward" forKey:@"name"];

        person.age = 24;
        // equal to
        //[person setAge:24];
        // equal to
        //[person setValue:[NSNumber numberWithInt:24] forKey:@"age"];

        NSLog(@"name:%@, age:%d", person.name, person.age);
        NSLog(@"name:%@, age:%d", [person valueForKey:@"name"], [[person valueForKey:@"age"] intValue]);

        [person release];
    }
}

KVO

KVO 的全称是 NSKeyValueObserving,文档:

The NSKeyValueObserving (KVO) informal protocol defines a mechanism that allows objects to be notified of changes to the specified properties of other objects.

简单的说 KVO 提供了一个观察者机制,当被观察的对象属性变化时自动通知相应的观察者对象。KVO 就是通过 KVC 的 setValue:forKeyvalueForKey: 来监察属性变化。KVO 的使用分为三个步骤,注册观察者,实现变化回调方法,取消观察者。

注册观察者:

- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context

其中 keyPath 就是要观察的属性值,options 是属性变化的选择,context 可以用来传递额外的数据等。

实现变化回调方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

keyPath 注册里的相对应,用来区分不同的被观察属性,change 包含了变化前后的数据。

取消观察者:

- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath

一般放在 - (void)dealloc 方法里面。

Demo

Demo:一个列表 listTableView,数据存储在 NSMutableArray *cellArray 里面,cellArray 的数据发生变化时候刷新列表展示,比如滑动到 table 下部时自动后台 load 更多数据然后更新列表。

添加一个 @property (nonatomic) NSInteger cellCount 作为被观察者,为啥不直接用 cellArray作为被观察者?因为 NSArray 不能被注册为观察者,参考 NSArray(NSKeyValueObserverRegistration) in NSKeyValueObserving.h

NSArrays are not observable, so these methods raise exceptions when invoked on NSArrays. Instead of observing an array, observe the ordered to-many relationship for which the array is the collection of related objects.

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self addObserver:self forKeyPath:@"cellCount" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)loadCellArrayInBackground
{
    // ...
    self.cellCount = [cellArray count]; //change cellCount value.
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"cellCount"]) {
        // check change if nessary.
        [listTableView reloadData];
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"cellCount"];

    [super dealloc];
}

以上就是一个简单的 KVO 实现。


PS. NSArray 也有方法注册观察者,[[self mutableArrayValueForKey:@"cellArray"] addObject:@"test"]; 就可以被观察到 cellArray 变化。mutableArrayValueForKey 返回的不是 cellArray 本身而是一个 proxy,而 proxy 是支持 KVO 的。不过这种方法感觉有点浪费,没必要每添加、删除一个数据就刷新列表,尤其大部分时候我们都是批量更新数据源,用 cellCount 这种方式反而会更好一点。参考 Key Value Observing NSArray and NSDictionary


PPS. 其实 KVO 的实现是比较笨重的,比如注册时候没有办法指定一个响应 selector,都需要在回调实现里面根据 keyPath 来区分不同的被观察者。Key-Value Observing Done Right 分析了 KVO 的一些缺点并给出了解决方案: MAKVONotificationCenter,找个机会在项目中实际运用一下。

Track iOS Device Model with Google Analytics Custom Variables

Add Google Analytics SDK for iOS to app project first.

#import <sys/utsname.h>

- (NSString*)deviceModel {
    struct utsname systemInfo;
    uname(&systemInfo);

    return [NSString stringWithCString:systemInfo.machine
                              encoding:NSUTF8StringEncoding];
}

NSString *model = [self deviceModel];
// model = @"iPhone3,1", as iPhone 4.
[[GANTracker sharedTracker] setCustomVariableAtIndex:1 //range from 1-5, not be re-used.
                                                name:@"DeviceModel"
                                               value:model
                                               scope:kGANSessionScope
                                           withError:NULL];

NSString *sysVersion = [[UIDevice currentDevice] systemVersion];
// sysVersion = @"5.0.1"
[[GANTracker sharedTracker] setCustomVariableAtIndex:2
                                                name:@"SystemVersion"
                                               value:sysVersion
                                               scope:kGANSessionScope
                                           withError:NULL];

Then, you can get the report on Google Analytics Audience / Demographics / Custom Variables.

第一次产品小结:不及格

第一次自己作为产品负责人带产品,自评不及格。总结一下,以后这样的机会还会很多,积累经验争取下次做的更好。

  1. 开发进度太慢。原本计划一周时间出原型,但是拖了两个星期才发了第一个测试版本,delay 太多了。自己负责的开发部分拖累了整个项目进展,这一点必须反思。
  2. 进度慢的一个原因是部分模块沿用之前项目的东西,花了很多时间去理解梳理然后添加到现在项目,但是由于两个项目架构等有很多不同,复用的成本太大。自己作为项目负责人,选型时候没有考虑清楚,引入后影响项目进度,却没有及时的拿出修正方案。教训一:不通用的模块复用的成本远远大于重写一份,切不可为了一时的快而忽视其他问题
  3. 另一个原因是在项目基本成型的时候,由于数据库一个设计缺陷,虽不影响当前使用,但很不利于以后的产品拓展,就重构了一下。这个相比上面虽然有了解决方案,但是延期是事实。究其原因还是在开发前期没有很好的考虑清楚,只解决了第一需求就立马开工,后期造成返工。教训二:产品设计,尤其是开发设计,架构方面要考虑清楚,要有一定的可扩展性,避免以后返工
  4. 作为产品负责人,忽略了开发以外的很多事情。比如设计方面,只是和设计师确定了基本界面和交互流程,然后一股脑的扎进开发去,加上开发跑偏,花在设计上的心思就更少了。还好旁边有人提醒着,设计方面这次没有拖累项目进展,但由于和设计师沟通交流太少,一些产品概念没有准确的传递给设计师,造成一些细节上不够完美。教训三:作为产品负责人,要对整个产品线全部部分掌握到把控到,并且准确的传递给相关人员
  5. 作为产品负责人,要 主动 和项目中的其他人员多沟通,一方面把产品传递给其他人,更重要的是要收集汇总其他人员对产品的概念和想法,然后迭代修正不完善的地方,再一个可以及时了解其他人的进度和遇到的问题,以便整体安排。
  6. 这次产品调研阶段确定了不少功能点要做,但是没有完全定型 当前版本 要做的点,这就造成一方面有东西没有做完,产品不能成型,另一方面却花了不少时间做了一些可以后续再增强的东西,造成时间的浪费。教训四:要控制产品功能的阶段性,必要时可以砍掉一些功能点来保证当前版本上线,然后再快速迭代产品
  7. 对开发分工和实际进度掌握不好。不是分工后就完全分工不管了,如果有人负责的部分提前完工,可以再对未完成的部分分工一下,虽然可能会增加一些人的负担然后引发其他问题,但是从整个产品进展来看,这样做无可厚非。
  8. 技术人做产品会有惯性思维,考虑问题过多从开发角度思考,这样的结果一是产品讨论时过于关注开发实现,产品比较僵硬;二是后续比较多的心思都花在了开发上,整体把握不足,引发上面两个问题。教训五:做产品的时候要跳出开发这个圈圈,这样才能碰撞出更多点子;要平衡好产品和开发

现在回过头来想想,自己这次产品做的真不咋样,好好反思一下,希望下次能有更好的表现。


题外话。

  1. 过于追求完美是做产品的大忌。
  2. 不要因为自己的错误影响别人的利益。
  3. Deadlines kill quality。

Grand Central Dispatch Sample

说来惭愧,做 iDev 一年多了,最近才第一次在正式项目中使用 GCD。做个笔记。

Grand Central Dispatch(GCD) 是苹果 iOS 4 推出的任务调度机制,把不同的任务分配给不同的 queue 来处理,非常适合异步任务,支持多核处理器,比 performSelectorInBackground 这种线程调度有更好的处理性能,而且配合 Blocks 使用非常方便。

dispatch_queue_t bgQueue = dispatch_queue_create("im.fann.bgQueue", NULL);
// or dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// or dispatch_queue_t bgQueue = dispatch_get_global_queue(0, 0,); for short

dispatch_async(bgQueue, ^{

    ...// load data from server

    dispatch_async(dispatch_get_main_queue, ^{
        // dispatch_get_main_queue get back to the main queue to update UI. You can only change UI in main queue.
        [rootTableView reloadData];
    });
});

非常棒的 GCD 系列教程:

Happy Birthday to my Blog

四年前的今天写了 Hello World!,希望能一直写下去。

Happy Birthday.

iOS 模拟器界面调试小工具

不知道 Xcode 什么时候添加进来的功能,在模拟器 Debug 菜单下选中 Color Blended Layers 可以很方便的查看界面元素排版布局。

BTW, 现在在模拟器可以直接 Command+S 来截图了。

iOS 5 Settings URL scheme

在 iOS 5+ 可以通过 URL scheme 快速打开【设置】及子项。

Settings                prefs:
About                   prefs:root=General&path=About
Accessibility           prefs:root=General&path=ACCESSIBILITY
Airplane Mode On        prefs:root=AIRPLANE_MODE
Auto-Lock               prefs:root=General&path=AUTOLOCK
Brightness              prefs:root=Brightness
Bluetooth               prefs:root=General&path=Bluetooth
Date & Time             prefs:root=General&path=DATE_AND_TIME
FaceTime                prefs:root=FACETIME
General                 prefs:root=General
Keyboard                prefs:root=General&path=Keyboard
iCloud                  prefs:root=CASTLE
iCloud Storage & Backup prefs:root=CASTLE&path=STORAGE_AND_BACKUP
International           prefs:root=General&path=INTERNATIONAL
Location Services       prefs:root=LOCATION_SERVICES
Music                   prefs:root=MUSIC
Music Equalizer         prefs:root=MUSIC&path=EQ
Music Volume Limit      prefs:root=MUSIC&path=VolumeLimit
Network                 prefs:root=General&path=Network
Nike + iPod             prefs:root=NIKE_PLUS_IPOD
Notes                   prefs:root=NOTES
Notification            prefs:root=NOTIFICATIONS_ID
Phone                   prefs:root=Phone
Photos                  prefs:root=Photos
Profile                 prefs:root=General&path=ManagedConfigurationList
Reset                   prefs:root=General&path=Reset
Safari                  prefs:root=Safari
Siri                    prefs:root=General&path=Assistant
Sounds                  prefs:root=Sounds
Software Update         prefs:root=General&path=SOFTWARE_UPDATE_LINK
Store                   prefs:root=STORE
Twitter                 prefs:root=TWITTER
Usage                   prefs:root=General&path=USAGE
VPN                     prefs:root=General&path=Network/VPN
Wallpaper               prefs:root=Wallpaper
Wi-Fi                   prefs:root=WIFI

via Apple Settings App/Preference Shortcuts

Setup Mac Development Environment

  1. System Software Update.
  2. Download and install Xcode, or Command Line Tools for Xcode only if you don’t need Xcode.
  3. Install Homebrew.
  4. Get back your dotfiles if you have.

Python

  1. Use pip instead of easy_install
  2. Use pip to install virtualenv and virtualenvwrapper
# use easy_install to install pip
sudo easy_install pip
# where pip
/usr/local/bin/pip
# virtualenv
sudo pip install virtualenv
# which virtualenv
/usr/local/bin/virtualenv
# virtualenvwrapper
sudo pip install virtualenvwrapper
# virtualenvwrapper will be installed in /usr/local/bin/virtualenvwrapper.sh
# config virtualenvwrapper
mkdir ~/.virtualenvs
# edit .zshrc and add
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

Ruby

Install RVM

# add to .zshrc
[[ -s $HOME/.rvm/scripts/rvm ]] && source $HOME/.rvm/scripts/rvm
# install ruby by rvm
rvm install 1.9.2 && rvm use 1.9.2
rvm rubygems latest

Compile and install SVN 1.7.2 on Mac

Just a note for myself.

  1. Download svn-1.7.2.tar.gz source.
  2. Run ./autogen.sh to check the necessary components to build svn.
  3. ./configure then make and sudo make install.

All commands:

./autogen.sh
./configure --disable-debug --with-ssl --with-zlib=/usr --with-sqlite=/usr --disable-neon-version-check --disable-mod-activation --without-apache-libexecdir --without-berkeley-db --with-neon=/usr/local/Cellar/neon/0.29.6/
make
sudo make install

加速 Xcode 文档搜索

Xcode 的文档搜索速度实在是不给力,因为 Xcode 是实时的索引所有 Doc Sets 来查找。解决方法:

  1. 更换 SSD,一劳永逸,更能带来编译速度的极大提升。
  2. 第三方文档搜索工具,比如 Ingredients、AppKiDo,缺点是没法和 Xcode 完美结合,比如 Option+Click 快速查找。
  3. 修改 Find Options 来减少一些索引,只做 iOS 就没必要选 Mac 的 Doc Sets. 另外 Match Type 选 Prefix 也会快很多。可以参考下面这个 Find Options 设置。

感谢 @jjgod 分享的小技巧。