大致总结下去年,虽然忙忙忙,但是其中还是有不少的收获。那么我就分享一点收获吧~
先说下之前项目中让我印象最深刻的两个坑吧。(虽然是很小的两个点,但是个人认为还是比较难发现的。)
坑1:
之前碰到过一个问题是关于debug和release环境下不同的问题,看下面的代码
1 | BOOL x; |
那么猜测下打印出来的x是值是多少呢
结果是在debug环境下是 0 而在release环境下是1
原因主要是release环境下,会给未复制的变量 按照意愿复制,而以上代码会给x事先复制YES
所以在之后的过程中 我会尽量给变量复制初始值,可以避免这类问题的出现,而且这类问题的排查起来也是很麻烦的,因为在debug环境下不会出现,只有在release环境下才会出现
坑2:
app中有时候会使用到coreData作为数据持久化的方式,一般coreData的保存 使用异步保存方法
1 | [_parentContext performBlock:^{ |
之前我们在项目中也会在app即将终止的回调中加入数据库的保存,希望能够把数据做保存。
然而如果在app即将终止的时候仍旧是用以上的方法,那么就会出现数据保存不了的情况。因为异步调用,app的生命周期会在其环节中的任何时候结束。所以这里要用同步的
1 | [_parentContext performBlockAndWait:^{ |
对于工作中遇到的坑,以后我会继续福利给大家。
下面分享一些之前看过的书中 我觉得比较适合的内容
前段时间刚看完的《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》这本书。
下面把部分觉得有意思的点拿出来分享下。
1、在类的头文件中尽可能少引入其他的头文件
少用#import xxx.h 如果正要用可以使用@class xxx
用@class 这种叫做“向前声明”该类
如果使用了import来引入 那么会引入.h中所以引入的内容,当然会增加编译时间,也许还会出现两个类相互引用的问题。
那么讲到这里 就有#import、@import、#include、@class这几个
#include 一般是c和c++的引入一般用
其中#import是objective-c中的一般引入类可以理解为include的改良版 确定文件只能被导入一次,不会在递归包含中出现问题
而@import 在xcode 5 下,为了更易于开发,增加了modules和 auto-linking 这两个新特性。用@import 来增加框架
2、多用类型常量,少用#define预处理指令
如#define XXX 0.3
这个预处理指令会把源代码中的XXX字符串替换为0.3。这样定义出来的常量没有类型信息,并且,预处理过长会把碰到的所有的xxx一律替换成0.3
相对来说在类中定义 static const NSTimeInterval xxx = 0.3; 这样更好一些。即定义了类型,同时也令代码的可阅读性加强
注:这里的变量xxx一定要同时用static 和 const 来声明。如果试图修改由const修饰符所声明的变量,那么编译器就会报错。而static修饰符则意味着该变量仅在定义此变量的编译单元中可见。因此如果在其他类中声明了同名的变量 也不会报错,
3、少用try catch
在arc情况下 更容易造成内存泄露(具体原因如何就不说了),而且在控制包大小的时候,如果把项目中所有try catch去掉 在设置里吧开启trycatch的开关关掉,能缩减包的大小不少,但是由于经常会用到第三方的库,所以这一点很难把控。所以具体能减少多少,我也没有尝试过。
4、用“自动释放池块”降低内存峰值
objective-c对象的生命期取决于其引用计数。在objective-c的引用计数架构中,有一项特性叫做”自动释放池(autorelease pool)“。释放对象有两种方式:一种是调用release方法,十七保留计数立即递减;另一种是调用autorelease方法,将其加入”自动释放池“中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空(drain)自动释放池时,系统会向其中的对象发送release消息。
创建自动释放池所用语法:1
2
3
4
5@autoreleasepool
{
}
`
然而,一般情况下无须担心自动释放池的创建问题。系统会自动创建一些线程,比如主线程或gcd机制中的线程,这些线程默认都有自动释放池,每次执行“事件循环”时,就会将其清空。因此,不需要自己来创建“自动释放池块”,通常只有一个地方需要创建自动释放池,那就是main函数
但是下面这种情况
1 | for (int i= 0; i < 10000 ; i++) |
其中doSomething: 方法如果要创建临时对象,那么这些对象很可能会放在自动释放池里,如果这些对象是一些临时字符串,那么即便在调用完方法之后就不再使用了,但是他们也依然是处于存活状态,因为目前还在自动释放池里,等待系统稍后将其释放回收。然而自动释放池要等线程执行下一次事件循环时才会情况,这意味着执行for循环时,会持续有新对象创建出来,并加入自动释放池中,所有这种对象都要等for循环执行完才会释放。这样一来,在执行for循环时,应用程序所占内存会持续上涨,而等到所有执行对象都释放后,内存又突然下降。这种情况不堪理想。
那么可以增加一个自动释放池来解决这个问题。如果把循环内的代码包裹在“自动释放池”中,那么在循环中自动释放的对象就会在这个池,而非线程的主池里面
如:1
2
3
4
5
6
7for (int i= 0; i < 10000 ; i++)
{
@autoreleasepool{
[self doSomething:i];
}
}
`
自动释放池机制就像“栈”一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就相当于将其放入栈定的那个池里。
5、用“僵尸对象”内存管理问题
僵尸对象Zombie Object
开发过程中经常会不经意间向已经回收的对象发送消息。然而这是不安全的,因为有时候可以,有时候不可以。具体可行与否,完全取决于对象所占内存有没有为其他内容所复写。而这块内存有没有移作他用,又无法确定,因此,应用程序只是偶尔奔溃,在没有奔溃的情况下,那块内存可能只复用了其中一部分,所以对象中的某些二进制数据仍然有效。还有一种可能,那就是那块内存恰好为另外一个有效且存活的对象所占用。在这种情况下,运行期系统会把消息发到新内存对象那里,而此对象也许能应答,也许不能。如果能,那程序就不奔溃,如果不能则奔溃。
而cocoa提供了“僵尸对象”。启动这项调试功能后,运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正回收他们。这种对象所在的核心内存无法重用,因此不可能遭到复写。僵尸对象受到消息后,会抛出异常,其中准确的说明了发送过来的消息,并描述了回收之前的那个对象。
因此“僵尸对象”也是一直debug的好方法,而且可以排查一些比较隐秘的bug。