记录一下平时开发工作中的一些经验和问题解决的积累,仍然是没什么体系可言。有些是转自项目组的集体智慧。
使用Mac操作系统不太习惯,它很多组合建都是用到Command,但项目组没钱买Mac真机,都是用的破解,Command似乎是被映射为Alt键了,那么平时常用的复制就成了Alt+C,粘贴自然就是Alt+V,切换输入法也变成了Alt+space,虽说是习惯就好,但每每回家用自己的机器按下错误的快捷键时也挺烦躁的。下文中所有说道Alt的地方应该都是指Command键。
胳膊拗不过大腿。现在使用的IDE是Xcode,经历过Visual Studio到Eclipse的镇痛之后,我立刻修改了Xcode的快捷键,主要还是使用了Eclipse的风格,而且把复制粘贴都改了,但很快就不爽了,当我一次次把网页里的代码Alt+C复制过来,再用Ctrl+V粘贴到Xcode里后,终于忍不住还原了Xcode的复制粘贴快捷键。
Finder的不习惯。对着文件夹按回车不是进入文件夹而是改名,文件夹层次的前进后退是Alt+[],一般来说不提供剪切功能,如果同一个盘里拖拽就是剪切,但是不同盘间拖拽则是复制,按住Alt则可以做到剪切,平时每次都是在两个Finder窗口里面拖拽,所以对于"把文件转移到上层文件夹"这项常见操作我深感苦恼。还有文件名字可以在前后留空格,这也导致了一次艰难的debug。按理说Mac操作系统是Unix的,但其实文件不区分大小写。。。
程序退出的不习惯,按Alt+Q只是退出窗口,按Alt+Q才是退出程序。搜索功能似乎也很怪,不是在当前目录下搜索而是全盘搜索,而且偶尔会毫无反映,听说这个要怪到黑苹果身上,因为没有提供索引功能。
Objective-C有吸引人的特性,也有恼人的地方。类别与代理设定的就很好,但内存回收机制我觉得里外不是人。信息的隐藏似乎是面对对象设计语言的本能,当在java里看到一个很棒的类想拿过来复用却发现了final关键字时不知你有什么感受,特别是这个类还在一个庞大的package里的时候;同样,虽然类别可以随意扩展你能看到的类了,但遇上已经是类别的类的时候你还是一筹莫展。代码的混写很爽但也可能造成问题,不过问题都是程序员造成的,但好处确是语言设计者带来的。
混写代码中对着空指针做delete会崩溃,其他情况不清楚。
文件不知道能不能层叠展示。
Cocoa提供的图形库效果非常棒,但谁都能轻易实现的界面毫无竞争力可言。
Objective-C的内存管理。在单个函数里自己写了alloc就要自己release,当然也可以设定为autorelease;属性里设定为retain的实例变量要在dealloc函数里release,如果不采用属性的get,set方法,那么应该这样实现
- (void)setData(NSObject *ob) { [ob retain]; //obj是实例变量 [obj release]; obj = ob; }
对于返回值是alloc或copy对象的函数,设定为autorelease,使用返回值的地方不需要再release;delegate对象不要设定为retain;autoreleasepool不推荐使用drain,而是在必要的时候release再alloc;对于工厂方法获得的对象不要release。
Xcode的源码组织不是像eclipse那样按照文件夹层次来的,可以随意把文件从不同的group拖拽,但却不影响文件本身的位置,不过把有层次的文件夹直接拖拽加入到Xcode里,那是会保留原有文件层次的。
界面编程时发现对控件操作毫无反映,请先检查是否把Interface Builder上的对应的线拉起来了。所有控件事件的函数声明一律为(IBAction ) funcname:(id)sender;不过后面的参数也可以省略,不过这样就没法复用了。
目前的线程操作我主要选择NSThread,起线程
[NSThread detachNewThreadSelector:@selector(VolumnTask:) toTarget:self withObject:nil];
那么就可以在本实例里的VolumnTask函数里执行了,如果想做Windows形式的静态回调函数,那么注意把函数声明为静态,同时toTarget的参数写成xxx class。
还有个很奇怪的问题,如果在线程的函数里写autorelease的变量,比如NSNumber的工厂方法numberWithFloat,那么这个变量会无法自动释放导致内存泄露,提示类似"NSCFNumber, no pool in place - just leaking",解决的方法没着到,只好用了普通的构造方法然后手动释放。现在想想可能需要在线程函数里套上NSAutoReleasePool,这也是NSOpertation的通用做法。
Objective-C的协议与Java的接口有点相似,不过语法稍微繁琐了些,比如我一开始想写一个返回满足某协议的工厂方法的时候就怎么都写不出来,翻了翻书根据满足协议的变量的定义形式再猜一下,Recordable自然是协议名,大致就是这样
+ (id<Recordable>)getRecordDevice:(NSString*)deviceName;
有句话我忍了很久了,但我已经忍不住了
和eclipse相比,Xcode就是一垛屎!!!
后注(2011.02.25):现在熟悉了Xcode的各种快捷键加gdb的整合之后,觉得不能太算一垛屎。。。
刚才遇到一个小问题,而且这个问题不是第一次遇到了,那就是如果在iPhone程序里读取资源,但资源名大小写弄错了,那么在iPhone模拟器上是可以读到的,但iPhone真机上就读不出来了,同时在Interface Builder上其实也是显示不出来的。还是一个奇怪的问题,一些图片用UIImage的imageNamed方法在iPhone4上就可以读取,但在3上就不能,我找了半天原因比如图片路径啊,大小写之类,最后才发现原来是我没写图片的后缀名,而在4上是可以不写后缀名的,但之前的版本必须写。同时4上不写后缀名还有显示高清图片的考虑,这个就不说了。
根据项目要求,单元测试对函数覆盖率必须达到90%以上,这对于一个iPhone业务软件确实是困难重重,如果是真心想满足而非糊弄的话。
拒绝泛泛之谈,困难到底出在什么地方我一个个列出来:
1.测试框架,使用的还是基于SenCaseTest的编译期静态测试的方法,所有调用到iPhone系统运行时间的功能的函数全部失效,比如NSUserDefaults设置获取键值。当然这个应该是可以处理的,因为我记得Apple提供的测试框架本身就有两种。
2.Objective-c语言本身的可测性,如dealloc函数应该是用户对想要释放的对象调用release后满足使用计数为0时系统调用,但如果要测试这个函数也不是不能直接调用,可调用之后要如何验证dealloc的有效性呢?就如同测试c++的operator delete,如果成功调用了,那么对象里的内容全部失效,再调用该对象的任何数据将导致访问违例,如果是抛出异常倒还可以可以,可EXC_BAD_ACCESS没法拦截啊。
3.iPhone界面框架的可测性,最常见的如viewDidLoad要怎么测,理论上是该视图确实需要加载的时候才会被调用,同样可以强行调用,不过完全可以想象,iPhone在主动调用viewDidLoad的时候一定已经做好了一系列的事前准备,比如初始化好视图上的控件之类,而我自己强行调用想通过检测视图上的控件来测试显然是行不通的。其他种种因为iPhone完善的界面回调和委托机制所以写得很方便的界面相关函数统统变得无法测试。打桩异常困难,实际上我没找到iPhone上的mock项目,那么参数都无法正确的传入又要如何进行测试呢?比如我想测试点击按钮后做事,形如- (void)click:(id)sender;的函数当然还是可以强行调用,在当前测试框架下显然是不能创造出sender的button啦,自己在测试代码里alloc一个也可以,但这样又要如何测试点击的逻辑正确性呢?如何测试是否把点击事件关联错误了呢?显然这都是不可能的。
4.我自己写函数的可测性,在项目开始的时候,我正好在je上看帖子,有一个将函数全部缩短到3-5行的争论深深影响了我,加上我本身就有一些不愉快的敏捷开发实践,一方便可以说对敏捷开发的好处有了一些认识,又对很多人对其的不实之词深恶痛绝,但又苦于有理论无更细化的动作,此时就决定从减少函数行数做起。必须承认这个做法是相当明确而有效,在不断的缩短和抽取函数的同时,将流程和模块功能逐步深入头脑,无论是编码还是调试都快了很多,在代码的美观性上也无可比拟,所谓的代码规范的问题间接就消失了,三五行的代码怎么可能出现变量名覆盖或者圈复杂度等问题呢。但这样短小的函数在测试时相当头疼,首先是函数太多了,其次是很多函数根本就没有返回值,因为函数里的所有语句都没有返回值,这对于功能连结紧密的三五行代码是很常见的事。
如此,想做到真心完成单元测试覆盖率看来是要付出很多努力了。
大家都知道app其实是个文件夹,如果在shell里直接运行的话还需要逐层进入其内部,而且不同打包方式的app可能内部结构还不同,其实用open命令就可以自动打开其运行程序了。不过对于一个常用的抓包软件WireShark这些必须要用sudo层层开打包最后直接打开的程序,用open还是没法直接获取网卡。
Finder用Alt+向下箭头就是打开,包括打开文件夹,各种文件等等,我用了半年总算找到这个快捷键了。Alt+向上箭头就是返回上一层目录,是上一层不是后一层。
Xcode的语法检测由于Objective-C的语言特性显得非常弱,比如一个id类型的元素,可以对其发任何本项目中有的消息而不会有任何警告,当然你随手写根本没有的消息还是会警告一下的,所以即使需要用id类型,也尽量使用id<interface>这种声明形式进行静态检测。奇妙的是不同的类的方法还不能单纯的返回值不一样,否则会警告,明明是在不同的类里面。
在Mac平台上有一个非常优秀的流程图绘制软件Omni Graffle,它功能强大而且上手简单,很轻松就可以绘制出惊艳的流程图,而且它扩展起来也很方便,特别是grafletopia 这个网站,有着相当多的模板扩展,我特别喜欢它的UML 2.1 Collection 和Ultimate iPhone Stencil ,安装的时候可以直接点击扩展包打开,然后在模板界面上点击安装按钮,或者拖拽到用户目录的
[(UILabel *)[[[[[[sw subviews] lastObject] subviews] objectAtIndex:2] subviews] objectAtIndex:0] setText:@"关"]; [(UILabel *)[[[[[[sw subviews] lastObject] subviews] objectAtIndex:2] subviews] objectAtIndex:1] setText:@"开"];
这里用到了Core Animation相关的内容
首先在工程里添加一个Existing Framework,把QuartzCore.framework添加进去
然后在需要圆角化的代码里引用对应头文件,然后用如下代码实现圆角效果
-(void)roundCorner:(UIView *)targetView withRadius:(CGFloat)radius { targetView.layer.cornerRadius = radius; }
其中两个参数:
targetView是要圆角化的目标view,可以是任意UIView的子类
radius是圆角的半径,可以用此参数调整圆角程度
lipo -create /device.a /simulator.a -output /uni.a
使用NSThread的initWithTarget似乎永远都是在主线程做操作,不过重新构建一个NSThread的子类,重写main之后又似乎可以到真正的子线程里了。
网络url经常会将中文字符转码为utf8的,而且很烦人它竟然是直接把内存数值写成字符串字面值,转过来花了我半个小时,这里记录一下方法
- (NSString *)encodeToChinese:(NSString *)encodeString { encodeString = [encodeString stringByReplacingOccurrencesOfString:@"%" withString:@""]; int length = [encodeString length]/2+1; char *p = malloc(length); if (p) { memset(p, 0, length); for (int i = 0; i < [encodeString length]/2; i++) { NSRange r = {i*2, 2}; const char *n = [[encodeString substringWithRange:r] UTF8String]; p[i] = strtol(n, NULL, 16); } return [NSString stringWithUTF8String:p]; } return nil; } NSString *s = [self encodeToChinese:@"%E7%BD%91%E7%BB%9C%E5%B0%8F%E8%AF%B4"];
结果是"网络小说"
我了割草,发现自己白写了,还做了很多假设,比如网址如果根本就是ascii编码的我就挂了,还是看标准解答吧
(NSString *) CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)inStr, CFSTR(""), kCFStringEncodingUTF8);
一行即可,其中inStr就是需要转码的url
如果制作静态库,其中使用到了类别,那么调用方会在调到的时候发出unrecognized selector的运行时错误,解决方法也很简单,首先静态库的项目在other link flag里加上-ObjC,当然如果用Xcode创建的话默认都会添加上,然后在使用该静态库的项目里的other link flag加上-all_load,这样就OK了。不过这也会导致程序体积变大,同时该静态库的全部符号外露。那么如果使用了-all_load后特别是依赖了多个静态库,多个静态库里又同时使用了同一个符号的话就没办法了。注意看apple的文档 里,似乎意思是如果是mac程序或者32位的话,使用静态库的项目不需要加-all_load,但64位或iOS的必须加,我这里程序几乎都是以iOS为前提的。仔细阅读文档之后发现用-force_load也可,只需要指定那个静态库的路径就行了,还避免了有些静态库在-all_load下的链接错误。不过在实际使用时,如果使用了如上两个链接参数,类似的情况即使不是多个静态库,如果该静态库有符号与宿主项目里符号重名都会报错,暂时没有办法解决。
创建文件夹直接使用
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
即可,不仅是现在推荐的方法,而且看第二个withIntermediateDirectories参数,如果添YES可以直接替你层层创建好,相当便利。
我今天才发现我犯了一个大错误,给self的成员赋值的时候,我忘记释放对象了,即
self.obj = [[OBJ alloc] init];
这下obj的引用计数上来就是2了。
NSString的引用技术已经和java的string一样很优化了,显然直接的常量字符值赋值是(unsigned int)-1,而且即使用alloc后的内存空间的initWithString或类方法的stringWithString但参数传常量字面值也一样,当然如果用format或者其他高级构造方法就正常了。
今天尝试给NSDictionary添加对象,观察其对象应用计数时发现,setObect:forKey:的key似乎只能用字符串,后来查了下,发现理论上只要满足NSCoping协议和NSObject 的hash以及NSObject的isEqual都可以做key,但键值管理的键应该始终使用字符串。不过我用了很多NSNumber应该也可以。
控件自适应大小其实很简单,直接在IB里面设置就可以了,点击一个控件的Inspector,第三栏里面的AutoSizing里面一看就知道了,比如可以保持和上下的距离,保持左右距离,但充满中间,或者直接拉伸到头等等。反正把虚线点成实线就是要充满,中间那个是充满中部,外面也可以点上,那么就可以把和外部的距离也充满。不过我不清楚怎么设置代码创建的控件自适应大小。
一直以来有个误解,以为把对象写明类型就是静态绑定,写id类型就是动态绑定,不过斯坦福教学视频里指出所有方法都是动态绑定(我猜alloc之类除外),写明类型只能让编译器加以教研提醒一下用户而已。
对nil发消息,返回值如果类型是NSObject或内置类型,那么都将返回0,但是struct则是未定义的。
KVO机制不错,还有它所依赖的KVC基础,但是需要仔细学习。
Mac里的自带程序都很不错,比如数码测色计可以非常精确的获取颜色,黑苹果也可以用,不用每次都找美工了。
UIActionSheet从最下面弹出的时候,如果正好下方有个UITabbar的话,那么被挡住的地方就没法按到,于是要把一般的[sheet showInView:self.view]改成[sheet showInView:self.view.window]
很奇妙的问题,如果把UIGestureRecognizer实例化之后并添加到两个以上的view上,那么只会对最后一个view有效。
构造方法一般都返回id类型,不然在子类的构造方法中如果调用父类的话,self = [super init];就会发生指针的转换警告。当然如果是单例则完全可以使用具体类型,以来方便直接用点方法,再者单例也不存在子类的可能。