0%

在这篇文章中,我会实现一个自己用的简单KVO类,我认为KVO非常棒,然而对于我大部分的使用场景来说,有这两个问题:

  1. 我不喜欢在observeValueForKeyPath:ofObject:change:context:方法里通过keyPath值来做调度,当Observe比较多的对象时,会使得代码变得杂乱和迷惑。
  2. 必须手动的来注册和删除一个观察者,如果能自动做就好了。

So,我们开始这个实现。这个技巧我第一次是在THObserversAndBinders项目中见到,本篇内容也仅仅描述了一下里面的做法,同时做了简化。

首先,我们定义一下我们的这个类,我们这个帮助类的类名是Observer:

1
2
3
4
5
6
@interface Observer : NSObject
+ (instancetype)observerWithObject:(id)object
keyPath:(NSString*)keyPath
target:(id)target
selector:(SEL)selector;
@end

Observer类的这个类方法有四个参数,每个参数都是自解释的,我选择使用target/action模式,当然也可以使用block,但是那样的话需要做weakSelf/strongSelf的转换,你懂的,通常来说分来来做比较好。

我们做的是在初始化方法中设置KVO,并在dealloc方法中移除。这意味着一旦Observer对象被retain,我们就有了一个观察者,下面这段代码是从我的一个ViewCOntroller中拿来的:

1
2
3
4
self.usernameObserver = [Observer observerWithObject:self.user
keyPath:@"name"
target:self
selector:@selector(usernameChanged)];

把这个Observer对象作为一个属性放在ViewController中来保证被retain,一旦我们的Viewcontroller被释放,就会设置它为nil,observer就停止观察了。

在这个实现中,使用一个weak引用指向被观察对象和观察者(target)是很重要的,如果两个中的其中一个是nil,我们就停止向观察者发送消息。

1
2
3
4
5
6
@interface Observer ()
@property (nonatomic, weak) id target;
@property (nonatomic) SEL selector;
@property (nonatomic, weak) id observedObject;
@property (nonatomic, copy) NSString* keyPath;
@end

初始化器里设置KVO通知,使用self作为context,如果我们会有一个子类也添加类似的观察者时就很有必要了。

1
2
3
4
5
6
7
8
9
10
11
- (id)initWithObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector
{
if (self) {
self.target = target;
self.selector = selector;
self.observedObject = object;
self.keyPath = keyPath;
[object addObserver:self forKeyPath:keyPath options:0 context:self];
}
return self;
}

一旦被观察者发生变化,我们就通知观察者(target),如果它还存在的话:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
if (context == self) {
id strongTarget = self.target;
if ([strongTarget respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[strongTarget performSelector:self.selector];
#pragma clang diagnostic pop
}
}
}

最后在dealloc方法中移除观察者对象:

1
2
3
4
5
6
7
- (void)dealloc
{
id strongObservedObject = self.observedObject;
if (strongObservedObject) {
[strongObservedObject removeObserver:self forKeyPath:self.keyPath];
}
}

这就是全部内容了。还有很多可以扩展的地方,比如增加block的支持,或者我比较喜欢的trick:再增加爱一个方便的构造方法用来第一次直接调用action。然而,我想的是展现出这个技术的核心部分,你可以根据自己的需求来调整它。

这个技术的优点是在使用KVO的时候不需要记住太多东西,仅仅retain住Observer对象,然后在完成的试试置为nil即可,剩下的会自动完成。

原文作者是Chris Eidhofobjc.io的创办者
原文地址:Lightweight Key-Value Observing

昨晚花了2个小时熟悉了一下AppCode,和IDEA系列给人的感觉一样:很卡很强大。就打算优化一下JVM的设置,AppCode的JVM参数配置文件在 /Applications/AppCode EAP.app/bin/idea.vmoptions

使用默认的参数,用一段AppCode,观察了一下GC的情况:

➜  ~  jstat -gcutil 50991 1s
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
 79.31   0.00  37.61  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.63  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.65  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.66  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.67  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.69  88.64  60.84   6654   57.031   137    3.017   60.048
 79.31   0.00  37.70  88.64  60.84   6654   57.031   137    3.017   60.048

发现YoungGC有6654次,耗时57s,FullGC有137次,3s多,花在GC上的总时间有60s,按每次卡一次1s来算,单是GC就让人感觉到60次明显卡顿,确实让人受不了。

查了一下默认的参数,内存设置的太保守,所以我改成了下面这个方案:
我的机子是8G内存,给AppCode分配1500M,如果你的是4G内存,建议把-Xms1500m-Xmx1500m都调成1000m,-XX:NewSize=600m-XX:MaxNewSize=600m改为400M。修改之前把idea.vmoptions文件备份一下,以防万一。

-Xms1500m
-Xmx1500m
-XX:NewSize=600m  
-XX:MaxNewSize=600m
-XX:SurvivorRatio=8
-XX:PermSize=200m
-XX:MaxPermSize=400m
-XX:ReservedCodeCacheSize=96m
-XX:+UseCompressedOops
-XX:+DisableExplicitGC

使用后:

➜  jstat -gcutil 58835 1s

  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066
 61.70   0.00  48.84  15.60  52.92     12    1.066     0    0.000    1.066

YGC降低到了12次,GC时间是1s,没有FullGC,没有感觉到卡顿的情况。

这个主要是从内存分配方面优化,GC算法上也可以优化,但是需要多测试每种GC算法的情况,也可能会因人而异,等我慢慢找到一个不错的方案再分享出来。

至于上面参数的意思,可以查看我在iteye上以前的一篇Blog:10s启动MyEclipse/Eclipse的JVM参数(含Mac下)

最近干了件蠢事,事情是这样的,我们App有2套图标,一套是测试版图标用于发布OTA的内部测试版,一套是正式版用于发布到AppStore,每次打包,我都会检查图标,结果上次粗心搞错了,把测试版的图标打包发布到AppStore了,发现之后想死的心都有了。马上修改了一版,申请紧急审核,结果你可能猜到了,没有通过。这是个很大的教训,像这一类的手动来改都不靠谱,毕竟有忘掉的概率存在,能不能自动处理呢? 在这篇Blog上找到了答案,我大概的翻译一下。

iOS系统区分两个App是否相同的根据是App的Bundle ID是否相同,在安装一个程序时,系统是根据Bundle ID来判断是全新安装还是升级。那想在一个系统上安装一个App的两个不同版本,其实是需要两个不同的Bundle ID。就是说正式版一个Bundle ID,OTA版本/Debug版本用一个Bundle ID,假设AppStore版的ID是com.mycompany.myapp,OTA版的是com.mycompany.myapp-beta。同时为了直观的区分两个App,一般也会使用两套图标, 假设AppStore版的图标名称为Icon.png, Icon@2x.png, OTA版是Icon-beta.png, Icon-beta@2x.png. 那如果做到自动化的配置呢?答案在Build设置(Build Setting)里。

默认Xcode会提供2个Build配置(Build Configuration):DebugRelease,我们再加一个AppStore,这样来用:

  • Debug: 用来直接连机调试
  • Release:用于发布OTA的测试版
  • AppStore:用户提交到AppStore

下一步我们来在项目的Build Setting里添加两个自定义的设置,一个命名为BUNDLE_IDENTIFIER, 另一个命名为APP_ICON_NAME,如下图这样设置:

add_user_define_setting

这两个值分别定义个Bundle ID和图标的名称,下一步需要在Info.plist(名字格式是YourAppName-Info.plist)中修改BundleId 和Icon图标名称,把bundle identifier值设置为${BUNDLE_IDENTIFIER},把图标值设置为${APP_ICON_NAME}@2x.png${APP_ICON_NAME}.png,如果提供了72px和144px等图标也类似这样。

${xxx}语法是预处理语法,都会被替换为xxx对应的真实值,在刚才的设置的基础上,在Debug的时候,实际的Bundle ID会替换为com.mycompany.myapp-beta,图标对应的为Icon-beta.pngIcon-beta@2x.png,Cooool

实际上我自己实践的时候,新建了一个叫myApp-AppStoreSchema,在不同的Schema里的Archive里是用不同的Build配置,myApp-AppStore的Schema里Archive的Build配置为”AppStore”,原来的myApp这个Schema的Build配置为Release,这样当我想发布OTA的时候,选择myApp-AppStore这个Schema,然后Archive,就能使用AppStore的自定义的配置来打包,用来提交AppStore;当选择myApp这个Schema的时候,Archive得到的是使用Release的自定义配置来打包的,用来上传到OTA测试。整个过程是自动化的,包括BundleId和图标文件的名称,如果你有别的类似的需要,也可以参考着来。

总之,麻麻再也不用担心我的图标会搞错了。

这篇文章编译自:How to Have Two Versions of the Same App on Your Device ,原作者Blog上还有其他精彩的文章等你发现。

几乎每个开发者都知道,让App快速响应的秘诀是把耗时的计算丢到后台线异步去做。于是,Modern Objective-C开发者有两个选择:GCDNSOperation.

由于GCD已经发展的比较主流了,我们稍后再说它,先说说面向对象的NSOperation.

NSOperation表示一个单独的计算单元,它是一个抽象类(很类似Java里的Runnable接口),给子类提供了一些非常有用且线程安全的特性,比如状态(state),优先级(priority),依赖(dependencies)以及取消(cancellation). 如果你不想子类化NSOperation,可以选择使用NSBlockOperation这个NSOperation的子类,它可以把一个block包装成为一个NSOperation.

非常适合使用NSOperation的任务例子包括network requests, 图片的缩放,语言处理或者其他一些重复的、结构化的以及需要运行较长时间来处理数据的任务。

但是,仅仅把计算包装成一个对象,没有一些监管也不会非常的有用,这时NSOperationQueue就出现了。

NSOperationQueue控制各个operation的并发执行.它像是一个优先级队列,operation大致的会按FIFO的方式被执行,不过带有高优先级的会跳到低优先级前面被执行(用NSOperation的queuePriority方法来设置优先级)。 NSOperationQueue支持并发的执行operations,通过maxConcurrentOperationCount来指定最大并发数,就是同时有最多有多少个operation同时被运行。

可以通过调用-start方法来启动一个NSOperation,或者把它放到NSOperationQueue里,当到达队列最前端时也会被自动的执行。

现在来看看NSOperation的几个不同的特性,以及如何如果使用和子类化它:

状态 State

NSOperation构建了一个非常优雅的状态机来描述一个operation的执行过程:

isReady -> isExecuting -> isFinished

State是通过这些keypath的KVO通知来隐式的得到,而不是显式的通过一个state的属性。就是说,当一个operation已经准备就绪,将要被执行时,它会为isReadykeyPath发送一个KVO的通知,对应的属性值也会变为YES.

为了构造一致的状态,下面每个属性都与其他属性相互排斥:

  • isReady: 如果operation已经做好了执行的准备返回YES,如果它所依赖的操作存在一些未完成的初始化步骤则返回NO。
  • isExecuting:如果operation正在执行它的任务返回YES,否则返回NO。
  • isFinished: 任务成功的完成了执行,或者中途被Cancel,返回YES。NSOperationQueue只会把isFinished为YES的operation踢出队列,isFinished为NO的永远不会被移除,所以实现时一定要保证其正确性,避免死锁的情况发生。

取消 Cancellation

如果正在进行的operation所做的工作不再有意义,尽早的取消掉是非常有必要的。取消一个operation可以是显式的调用cancel方法,也可以是operation依赖的其他operation执行失败。

和state类似,当NSOperation的被取消,是通过isCancelledkeypath的KVO来获得。当NSOperation的子类覆写cancel方法时,注意清理掉内部分配的资源。特别注意的是,这时isCancelled和isFinished的值都变为了YES,isExecuting为值变为NO。

一个需要格外注意的地方是和单词“cancel”有关的两个词:

  • cancel : 带一个”l” 表示方法 (动词)
  • isCancelled : 带两个”l”表示属性(形容词)

优先级 Priority

所有的operation在NSOperationQueue中未必都是一样的重要,设置queuePriority属性就可以提升和降低operation的优先级,queuePriority属性可选的值如下:

  • NSOperationQueuePriorityVeryHigh
  • NSOperationQueuePriorityHigh
  • NSOperationQueuePriorityNormal
  • NSOperationQueuePriorityLow
  • NSOperationQueuePriorityVeryLow

另外,operation可以指定一个threadPriority值,它的取值范围是0.0到1.0,1.0代表最高的优先级。queuePriority决定执行顺序的优先级,threadPriority决定当operation开始执行之后分配的计算资源的多少。

依赖 Dependencies

取决于你的App的复杂性,可能会需要把一个大的任务分成多个子任务,这时NSOperation依赖就排上用场了。

比如从服务器上下载和缩放图片的过程,你可能会想把下载图片作为一个operation,缩放作为另外一个(这样也可以复用下载图片和缩放图片的代码)。然后,一个图片在从服务器上下载下来之前是没有办法缩放的,于是我们说缩放图片的operation依赖从服务器上下载图片的operation,后者必须先完成,前者才能开始执行。用代码表示是这样的:

1
2
3
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];

一个operation只有在它依赖的所有的operation的isFinished都为YES的时候才会开始执行。要记住添加到queue里的所有的operation的依赖关系,并避免循环依赖,比如A依赖B,B依赖A,这样会产生死锁。

completionBlock

completionBlock是在iOS4和Snow Leopard中添加的一个非常有用的特性。当一个NSOperation完成之后,就会精确地只执行一次completionBlock。我们需要在operation完成之后想做点什么的时候这个属性就会非常有用。比如当一个网络请求结束之后,可以在completionBlock里处理返回的数据。

总结

NSOperation依然是Modern Objective-C程序员杀手锏里的重要工具。相对于GCD非常适用于in-line的异步处理,NSOperation提供了更综合的、面向对象的计算模型,非常适用于封装结构化的数据,重复性的任务。把它加到你的下个项目中,给你的用户和你自己都带来乐趣吧!

译者注

本文编译自NSHipster里的NSOperation一文,感谢作者Mattt Thompson, 来头很大,这是他的简介:

Mattt Thompson is the Mobile Lead at Heroku, and the creator & maintainer of AFNetworking and other popular open-source projects, including Postgres.app & Induction. He also writes about obscure & overlooked parts of Cocoa on NSHipster.

最上面的图片是来自于WWDC2013中的“Hidden Gems in Cocoa and Cocoa Touch”(228)中Mattt讲NSOperation时的截图,这个视频一共有30个tips,这是第8个tip,大部分的内容我是第一次知道,非常值得看,而且如果有条件的话,建议下载HD版本的视频来看,效果比SD好太多。字幕文件在我的这个repo里, :)

如有文中有不准确的地方,欢迎留言指正 :)

Enjoy!

刚写iOS程序的时候就知道Xcode支持第三方插件,比如ColorSense等很实用的插件,但Xcode的插件开发没有官方的文档支持,一直觉得很神秘,那今天就来揭开它的面纱。

在Xcode启动的时候,它会检查插件目录(~/Library/Application Support/Developer/Shared/Xcode/Plug-ins)下所有的插件(扩展名为.xcplugin的bundle文件)并加载他们。其实到这里我们就猜到了,我们做的插件最终会是一个扩展名为.xcplugin的bundle文件,放在插件目录下供Xcode加载。

OK,我们先做一个简单的插件,需要很简单的几个步骤即可完成,我的环境是Xcode 4.6.3 (4H1503)。

1. 新创建一个Xcode Project

Xcode插件其实就是一个Mac OS X bundle,所以可以参考下图创建一个Bundle。
Image1 icon

给Project起个名字,并确保不要勾选Use automatic reference counting,因为Xcode是使用GC来管理内存的,所以Xcode的插件也需要是用GC来管理内存的。Framework选择Cocoa

Image2 icon

2. 设置Target Info

像下图一样设置这些信息

  • XC4Compatible = YES
  • XCPluginHasUI = NO
  • XCGCReady = YES
  • Principal Class = Plugin (这个设置为你插件的名字,本例中命名为Plugin)

前三个可能Info里缺省没有,可以自己添加,都选Boolean类型,最后一个Principal ClassString类型。
Image3 icon

3. 设置Build Settings

然后打开Build Setting Tab,设置这些:

  • 设置Installation Build Products Location${HOME},Xcode会自动转换为你当前用户的Home路径
  • 设置Installation Directory/Library/Application Support/Developer/Shared/Xcode/Plug-ins, Xcode会把拼接Installation Build Products LocationInstallation Directory为一个绝对路径来查找你的插件
  • 设置Deployment LocationYES
  • 设置Set Wrapper extensionxcplugin

Image4 icon
Image5 icon

4. 添加 User-Defined 设置

  • 设置GCC_ENABLE_OBJC_GCsupported
  • 设置GCC_MODEL_TUNINGG5

Image6 icon

有了这些设置,每次build这个Projct的时候,Xcode就会把build后的插件copy到plugin文件夹下,然后我们需要重启Xcode来重新加载新build的插件。开发插件相对来说简单一些,调试插件就比较纠结了,唯一的办法就是build之后,重启Xcode,来加载最新build的插件。

准备工作已经结束,下面开始实现我们的插件。

5. 实现我们的插件

在第二步的时候我们设置了一个Principal Class,那么在Xcode里新建Objective-C类,名字和Principal Class设置的值保持一致。在实现文件中添加上+ (void) pluginDidLoad: (NSBundle*) plugin方法。 该方法会在Xcode加载插件的时候被调用,可以用来做一些初始化的操作。通常这个类是一个单例,并Observe了NSApplicationDidFinishLaunchingNotification,用来获得Xcode加载完毕的通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (void) pluginDidLoad: (NSBundle*) plugin {
static id sharedPlugin = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedPlugin = [[self alloc] init];
});
}

- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidFinishLaunching:)
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
return self;
}

一旦接收到Xcode加载完毕的通知,就可以Observe需要的其他notification或者在菜单中添加菜单项或者访问Code Editor之类的UI组件。

在我们的这个简单例子中,我们就在Edit下添加一个叫做Custom Plugin的菜单项,并设置一个⌥ + c快捷键。它的功能是使用NSAlert显示出我们在代码编辑器中选中的文本。我们需要通过观察NSTextViewDidChangeSelectionNotification并访问接收参数中的NSTextView,来获得被选中的文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void) applicationDidFinishLaunching: (NSNotification*) notification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(selectionDidChange:)
name:NSTextViewDidChangeSelectionNotification
object:nil];

NSMenuItem* editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
if (editMenuItem) {
[[editMenuItem submenu] addItem:[NSMenuItem separatorItem]];

NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:@"Custom Plugin"
action:@selector(showMessageBox:)
keyEquivalent:@"c"];
[newMenuItem setTarget:self];
[newMenuItem setKeyEquivalentModifierMask: NSAlternateKeyMask];
[[editMenuItem submenu] addItem:newMenuItem];
[newMenuItem release];
}
}

- (void) selectionDidChange: (NSNotification*) notification {
if ([[notification object] isKindOfClass:[NSTextView class]]) {
NSTextView* textView = (NSTextView *)[notification object];

NSArray* selectedRanges = [textView selectedRanges];
if (selectedRanges.count==0) {
return;
}

NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
NSString* text = textView.textStorage.string;
selectedText = [text substringWithRange:selectedRange];
}
}

- (void) showMessageBox: (id) origin {
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: selectedText];
[alert runModal];
}

你会发现在出现selectedText的地方会报错,在实现里添加上NSString *selectedText即可。

1
2
3
@implementation Plugin {
NSString *selectedText;
}

最终效果:
Image7 icon

6. 需要注意的

  • Plugin不能使用ARC,需要手动管理好内存(谢谢@onevcat的提醒,因为是用GC,不需要手动管理内存了)
  • 不能直接Debug,不过可以在程序里通过NSLog打印出日志,并通过tail -f /var/log/system.log 命令来查看输出的日志
  • 如果Xcode突然启动不起来了,可能是插件有问题,跑去~/Library/Application Support/Developer/Shared/Xcode/Plug-ins目录下,把插件删掉,restart Xcode,查找问题在哪
  • 如果1-4步骤的各种设置你比较讨厌的话,可以直接用这个Xcode4 Plugin Template来搞定, 怎么使用在它的Readme中有详细的说明,:)

总结

这只是一个简单的Xcode插件的入门编写示例,不过“麻雀虽小,五脏俱全”,可以了解到Xcode的插件一些东西,比如Xcode插件本质上其实就是一个Mac OS X bundle等等,而且因为没有Apple官方的文档的支持,很多东西只能去Google,或者参考别人插件的一些实现。

REF

本文主要参考和编译自WRITING YOUR OWN XCODE 4 PLUGINS,感谢原作者Blacksmith Software


另:
前两天我们的小伙伴@onevcat写了一个Xcode插件VVDocumenter,作用是在方法、类等前面输入三个/就会自动生成规范的JavaDoc文档(Xcode5中将支持JavaDoc类型的文档,对于我这样从Java转过来的来说是真是雪中送炭),赶紧clone了一个,用起来很方便,很好很强大,强烈推荐! 赶紧把我们的项目代码文档化起来,迎接Xcode5的到来吧,:)

Enjoy!!!

之前在用Eclipse写Java的时候,有几个常用的快捷键,比如删除当前行,在当前行下面插入空行,向上/下移动当前行等等,到了Xcode里怎么也找不到这些快捷键,一直觉得Xcode自带的快捷键不够强大,直到今天才知道不借助第三方的插件,在Xcode下完全也可以实现这些功能,下面就说一下如何来做。

首先找到Xcode中的自带的配置文件
/Applications/Xcode.app/Contents/Frameworks/IDEKit.framework/Versions/A/Resources/IDETextKeyBindingSet.plist
这个文件里配置了一些可以设置快捷键的操作, 使用常用的编辑器打开它(需要root权限)。

然后看看下面这段配置, (来自gist,感谢作者@gdavis )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<key>GDI Commands</key>
<dict>
<key>GDI Duplicate Current Line</key>
<string>selectLine:, copy:, moveToEndOfLine:, insertNewline:, paste:, deleteBackward:</string>
<key>GDI Delete Current Line</key>
<string>moveToEndOfLine:, deleteToBeginningOfLine:, deleteBackward:, moveDown:, moveToEndOfLine:</string>
<key>GDI Move Current Line Up</key>
<string>selectLine:, cut:, moveUp:, moveToBeginningOfLine:, insertNewLine:, paste:, moveBackward:</string>
<key>GDI Move Current Line Down</key>
<string>selectLine:, cut:, moveDown:, moveToBeginningOfLine:, insertNewLine:, paste:, moveBackward:</string>
<key>GDI Insert Line Above</key>
<string>moveUp:, moveToEndOfLine:, insertNewline:</string>
<key>GDI Insert Line Below</key>
<string>moveToEndOfLine:, insertNewline:</string>
</dict>

这个dict是一组可以设置快捷键的操作,里面的key是名称,对应的string是对应的一组操作,从名字本身也可以看出是什么意思,而且也可以根据这些自由装配成自己的别的快捷操作。

  • GDI Duplicate Current Line 复制当前行到下面一行
  • GDI Delete Current Line 删除当前行
  • GDI Move Current Line Up 把当前行往上移动一行
  • GDI Move Current Line Down 把当前行往下移动一行
  • GDI Insert Line Above 在当前行上面增加一空行
  • GDI Insert Line Below 在当前行下面增加一空行(不管光标是否在行尾)

把这段配置放到上面提到的IDETextKeyBindingSet.plist里,放在文件的最后的这两行之前:

1
2
	</dict>
</plist>

重启Xcode,在Xcode菜单中,打开Preferences,选中Key Binding,在右上方搜索GDI, 会出现类似下图的显示,如果没有的话,请检查上面的每步操作。

img

双击右边的空白处,就可以为每个功能设置不同的快捷键,我设置和Eclipse里的一致,感受了下,非常爽,Cooool

Have fun!~

不卖关子,这是一个git repo ,可以从这里下载到WWDC 2013公开的100个视频的英文字幕。
如果觉得有用的话,不妨star一下,或者在微博上@我满足一下我的虚荣心 :-),这都不重要,重要的是一定要坚持看完这100个视频

我发起这个项目以及抓取到这些字幕的的原因是这样的,一个是英语的听力太差,基本上听不懂苹果的传道士们在视频中说的是什么,没有字幕真是很难受,然后是发现在iPad上使用WWDC这个App看视频的时候是有字幕的,但在iPad上看屏幕不够大,看起来也很费劲。就想既然在iPad上有字幕,一定有办法抓取出来,于是就开工,用burpsuite之类的抓Http请求包的App很容易就能探测到字幕文件的地址,在准备写代码的时候,Google了一下,发现一个python写的gist正是做这个的,于是就用这个脚本把一部分视频的字幕下载下来,自己又现学了点ruby写了个gist脚本来把分散的字幕文件按照顺序合并起来。 刚开始下载的比较慢,因为这个脚本是单线程的,后来自己改了一下,分10个线程,每个线程下载10个视频的字幕,这样就快很多,这个代码因为比较简单,就没放出来,有兴趣的童鞋自己也可以实现。

另外@lexrus同学的这个gist里提供了所有视频的HD和SD的版本,以及文件序号和视频名称的对应关系,可以直接放在迅雷里下载,完了再配上字幕,可以像欣赏好莱坞大片一样的欣赏WWDC2013带来的新技术盛宴了!

事情是这样的,前几天电脑崩溃,硬盘数据全部丢失,重装系统和Xcode之后,从Develop Center的Certificates里重新下载证书,安装到新电脑上,在真机上运行时,提示报错:”A valid signing identity matching this profile could not be found in your keychain”, 按照字面意思查了一下,是因为本地KeyChain里丢失了private key的缘故,得到的结论是要么从之前的备份中恢复,或者重新生成新的证书。遗憾的是我之前并没有保存备份,无奈只好重新生成。一个问题马上出现在脑海,就是重新生成新的证书是否会对线上的App产生影响。查了一下官方的文档,发现有这部分详细的说明,消除了我的顾虑,内容如下(参考2):

Important: Members of the Standard iOS Developer Program can be assured that replacing either your developer or distribution certificate will not affect any existing apps that you've published in the iOS App Store, nor will it affect your ability to update those apps.

Notes before beginning:

Replacing your distribution certificate won't affect your developer certificate or development profiles.

Similarly, replacing your developer certificate won't affect your distribution certificate or distribution profiles.

Replace only your development certificate if you are troubleshooting an issue running your app on device through Xcode.

Replace only your distribution certificate if you are troubleshooting an issue creating, submitting or installing a distribution build.

After replacing your certificate(s) you are required to update and reinstall any provisioning profiles that were bound to the old certificate.

搞清楚问题和解决办法,就开工:

  1. 首先在iOS Provisioning Portal里revoke掉当前失效的Certificates,并创建一个新的Certificates(参考[2])
  2. 通过import和export Developer Profile备份和恢复(参考[3]和[4])

Links:

  1. iOS Provisioning Portal
  2. How do I delete/revoke my certificates and start over fresh?
  3. Exporting and Importing Developer Profile
  4. Transferring Your Identities

Version & Build 号

Image1 icon

今天对Xcode里iOS的版本号又有了新的认识,一个叫做Version,一个叫做Build,这两个值都可以在Xcode中选中target,点击“Summary”后看到。 Version在plist文件中的key是“CFBundleShortVersionString”,和AppStore上的版本号保持一致,Build在plist中的key是“CFBundleVersion”,代表build的版本号,该值每次build之后都应该增加1。这两个值都可以在程序中通过下面的代码获得:

1
[[[NSBundle mainBundle] infoDictionary] valueForKey:@"key"]


### Archive后自动增长build号 除此之外,如果我们想在Archive后build号自动增长,就可以使用到Xcode的run script来实现,步骤是
  1. 选中项目的target,点击“Build Phases“
  2. 点击右下角的”Add Build Phrase“,选择”Add run script“,会产生一个新的Run Script项
  3. 拖拽新生成的Run Script项到最上面
  4. 点开该项,copy下面的shell代码进去,代码来自[这里](http://stackoverflow.com/questions/9855955/xcode-increment-
    build-number-only-during-archive?answertab=active#tab-top),如下图所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if [ $CONFIGURATION == Release ]; then
echo "Bumping build number..."
plist=${PROJECT_DIR}/${INFOPLIST_FILE}

#increment the build number (ie 115 to 116)
buildnum=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${plist}")
if [[ "${buildnum}" == "" ]]; then
echo "No build number in $plist"
exit 2
fi

buildnum=$(expr $buildnum + 1)
/usr/libexec/Plistbuddy -c "Set CFBundleVersion $buildnum" "${plist}"
echo "Bumped build number to $buildnum"

else
echo $CONFIGURATION " build - Not bumping build number."
fi

这段shell脚本的意思就是说,如果当前的配置是Release(Archive时该值为Release,直接在模拟器上运行是Debug),就设置build值为当前build值+1, 否则什么都不干。

这样在build的时候就会看到build号会自动加1的,想看build时输出的信息,可以通过”View -> Navigators -> Log”来查看最新的build时产生的log。


Ref:

  1. Concurrent Debug, Beta and App Store Builds
  2. stackoverflow: Xcode-Increment build number only during ARCHIVE?

Have fun!

KVC

KVC是Key-value coding的缩写,是一种通过key-value的方式获取对象属性的机制。
这个key是一个String的唯一标示符,这个key的name约定是必须是ASCII码、小写字母开头、中间不能有空格。

让一个类实现KVO的方式是遵循NSKeyValueCoding这个协议,该协议中定义了2个方法:valueForKey: and setValue:forKey:.这两个方法用来通过key访问和获取对象属性。

More about KVC

KVO

Apple Document about KVO

KVO的实现方式:isa-swizzing

Lightweight Key-Value Observing