[Xcode]如何在command-line下编译带有sub-project的项目

针对含有sub-project的project, 在依赖关系正确设置(关于如何正确设置, 参考本文最后提供的链接)的情况下, 如果直接用下面命令, 基本上编译会出错, 提示找不到sub-project里面的头文件:

xcodebuild -target TestApp -configuration Release clean build

这个命令会在当前目录下创建一个build文件夹, 然后将编译的中间产物和结果放进去, 而Xcode GUI在编译时是将这些放到DerivedData下面, 这一区别导致了在编译时找不到sub-project包含的一些东西.

解决方法很简单, 加上-scheme, 命令如下:

xcodebuild -scheme TestApp -target TestApp -configuration Release clean build

指定了-scheme之后, 命令行编译的行为就跟在Xcode GUI下编译一样了.

如果使用添加header search path的方法, 也能让编译通过, 但其实不是最正确的方法, 因为在GUI下编译不需要这一步.

如何正确设置sub-projct, 及本文参考见这里

Objective-C代码格式整理

团队合作中代码风格一致比较重要, 像Google NewYorkTimes这些公司都公开了各自的Objective-C代码风格, 有需要的搜索一下就可以找到, 这里主要介绍借助Uncrustify自动整理代码格式的一些技巧.

Uncrustify: Source Code Beautifier for C, C++, C#, ObjectiveC, D, Java, Pawn and VALA, 篇幅及时间有限, 没必要过多介绍, 总之是一款定制性很高的代码格式整理工具, 但高定制性意味着配置起来很烦, 所以这里推荐一个开源图形界面配置文件编辑器UncrustifyX, 这个工具对各个配置都有详细的说明, 并且有直观的预览功能, 可以较为方便地编辑出你需要的配置, 当然更方便的方式是直接搜索别人写好的配置文件, UncrustifyX本身就带一个.

其他的关于如何安装, 如何更方便地在Xcode中使用, 可以参考这篇. 本文不再详细说明.

最后要说一下Uncrustify的一个缺点(Feature):Xcode会自动对代码中的空行添加空格缩进, 这样会对编辑代码带来方便, 而Uncrustify会删除所有行尾多余的空格, 这就意味着如果你用Uncrustify对一个已有的代码进行处理, 所有的空行缩进都会被删除, 产生大量无意义的diff, 同时影响下次编辑. 如果你期待可以通过配置文件来改变这个行为, 那恐怕就要失望了, 而且Uncrustify的作者基本上也明确表示了不会添加这个功能(现在有个issue, 里面有几个人在求这个功能).

这里提供一个简单技巧, 轻松解决这一问题, 在Xcode中, 我们可以通过全选代码, Move Line up(option+command+[) + Move Line Down(option+command+]), 来自动添加回那些消失的空格, 上面说的那个问题也就迎刃而解了. 另外说一下, AppCode自带的代码格式整理, 估计也是用Uncrustify实现的, 所以跟Uncrustify一个毛病.

–以上–

如何在Cocoa中设置文件的权限

这篇属于技巧性文章, 没什么营养.

如果你常写NSTask相关的东西, 可能需要设置一个脚本的权限为可以执行. 有各种Unix方法, 也有只用Cocoa就能实现的:

NSNumber *permissions = [NSNumber numberWithUnsignedLong: 493];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:permissions forKey:NSFilePosixPermissions];
[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:path error:&err]

简单解释一下那个"493", 这个就是十进制的"755", 也就是"rwx-xr-x", 所以如果你想要"777"的权限, 就用"511".

用NSFileManager创建文件的时候也可以传递上面的attributes, 创建可执行的文件.

–以上–

关于C++和Objective-C混编

Objective-C在大部分情况下足够满足我们的需求,但是还是会有一些情况必须要使用C++,比如:

  1. 使用C++的库
  2. 当Objective-C不够快的时候

第一点自然不必多说,至于第二点,Objective-C的消息机制比起函数调用还是比较慢的,当对性能有极高要求的时候,就需要C/C++来替代。

C++与Objective-C混编只要注意将包含C++代码的.m文件改为.mm即可,XCode就会自动判断该使用何种编译器来编译。

这看起来简单,但实际操作中还是很容易出现令人费解的编译问题,比如最经典的"Unknow type name ‘class’; did you mean ‘Class’?“。究其原因就是我们没有遵守这两条规则:

  • .m文件不能含有C++代码
  • .m文件所import或include的.h文件中不能直接或间接包含C++代码

在保证你的C++代码是正确前提下,如果发现相关编译错误可以通过如下几个方法修复

  1. 将.h文件中的c++代码转移到其他地方
  2. 阻段include或import链
  3. 将相关的.m文件后缀改成.mm

显然第三种方法相对于前两种实施起来更方便,但如果你使用XCode 4以及之后的版本所包含的模板建立项目的话有可能会忽视一个问题:

当你在AppDelegate.h中include或import一个C++的头文件时,当然你一定不会忘记修改AppDelegate.m为AppDelegate.mm,如果只做了这点儿还不够,我们还忽略了隐藏在Supporting Files组内的一个文件的存在–main.m

在以前的XCode模板中main.m默认是这样的

#import <UIKit/UIKit.h>;
 
int main(int argc, char *argv[]) {
 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

而新的模板是这样的:

#import <UIKit/UIKit.h>;
 
#import "AppDelegate.h"
 
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

看出问题来了吧,main.m新增了#import “AppDelegate.h",这就导致了main.m作为一个Objective-C源文件却引入了C++代码,而Xocde会使用Objective-C的编译器进行编译,从而产生编译错误,因此我们还需要将main.m的后缀改成.mm。

总结+吐槽:我在发现main.m这个问题之前对编译错误纠结了好久,一度修改代码,也一度怀疑是XCode本身的问题(XCode 4出奇的不稳定让人不得不对其产生质疑,虽然这次是我错了),希望同样使用Objective-C++的同学不要像我一样栽在main.m上。

–以上–

[iOS]分享最近发现的一些好东西(120311)

好久没更新了,最近在鼓捣一些Core Audio的有关内容,加上各种事儿,没有时间写东西,先把最近发现的一些好东西分享出来吧。

文章

项目

XCode 4.3.1一个小众Bug及解决方法

哦… 首先希望闲来无事发现此文的人,可以回复报下你们一天中XCode Crash的次数,附上版本,大家比比谁受气受得多。(我:4.3.1 10多次crash)

目前测试Bug的环境:XCode 4.3.1,用svn作为版本控制
PS. 4.3可能也有这个Bug,4.2.1貌似没有,但不排除svn下的其他毛病

Bug产生过程:

  1. Project处于svn版本控制之下
  2. (可有可无)向Project中添加文件,并加入版本控制(A)
  3. 从项目中Delete某个文件,并选择Move to trash
  4. 编译的时候会有Wraning提示缺少了之前删除的那个文件(Missing File xxxx)

虽然这是一个误报,对编译没有任何影响,但是有Warning看着就心烦,而且如果删除了很多文件就会造成满屏Warning,既影响心情,又影响工作。

下面是几种解决方法

  1. 如果你对XCode 4.3+移植抱有敌视态度,那么你的/Developer或者废纸篓里面可能还有XCode 4.2.1,用4.2.1进行删除的步骤,那些Warning就不会出现。
  2. 完全用XCode 4.3.1 + Terminal解决
    1. 对那些你要删除的并且有"A"(版本控制)标记的文件,右键 >> Source Control >> Discard Changes…
    2. 右键 >> Source Control >> Ignore
    3. Delete并Move to trash

这样编译就不会有warning,如果你删除的是一个文件夹,那么还会提示Missing这个文件夹,再或者上一步删除文件之前忘记设置ignore了,warning就会出现,用终端解决:
1. 打开终端,cd到项目主目录下
2. svn status,可以看到那个Warning的文件(夹)
3. svn rm xxxxx 从版本控制里去掉那个文件(夹)

这时再编译就不会有那个warning了,世界终于清净了

总结:XCode 4+对svn支持非常废,经常有莫名其妙错误,要手动解决,所以一个更方便的方法就是不用svn,现在git这么火,不怕开源的项目就直接放到github上得了,保密的就放到Bitbucket上,或者找个服务器自己搭一个,总比折腾svn这些破事好,不过广大开发者最终还是应该鄙视苹果的,照这么下去苹果还是回到几年前的小众状态比较好,那时无论是SL,还是Xcode 3都是精品中的精品。现在苹果到了风口浪尖上,做东西都很浮躁,各种神奇,傻X的Bug层出不穷,老乔也不托梦骂他们几句。

懒得喷了,话说中午写的这篇,晚上发的,发现XCode 4.3.2出了,不知道老乔显灵没,如果还Crash记得上App Store评一星啊!link

更新:恭喜,XCode 4.3.2下这个Bug依旧

–以上–

[iOS]在运行时为类添加方法

Obj-C用起来真是各种happy,比如现在有这样一种情况:有一个类,我们希望它能响应一个消息(message),但是这个类没有相应的方法(method),而你又偏偏不能重写/继承这个类。这时我们可能会想到,能不能动态地给类添加一个方法呢?感谢Obj-C,仅需简单几步就能实现。

先看一段代码

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
 
@interface EmptyClass:NSObject
 
@end
 
@implementation EmptyClass
 
@end
 
void sayHello(id self, SEL _cmd) {
    NSLog(@"Hello");
}
 
- (void)addMethod {
    class_addMethod([EmptyClass class], @selector(sayHello2), (IMP)sayHello, "v@:");
 
    // Test Method
    EmptyClass *instance = [[EmptyClass alloc] init];
    [instance sayHello2];
 
    [instance release];
 
}

我们首先定义了一个EmptyClass,继承NSObject,没有任何自带方法,接着定义了一个函数。这里提一句,Obj-C的方法(method)就是一个至少需要两个参数(self,_cmd)的C函数,这个函数仅仅输出一句Hello。接下来在addMethod方法中,我们调用class_addMethod()为EmptyClass添加方法,class_addMethod()是这样定义的:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

参数说明:

cls:被添加方法的类

name:可以理解为方法名,这个貌似随便起名,比如我们这里叫sayHello2

imp:实现这个方法的函数

types:一个定义该函数返回值类型和参数类型的字符串,这个具体会在后面讲

接着创建EmptyClass的实例,调用sayHello2,运行,输出Hello,添加方法成功。

接下来说一下types参数,
比如我们要添加一个这样的方法:-(int)say:(NSString *)str;
相应的实现函数就应该是这样:

int say(id self, SEL _cmd, NSString *str) {
    NSLog(@"%@", str);
    return 100;//随便返回个值
}

class_addMethod这句就应该这么写:

class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");

其中types参数为"i@:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void

@:参数id(self)

::SEL(_cmd)

@:id(str)

这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档

最后调用say:方法:

int a = [instance say:@"something"];
NSLog(@"%d", a);

输出something和100。

关于本文所涉及内容的详细信息请参考Objective-C Runtime Reference

本文参考了:

推荐去看看
—以上—

解决因删除xib(nib)文件所导致的错误

用XCode编写程序,如果你由于各种原因需要删除一个xib文件,在删除文件后就可能遇到一些稀奇古怪的问题,首先说下解决方法

尝试做以下工作:

  • 粗暴地删除真机/模拟器上对应的App (这个一般就能解决问题了)(注意看后文,有更温和的解决方法)
  • 对整个工程Clean一下,至于Clean在哪,建议到Help下面搜索一下
  • XCode 3可能还有个Empty Caches… -> Empty

原因我想大家已经猜到了,编译一个新版本的应用只是把要修改的部分替换成新的,似乎并不会对删除多余的东西,比如你之前建立的xib(nib),这也就导致了虽然你的代码已经不在依靠那个nib运行了,但是原来的nib还是残留在那里,并且会在运行时加载,这就可能造成一些问题.

同样,如果你错误地删除了一个nib而没有"刷新"一下你的应用,这个错误就会被掩盖起来,等你的应用编译或安装到其他机器上时就可能产生各种错误.

了解了原理也就知道了替换第一步的放法:

  • 找到xxx.app,显示包内容,删除你不想要的nib文件

–以上–

XCode 4连接gitolite服务器的方法

XCode 4终于支持git了,但是要连接一个gitolite服务器还是稍微有点麻烦,不能通过orgnizer直接添加,需要执行以下几步(关于gitolite的搭建参考这篇"

  • 编辑 gitolite-admin/config/gitolite.conf
    仿照已有的内容,添加一个repository
  • git add config/   (假设在gitolite-admin目录下)
    git commit -m "MESSAGE"
    git push
  • 从返回的信息中我们可以看到添加了那个repository
  • git clone 下来新添加的repository
  • 在Xcode中新建项目,然后保存进clone下来的文件夹内,注意别添加本地的git
  • 再次add commit push
  • 接着用XCode打开项目就可以在Orgnizer的repository标签里看到了

–以上–

在XCode 4中使用TODO FIXME等标记

在XCode 3,我们可以用类似这样的注释来方便我们标记需要修改的部分:

// TODO:
// FIXME:
// !!!:
// ???:

XCode 4不知为何不支持这一功能了,网上有一种解决方法,可以在编译后的Warning里面看到我们标记的TODO, FixME等,原文在此,转发过来:

  • 进入项目属性设置那个页面
  • 选择一个Target
  • 选择Build Phases标签
  • 点击右下角的Add Build Phase
  • 展看上面刚出现那一栏Run Script,输入以下内容
    KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:"
    find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | perl -p -e "s/($KEYWORDS)/ warning: \$1/"

–以上–