石锅拌饭

Tag: xcode

浅谈Blocks

by Robin Lu on Sep.06, 2009, under Uncategorized

在上次的Snow Leopard tech review摘要中提到,Snow Leopard(实际上是Xcode 3.2附带的编译器)开始支持Blocks。这是一个非常有意思的功能,以前通常只有一些动态语言支持,而现在,C/C++/Objective-C也可以用上这个功能了。

Block, 简单的说,就是一个函数对象,和其它类型的对象一样,你可以创建它,可以赋给一个变量,也可以作为函数的参数来传递。计算机科学中,更常用的名字是”closure”或者”lambda”。先通过一个例子看看什么是Block:

1
2
3
4
5
6
7
    void (^hello)(char*);
 
    hello = ^(char* str) {
        NSLog(@"hello %s", str);
    };
 
    hello("robin");

这段代码申明了一个block变量hello,然后用一个block对象为它赋值,最后调用了这个函数。这是打印出的结果:

2009-09-06 16:36:12.693 blocks[6379:903] hello robin

再看一个例子。玩ruby的都知道each方法,可以很方便的遍历一些数据组合。比如:

[1,2,3].each {|x|
  puts x
}

就可以打印出

1
2
3

现在,我们也可以为NSArray加上这样的方法。我为NSArray写了一个Category,加入下面函数的实现:

- (void)each:(void (^)(id))block
{
    for (id obj in self) {
        block(obj);
    }
}

很简单,我们就有了一个属于NSArray的each。看一个调用的例子:

NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
[array each:^(id obj) {NSLog(@"hello %@", obj);}];

首先,生成了一个数组对象,里面有三个名字,然后分别向这三个名字说hello。

再来为NSArray实现一个select方法,这个方法为每一个数组中的对象调用block,这个block做出选择,选中的返回true,最后select函数返回一个数组,其中包含所有选中的对象:

- (NSArray *)select:(BOOL (^)(id))block
{
    NSMutableArray *rslt = [NSMutableArray array];
    for (id obj in self) {
        if (block(obj)) {
            [rslt addObject:obj]; 
        }
    }
    return rslt;
}

再看一个调用的例子:

NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
    [[array select:^(id obj) {
        return [obj isEqualToString:@"robin"];
    }] each:^(id obj) {
        NSLog(@"hello %@ only", obj);
    }];

依然生成包含三个名字的数组,但是先调用select,只选出’robin’,然后只向选出的名字说’hello’。

看到现在,应该已经对block有一个初步的认识了。也许会产生一个疑问,block和函数指针有什么区别?

第一个区别,函数指针是对一个函数地址的引用,这个函数在编译的时候就已经确定了。而block是一个函数对象,是在程序运行过程中产生的。在一个作用域中生成的block对象分配在栈(stack)上,和其他所有分配在栈上的对象一样,离开这个作用域,就不存在了。这是一个Xcode文档中的错误案例:

void dontDoThis() {
    void (^blockArray[3])(void);  // an array of 3 Block references
 
    for (int i = 0; i < 3; ++i) {
        blockArray[i] = ^{ printf("hello, %d\n", i); };
        // WRONG: Block literal scope is "for loop"
    }
}

另外,block对象在一个作用域中,在这个block之中可以访问同在这个作用域中的本地变量。我们来看一个例子:

void say(NSString *something)
{
    NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
    [array each:^(id obj) {
        NSLog(@"%@, %@", something, obj);
    }];    
}

这个函数是向数组中的名字说点什么,具体说什么,在block中并不确定. 因为each本身接口的限制,也没法通过函数的参数传入。如果用函数指针来实现,就不可避免的要引入全局或者静态变量,而使用block,则完全没有这个问题。避免使用全局以及静态变量最直接的现实意义就是,更容易写出线程安全的程序。

通常情况下,block对同作用域中本地变量是以只读形式访问的,如果希望以读写方式来访问,需要在申明这个变量时加上__block。

本文的例子放在github上了,其中对NSArray还实现了map和reduce。

如果希望对Blocks做更多的了解,可以参看Xcode中的”Blocks Programming Topics”。

10 Comments :, , , , more...

iPhone Application Post Build Script

by Robin Lu on Feb.13, 2009, under Uncategorized

很多平台软件开发发布前, 有个很重要的事情就是保留好符号表(symbol)文件. 因为发布的软件一般来说都去除了符号表, 而符号表对于维护是很重要的信息, 没有它, 在拿到call stack后会很难对应到代码中. iPhone开发也是一样, 每次做完Build, 都会有一个.dSYM的目录, 就是符号表. 有了它, 在用户提交crash log后会很容易定位问题.

手工保存这些符号表文件很容易产生疏漏, 恢复起来又麻烦. 我做了一个脚本, 添加到Target的Run Script Build Phase中, 可以在Build类型是Distribution时, 将build出的binary和符号表分别打包为:

  • [application].app.[version].zip
  • [application].app.dSYM.[version].[git hash].zip

如果你用的版本管理系统不是git,需要对脚本做相应改动.
以下是脚本代码

#!/usr/bin/env ruby
if ENV["BUILD_STYLE"] == "Distribution" && ENV["ARCHS"] == 'armv6'
  common_git_paths = %w[/usr/local/bin/git /usr/local/git/bin/git /opt/local/bin/git]
  git_path = ""
 
  common_git_paths.each do |p|
    if File.exist?(p)
      git_path = p
      break
    end
  end
 
  if git_path == ""
    puts "Path to git not found"
    exit
  end
 
  command_line = git_path + " rev-parse --short HEAD"
  sha = `#{command_line}`.chomp
 
  info_file = ENV['INFOPLIST_FILE']
 
  f = File.open(info_file, "r").read
  re = /([\t ]+<key>CFBundleVersion<\/key>\n[\t ]+<string>)(.*?)(<\/string>)/
  f =~ re
  puts $1
 
  # Get the version info from the source Info.plist file
  # If the script has already been run we need to remove the git sha
  # from the bundle’s Info.plist.
  version = $2.sub(/ \([\w]+\)/, "")
 
  cmdline = "cd #{ENV['BUILT_PRODUCTS_DIR']};zip -r #{ENV['CONTENTS_FOLDER_PATH']}.#{version}.zip #{ENV['CONTENTS_FOLDER_PATH']};zip -r #{ENV['CONTENTS_FOLDER_PATH']}.dSYM.#{version}.#{sha}.zip #{ENV['CONTENTS_FOLDER_PATH']}.dSYM"
  `#{cmdline}`
end

添加方法为:

  1. 在XCode左边栏选中相应的Target
  2. 在菜单中选择Project -> New Build Phase -> New Run Script Build Phase .
  3. 弹出的对话框中,Shell里填写/usr/bin/ruby, Script中填写上面的脚本代码.
Leave a Comment :, , more...

分享一下我的xcode主题

by Robin Lu on Nov.13, 2008, under Uncategorized

根据我自己的经验,白底色的编辑器是比较伤眼睛的。在使用编辑器的时候,我会尽量选择暗底色的主题。TextMate 里有不少不错的主题,但xcode中缺省的暗底色主题比较一般。我稍微改动了一下,做了一个新的出来,大概效果如下:

Xcodetheme

如果需要,可以从这里下载:DarkCity.xccolortheme。然后拷贝到/Developer/Library/PrivateFrameworks/XcodeEdit.framework/Resources 就可以用了。

19 Comments :, , more...

Archives

Browse by tags