iPhone开发日常

       记录一下平时开发工作中的一些经验和问题解决的积累,仍然是没什么体系可言。有些是转自项目组的集体智慧。

 

        使用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 CollectionUltimate iPhone Stencil ,安装的时候可以直接点击扩展包打开,然后在模板界面上点击安装按钮,或者拖拽到用户目录的

~/Library/Application Support/OmniGraffle/Stencils/中。

改变UISwitch的标签并不是一挥而就的,根本就没有提供直接的方法,你可以重写,然后设置左右的Label,也可以用这个找来的很简单的方法,但这不保证以后一定能用
[(UILabel *)[[[[[[sw subviews] lastObject] subviews] objectAtIndex:2] subviews] objectAtIndex:0] setText:@"关"];
[(UILabel *)[[[[[[sw subviews] lastObject] subviews] objectAtIndex:2] subviews] objectAtIndex:1] setText:@"开"];
出处是CocoaChina的 beauseJour
 
NSArray等容器没有自动装箱功能,所以自然没办法添加NSInteger,这时就是类别大展身手的时候了,不过就光addInt这种方法并不完备,你还得intAtIndex,特别是indexOfInt这些方法,因为没有c++的重载==运算符,或java的重写equals和hashCode方法,只好先取出来在对具体类型做比较,确实挺傻的。不过就这个需求而言,我宁可用类别而非别的方法。

对象的dealloc中必须要调用super的方法,那么请把super方法放到最后调用,否则可能会产生父类归属的对象已经被释放了而你继续使用的问题,当然你确保super的dealloc方法和你后续要操作释放的变量没有关联那写前面也无所谓,这只是一条指导意见而已,不过为了确保安全还是尽量这么实行。

NSArray等容器类在释放的时候不需要先移除内容,直接release就会自动释放所有成员,想想也是,NSMutableArray还能removeAllObjects,可NSArray根本没法移除内容啊,自然只能在数组的release时候释放数组元素了。不过容器的添加自动retain特性有时候也不是好事,比如我可能有不定多个委托,如果将其放入容器就对其增加了引用技术,但委托和被委托对象是没有从属关系的,这在结构上就不对,所以看源代码也可以看出来,一般delegate的属性都写成assign的简单赋值,当然这个多委托设计可能是我在根本上错了,现在正在思考。

 

Objective-c的指针和c语言指针是一样的,NSString* s1,s2;那么s2就定义错误了,不过至少编译器会马上报警。同样你也可以对Objective-c的指针做typedef以方便敲代码和防止类型错误。

原来在Objective-C里可以直接用->取成员变量,我开发了这么大半年竟然都不知道。。。怎么说呢,只能认为自己太不上心了,两年前转java的时候那个学习激情再也没有了。

看了一下以前的Objective-c的数,感觉2.0确实增加了很多便利方式,比如property就很方便,还有以前的版本竟然如果把类别方法隐藏就会编译错误,现在即时发送一个根本不存在的消息也不过是个警报而已。

不定参数的时候,一般使用nil作为参数传递完毕标志,不过万一动态运行中获取参数往函数里传,其中有参数为nil就完了,后面的数据就塞不进去了,当然你可以用NSNull替代,单也很麻烦,写个类别专门为了处理nil挺累人的,而且这样就要va_args宏了,我到现在也没看懂。。。

NSMutableArray添加相同的对象可以反复做,不过移除的话就会一下子把这些对象全部移除。包括只是数值上一样的不同NSNumber对象。

NSMutableDictionary似乎已经对NSNumber做了特殊处理,即使alloc出来的不同对象,只要其中数值一样,那么添加的时候只能添加一个,同样,通过NSNumber做键取值的时候,完全可以创建出新的NSNumber对象,那么一样可以获取到之前的键所对应的值。看起来就像重写了==或者equals方法一样。

id这个关键字是可以做变量名的,很奇怪。当然犯懒的时候用就是了,可如果你声明了名为id的变量,如函数的参数名,那么这个函数里面所有需要obj-c意义的id关键字时都无法正确识别了,所以大工程里还是少用吧。

NSString的stringByAppendingPathComponent很好用,可以自动添加必要的或取出不必要的分隔符,主要填入每个路径的组成部分即可。

如同c语言里不写参数或返回值类型就默认为int类似,objective-c里则是默认为id,不过id类型到底什么意义我还需要深入了解。

把view变成圆角

这里用到了Core Animation相关的内容

首先在工程里添加一个Existing Framework,把QuartzCore.framework添加进去

然后在需要圆角化的代码里引用对应头文件,然后用如下代码实现圆角效果

 

-(void)roundCorner:(UIView *)targetView withRadius:(CGFloat)radius
{
    targetView.layer.cornerRadius = radius;
}
 

其中两个参数:

targetView是要圆角化的目标view,可以是任意UIView的子类

radius是圆角的半径,可以用此参数调整圆角程度


使用posix创建的线程里不要使用 AutoRelease释放内存, 否则程序肯定会出现core或者僵死

合并模拟器和设备的两种静态库,当然你要实现编译好
lipo -create /device.a /simulator.a -output /uni.a
 
起线程的时候注意一定要在方法如main前后包夹NSAutoReleasePool,不然会有莫名其妙的内存泄露问题,明明我在线程里根本也没做什么相关的操作。

 

使用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];就会发生指针的转换警告。当然如果是单例则完全可以使用具体类型,以来方便直接用点方法,再者单例也不存在子类的可能。

 

你可能感兴趣的:(eclipse,Objective-C,敏捷开发,xcode,软件测试)