iOS编程遇到的一些难点问题总结

最近做的一个项目,虽然不大,但刚开始接触iOS编程,遇到和克服的问题还不少,记录下来,温习一下,或者对别人也会有一点帮助。

这个项目采用的是swift和objective C,CPP混合编程的方式,有些模块之前在别的平台已经实现了,想直接拿过来用。另一方面是对swift不太熟悉,而且版本变化太快,很多时候按照教程例子来做,结果还是错误。感觉objective c相对稳定一点,所以主体还是以OC为主。

1. swift语言里面的?和!问题

有帮助的网页,网上查了很多,各说纷云,看得一头雾水,还好一个网页讲得算是明白一点。

http://joeyio.com/ios/2014/06/04/swift---/

补充一点我个人的理解,swift语言看起来简单优雅,实际上里面藏了很多玄机。之所以要用?和!,目的是为了让代码更明确,同时给编译器提供更多线索,发现更多潜在错误。它是Apple发行的语言,很符合Apple的性格,它要你把事情弄清楚再写代码,而不是把模糊的问题丢给编译器来做决定。


2. table view的逻辑

由于iOS系统是不开源的,因此我们要实现表格功能必须安装系统预定好的套路来走。有几个关键点,第一个是它的2个代理,其中一个代理负责提供数据,所谓提供数据,就是2个最主要的接口函数,第一个是告诉系统表格有多少行,第二个就是告诉系统每一行里面的数据是什么内容。第二个会被多次调用,假设一个页面里面有10行,那么这个接口函数就会被调用10次。但不会超出一个页面的次数,因为系统只会请求当前用户看到的页面的数据。假如表格的数据有100个,那么它是通过不断滚动的过程中丢弃旧的,填充新的数据这种方式来实现的。

这是关于view的一个代理,另外一个代理是关于controller的,就是当用户点中表格的某一项时,程序要做些什么,这里面最常用的就是didSelected接口函数,开发者只需要在这里面写自己的实现代码即可。要注意不要写到didDeselected里面,这里面的函数名很容易搞混,这个Deselected是某一项从选中变成不被选中时呼叫的接口。一开始我写到这个里面,总感觉哪里不对,后来才发现。

使用customer类型的prototype时,如何访问里面的label对象?tag的使用。

常见的表格例子,表格里面的cell只有最多2个标签,但我的项目里要放入5个标签,那么如何在代码里面操作这些标签呢?由于界面是在IB里面拉进去的,而cell里面的标签却不能通过ctrl drag的方式拉进代码里面。后来看到一个例子是给每个标签定一个tag值,然后在代码里面用viewByTag

UILabel *name = [cell viewWithTag:TAG_NAME];
    UILabel *singer = [cell viewWithTag:TAG_SINGER];
    UILabel *code = [cell viewWithTag:TAG_CODE];
    UILabel *lang = [cell viewWithTag:TAG_LANG];
    UILabel *type = [cell viewWithTag:TAG_TYPE];

通过这样的方式就能获取到对于的label。

后来偶然的机会了解到,还是能通过直接的方式访问,但麻烦一点,就是要先把cell用一个class定义好,然后在interface里面手工建立好label的声明,这个时候就可以storyboard里面拉线到table view里面了。

http://stackoverflow.com/questions/10176312/connect-outlet-of-a-cell-prototype-in-a-storyboard

这个页面里面讨论了这个问题。

表格的配置不当,很容易导致app崩溃,而且经常会看到:

在这个地方崩溃:AppDelegate: UIResponder, UIApplicationDelegate 

debug显示的信息是:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationItem tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fd9d1760d70'
这个原因其实是我在storyboard里面看到table view的data source没有关联,就自己把数据关联到table上面了。很多时候这种崩溃都说由于这个关联的datasource不对,可以先断开来试试。按照我的理解,因为这底层是看不到的,只能猜,就是它关联了这个,就把另一个释放了,从而有用的那个对象找不到,造成系统崩溃。这就是为什么很多资深玩家不喜欢用stroyboard,因为不好掌控,有些隐藏的东西不容易发现。而用代码来实现界面的构造虽然麻烦一点,但胜在够明白,写成什么样就是什么样。而我现阶段还是要依赖图形的工具,我体会图形的一点不好的地方就是2个项目的storyboard不可比较,曾经有些例子,完全按照教程来一步一步做,可是死活不对劲,但下载老师做好的项目又是正常的,比对代码也没有差别,差别就在storyboard,可是storyboard里面的差异非常大,根本看不出端倪,而且把对方的文件直接覆盖自己的也错误百出。

总的来说,还是自己功夫不够深。以后还要深入把基础打好才行。

3. search bar的逻辑

以前的search bar和search display之类的对象是分开的,后来Apple提供了整合在一起的方案,而且还把搜索的算法也封装成对象,期望可以简化开发者的工作。可是对于我这种开发者来说是恰恰相反。反而是最简单的search bar更适合我,因为我只需要在searchbar的内容发生改变的时候运行一次查找,然后把表格刷新一次就行了。而如果调用那些捆绑的display controller,则需要实现更多的协议,而且还要提供一个输出结果显示的table view controller,真的搞得我头晕。Apple官方的资料里面也提供了一个实现的例子,可是这个例子本身就很复杂,涉及到多个view controller。

http://www.hcios.com/archives/1143

这个页面里面提供的方式比较适合我。

https://developer.apple.com/library/prerelease/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html

这个是官方提供的例子,是Apple主导的做法,他们已经剪除了旧的那种做法,现在推荐使用ui search controller的方式,但是不太清楚这样改的背景原因是什么?感觉也没有那么好用啊。


4. 用IB来做界面还是用代码里面实现?

用代码来设置控件的大小有可能在适配不同的屏幕时出现问题,暂时还没有去研究auto resize的部分要怎样做。

但用代码要做界面其实没有想象中那么难,其实只有把界面中的控件当成对象来处理就可以了,创建对象,设置对象的熟悉,把对象add到view里面,设定相关的delegate,然后就OK了。


5. bundle文件的访问方式

这个项目需要访问外部的一个bin文件,从这个文件里面读取数据,但iOS与android不同,它有一个sandbox的机制,应用程序只能访问自己的空间。所以不能把bin文件放在sdcard目录,而是一个叫bundle的目录。

这个bundle说白了就是在xcode界面左边项目列表里的内容,我们要往里面加入bin文件,方法就是把这个bin拖动进去,当拖进去后,会提示是否要copy,选择是,还有要选择add to app,否则代码里里面会访问不了。把bin文件拖进去之后,代码要如何访问呢?

-(void)loadIndexFile{
    NSString *path = @"/MEGIDX.bin";
    NSString *filePath=[[NSString alloc] initWithFormat:@"%@%@",[[NSBundle mainBundle]resourcePath],path];
    
    [self readIndexFile: filePath];
}
这样就可以获取到目标文件的路径,然后就可以像一般的文件操作那样对它进行open,read, seek了。
但是bundle是只读的,如果有要写如的文件,则需要通过

NSString *filePath=[[NSString alloc]initWithFormat:@"%@/MEGIDX",NSHomeDirectory()];


6. 容易导致程序崩溃的原因

由于某些对象会被自动释放,所以当某些对象不能被访问到时,就会导致崩溃。解决的办法是屏蔽掉一些地方,看问题是否仍然出现。有些对象如果只有指针,而没有实例化,也会导致崩溃,特别是列表的array,需要用alloc init这样的方式分配好空间,甚至要预先赋值,否则当程序需要访问时会导致崩溃。

有些又IB创建的对象,也很容易因为配置不当而导致程序崩溃,比如说某个对象在IB里删除了,后来又添加一个同名的对象,这个时候它之前残留的outlet会仍然连接到代码里,但这个时候会有可能导致出错,正确的方法是同时删除全部重新做一次。


7. 对适配器模式的理解

看了一个极客学院的视频,对适配器模式的讲解比较好,老师举的例子是实际生活中手机的电源适配器,就是不管外界的电压是220V还是110V,通过适配器之后可以转换成统一的5V电压。这样用在代码中,是为了降低程序间的耦合,使灵活性提高。当缺点就是可读性降低,提高了学习成本,对于没有这个概念的程序员来说可能根本看不懂设计者的用意所在。


8. 对segue的理解,新旧版本ios的变迁

segue是在storyboard里面连接2个视图控制器的那个箭头,它起到承接2个对象之间切换的功能,它需要把前一个对象里面的信息传递到下一个对象去的这么一个功能角色。

- (void)prepareForSegue:(UIStoryboardSegue *)segue
                 sender:(id)sender

这个方法用来在界面切换时执行一些代码。传进来2个参数都是有用的。


9. 二进制字符串的处理,如何解决显示乱码的问题。

对于中文,需要定义一个NSStringEncoding对象,然后在init NSString的时候传进来,

NSStringEncoding

if ([lang isEqualToString:@"CHINESE"] == true) {
            NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
            name = [[NSString alloc] initWithCString:songName encoding:enc];
            singer = [[NSString alloc] initWithCString:singerName encoding:enc];
        }else if([lang isEqualToString:@"VIETNAM"] || [lang isEqualToString:@"REMIX"]){
            unichar temUni[128];
            int len = (int)strlen(songName);
            len = StringUtils::CP1258ToUnicode((unsigned char *)songName, temUni, len);
            name = [[NSString alloc] initWithCharacters:temUni length:len];
            
            len = (int)strlen(singerName);
            len = StringUtils::CP1258ToUnicode((unsigned char*)singerName, temUni, len);
            singer = [[NSString alloc] initWithCharacters:temUni length:len];
        }
        else{
            name = [[NSString alloc] initWithCString:songName encoding:NSUTF8StringEncoding];
            singer = [[NSString alloc] initWithCString:singerName encoding:NSUTF8StringEncoding];
        }

对于越南文,则需要在底层转换成unicode编码,然后用initWithCharacters这个函数来给NSString初始化。

对于英文,则需要用NSUTF8StringEncoding来初始化字符串。

总结,iOS系统内部以unicode为主,其他的编码最终会被转换成unicode处理。NSStringEncoding对象提供了一个函数入口给NSString的初始化方法,它其实是先用这个转换函数把别的格式的编码转换成为iOS统一使用的unicode编码,然后再初始化一次。

10. cpp代码移植到iOS项目遇到的问题,关于重载。

cpp支持同名的构造方法,而iOS导入这样的同名方法会报错,解决的办法是把另外的构造方法改一个名字。



你可能感兴趣的:(iOS编程遇到的一些难点问题总结)