此篇我将罗列出iOS开发中,Xcode编译器出现的常见错误,警告视同为错误处理。(序号只做排序用)
1.死存储问题
这个问题上篇文章提到过,之所以重新罗列出来,是因为这个问题也挺常见的。导致这个问题的原因是,当我们对某个对象直接赋值之后,如果包含这个对象的方法立马就结束了,那么这个对象就会被自动释放,但是我们本身根本还没有用到它,所以就形成了一个死存储代码,这个赋值语句其实也是无效的。
2.访问了某个不存在的方法
ObjC 的方法调用跟 C++ 很不一样。 C++ 在编译的时候就已经绑定了类和方法,一个类不可能调用一个不存在的方法,否则就报编译错误。而 ObjC 则是在 runtime 的时候才去查找应该调用哪一个方法。
这两种实现各有优劣,C++ 的绑定使得调用方法的时候速度很快,但是只能通过 virtual 关键字来实现有限的动态绑定。而对 Objective-C语言来说,本质上它使用的是“消息结构”而非“函数调用”,因为ObjC是由Smalltalk演化而来的,后者是消息型语言的鼻祖。
关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。如果代码中调用的函数是多态的,那么采用函数调用方式的语言就要按照“虚方法表(virual table)”来查出到底应该执行哪个函数来实现。
采用消息结构的语言,不论是否多态,总是在运行时才会查找所要执行的方法。实际上,编译器甚至不关心接受消息的对象是何种类型。接收消息的对象问题也要在运行时处理,其过程就是“动态绑定”。
举个例子,比如下面这行代码
[self newMethod];
假设我们调用了一个不存在的方法,就会报出unrecognized selector sent to instance的异常。其实我们用respondsToSelector:方法判断一下即可。大多数情况下,可能在使用delegate的时候,需要加一下判断,因为delegate通常是一个id类型(泛类型)。
if ([self respondsToSelector:@selector(newMethod)]) { [self newMethod]; }
3.堆栈溢出
多数情况下应用程序是不需要考虑堆和栈的大小的,总是当作足够大来使用就能满足一般业务开发。但是事实上堆和栈都不是无上限的,过多的递归会导致栈溢出,过多的 alloc 变量会导致堆溢出。
iOS内存布局原理如下图所示:
一个运行时进程的典型分布具体有五个部分,分别是代码段,初始化数据段,未初始化数据段,堆,栈。
在应用程序分配的内存空间里面,最低地址位是固定的代码段和数据段,往上是堆,用来存放全局变量,对于 ObjC 来说,就是 alloc 出来的变量,都会放进这里,堆不够用的时候就会往上申请空间。最顶部高地址位是栈,局部的基本类型变量都会放进栈里。 ObjC 的对象都是以指针进行操控的,局部变量的指针都在栈里,全局的变量在堆里,而无论是什么指针,alloc 出来的都在堆里,所以 alloc 出来的变量一定要记得 release。
对于 autorelease 变量来说,每个函数有一个对应的 autorelease pool,函数出栈的时候 pool 被销毁,同时调用这个 pool 里面变量的 dealloc 函数来实现其内部 alloc 出来的变量的释放。
下面的链接能够更好的帮助你理解堆栈,不过是在stackoverflow
上的,全英文,你可以逐句翻译,有助你理解。
http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap
4.多线程并发操作
这个问题应该是日常开发操作中都会遇到的问题了。当某个对象会被多个线程修改的时候,有可能一个线程访问这个对象的时候另一个线程已经把它删掉了,导致 Crash。比较常见的是在网络任务队列里面,主线程往队列里面加入任务,网络线程同时进行删除操作导致挂掉。
这个问题的解决方案就是对某个线程加上线程锁,举个例子:
- (void)addStudent:(Student *)student {
if (student != nil) {
NSLock* aLock = [[NSLock alloc] init];//创建线程锁
[aLock lock];//加锁
[students addObject:student];
[aLock unlock];//解锁
}else{
return;
}}
也可以使用关键字synchronized来简化代码
- (void)addStudent:(Student *)student {
if (student == nil) return;
@synchronized(players) {
[students addObject:student];
}
}
synchronized关键字其实就是互斥锁,意思是当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
在日常开发中,尽量不要加锁,因为多线程开发本身就是为了提高程序执行顺序,而同步锁本身就只能一个进程执行,这样不免降低执行效率,除非必须加锁,哪里有隐患,就在那里加锁,在使用互斥锁的时候,要尽可能缩小它的范围,范围越大,效率越差。
当然,线程锁很分很多种,上面代码片段中用到的只是普通的线程锁。iOS中的线程锁,还有以下几种:
1)NSRecursiveLock 递归锁:使用普通的 NSLock 如果在递归的情况下或者重复加锁的情况下,自己跟自己抢资源导致死锁。Cocoa 提供了 NSRecursiveLock 锁可以多次加锁而不会死锁,只要 unlock 次数跟 lock 次数一样就行了。
2)NSConditionLock 条件锁:多数情况下锁是不需要关心什么条件下 unlock 的,要用的时候锁上,用完了就 unlock 就完了。Cocoa 提供这种条件锁,可以在满足某种条件下才解锁。这个锁的 lock 和 unlock, lockWhenCondition 是随意组合的,可以不用对应起来。
3)pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。
4)NSDistributedLock 分布式锁:这是用在多进程之间共享资源的锁,现在不涉及
更多的有关线程锁的内容,请阅读下面的博客,很有收获
李剑飞的技术博客
下面是一些具体的错误处理
1.warning: Could not resolve external type c:objc(cs)NSObject
这个问题我最近遇到过,之前个人写的一个小项目(Xcode6.2时候写的),因为Xcode7版本更新的缘故,导致诸如下图2-1所示的小问题,后来自己更改之后,又在原项目中集成了BugTags的SDK,然后编译未通过,出现了上述错误,当时忘了截图。
我在Stack Overflow找到了调试方法,步骤如下:
- Go to Build Settings -> Build Options -> Debug Information Format
- Change the Debug setting from "DWARF with dSYM File" to "DWARF"
- Leave the Release setting at "DWARF with dSYM File"
The problem appears to be that Xcode was trying to create dSYM files for Debug builds. You don't need dSYM files for Debug builds -- it's release builds where you need them.
什么是dSYM文件?
Xcode编译项目后,我们会看到一个同名的 dSYM 文件,dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件,位于 /Users/<用户名>/Library/Developer/Xcode/Archives 目录下,对于每一个发布版本我们都很有必要保存对应的 Archives 文件.
更多有关dSYM的内容请点击如下链接
dSYM文件分析工具
2. -[__NSArrayI objectAtIndex:]: index 100 beyond bounds [0 .. 1]'
问题属于数组越界的问题,下拉刷新,上拉加载tableview的时候容易出现。这个问题需要放到与你相对应的工作环境中解决,一些比如数组是从零开始取下标的细节,都是可能疏忽出错的地方。
请参考下面的博客
取自CSDN博客
3.升级Xcode7后,旧项目无法加载网络数据
这个问题,Xcode7刚更新的时候很常见,这是因为iOS9引入了新特性 APP Transport Security (ATS:简单理解意思是:应用传输安全...翻译可能不太准确, 只是字面意思的理解, 欢迎指正)
由于新特性要求APP内访问的网络必须是使用HTTPS协议, 查询到, 这个协议相对于HTTP协议较安全, 但是目前很多公司, 很多项目依旧是使用HTTP协议, 有时候也不能立马改成HTTPS协议, 只能在工程中进行修改.
修改步骤如下:
1)打开项目中Info.plist文件
2)在plist文件中添加项NSAPPTransportSecurity,类型为字典。
3)添加子项NSAllowsArbitraryLoads,类型为Boolean,值为YES。如图4-1
4)Command+R即可
4.数组中存在空对象
这个问题是因为某个数组中插入的对象为空值,在出错的地方打断点调试即可。在Debug模式下可能不会报错,但是在release模式下会出错。因为Objective-C是一门动态语言,数组对象为空的问题在运行期环境可能不会出错,但是放在工作环境中就会因为对象为nil报错。
其实这个问题完全可以在代码编写的时候优化,那就是在创建数组的时候多使用字面量语法,也就是语法糖的形式。或者对可能出现对象为空值的数组加if语句判断。
请参考以下链接http://www.2cto.com/kf/201501/369307.html
神书《Effective Objective-C 2.0》对这个问题有深层次的解释,可以去看看,看过的人都说好。
5.[BEROR]CodeSign error: Certificate identity ‘iPhone Distribution: ***.’ appears more than once in the keychain. The codesign tool requires there only be one.
上述错误是在项目进行Archive打包时出现的。
原因:出现此问题的原因是多个证书之间冲突造成。
解决方案:打开mac系统的“实用工具”-“钥匙串访问”-“我的证书”中,如果看到有重复的证书名,那么请将早期的证书删除掉,重启Xcode。
6.Could not change executable permissions on the application.
原因:拥有相同的bundle Identifier已经在设备上运行。
解决办法:删除设备中或者模拟器中的App。
7.ld: library not found for -lmp3lameclang: error: linker command failed with exit code 1 (use -v to see invocation).
原因:在工作中,一般是多人编辑同一个工程时其中一人没将某个库上传导致的。
解决方案:上传具体静态库
8.A valid provisioning profile matching the application's Identifier 'XXXX' could not be found
原因:当编译后出现上述问题,缺少证书或者是在Code Signing Identity处没有选择对应的证书或者是证书不对应。
解决办法:重装证书,检查证书是否是否选择是否对应。
9.在ARC项目中,使用了MRC模式开发的第三方库
1)打开项目所属的TARGETS设置
2)点击Build Phases选项,进入到子项Compile Sources中,找到使用MRC的文件,添加-fno-objc-arc标记,表明在编译时,该文件使用MRC编译
3)相反的,如果想在MRC项目中,使用ARC的文件,可以使用 -fobjc-arc 标记即可
总结:
今天先罗列出这么多,下篇我将继续总结日常开发中,Xcode抛出的各种异常和错误,供大家参考,如有不对之处,请您指正,谢谢!
更新
1.Xcode编译出现Link错误,出现“duplicate symbols for architecture i386 clang”提示
问题:链接时,项目有重名文件
解决方案:
1.在Xcode的Targets->Bulid Phases-Link Binary With Libraries中查看有无重复的lib
2.全工程搜索下重名文件
2.关于category位于静态库,引用该静态库的工程使用其中的Category,出现“unrecognized sent to class”提示。
问题:标准静态库与OC之间的linker的差异。在标准的UNIX静态库,linker symbol是依照每一个类别而产生的,但由于category并没有产生一个类别,所以抛出上述错误。
解决办法:
1.在该静态库的Targets->Bulid Setting->Linker flags->在其中添加-Objc
2.在使用该静态库的工程Targets->Build Setting->Linker Flags->加上-all_load或者-force_load
3.warning:ld:warning directory not found for option'-L'
问题:通常是path问题
解决:
TargetsText 里面找到Build Setting ,然后Library Search Paths和Framework Search Paths,删除掉编译报warning的路径即可。