《Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》---学习笔记(31-40)

31. 在dealloc方法中只释放引用并解除监听

对象在引用计数为0时,会执行dealloc方法。

并不是引用计数一为0,就立刻执行

在dealloc方法中,主要做的事:

  • 释放对象所拥有的引用,通过.cxxx_destruct方法
  • 对象所拥有的其他非OC对象,比如由纯C的API生成的对象
  • 监听行为清理掉(KVO和NSNotificationCenter)
- (void)dealloc{
	CFRelease(coreFoundationObject);
	[[NSNotificationCenter defaultCenter] removeObserver:self];
}

在iOS系统,应用程序终止时会调用UIApplicationDelegate中的:
- (void)applicationWillTerminate:(UIApplication *)application

32. 编写“异常安全代码”时留意内存管理问题

在try块中,如果先保留了某个对象,然后在释放它之前又抛出了异常,那么,除非catch块能处理此问题,否则对象所占内存就讲泄露。

@try {
	//这部分代码是有可能出现异常的错误
}
@catch(NSException *exception){
	//只有出现了异常错误,才会执行
	//通过打印exception.reason,可以知道错误原因
	//还可以做一些操作,避免影响用户
}
@finally{
	//不管有没有异常,都会执行
	//即使前面代码块中有return,也会执行
}

-fobjc-arc-exceptions打开,ARC会生成安全处理异常所用的附加代码。
不过打开后,会导致应用程序变大,而且会降低运行效率

可以在找错误时打开,平时开发用不到的时候,关闭

33. 以弱引用避免保留环

用unsafe_unretained修饰的属性特质,其语义同assign特质等价
但,assign通常只用于”整体类型“(int、float、结构体等),
unsafe_unretained则多用于对象类型。

weak,不拥有对象,并且在属性回收时,对象会自动设为nil

在这里插入图片描述

34. 以“自动释放池块”降低内存峰值

在OC的引用计数架构中,有一项特性叫做:“自动释放池”(aoturelease pool)

释放对象有两种方式:

  • relase,立即递减
  • autorelease,将其加入“自动释放池”中

自动释放池用于存放那些需要在稍后某个时刻释放的对象。
清空自动释放池时,系统会向其中的对象发送release消息

@autoreleasepool {
	//...
}

一般情况下,无须担心自动释放池的创建问题。
系统会自动创建一些线程,比如主线程或GCD机制中的线程,这些线程默认都有自动释放池,每次执行“事件循环”(event lop)时,就会将其清空。因此,不需要自己来创建“自动释放池块”。

  • 自动释放池于左花括号处创建,于对应的右花括号处自动清空
  • 位于自动释放池范围内的对象,将在此范围末尾处收到release消息。
  • 自动释放池可以嵌套,嵌套的好处:可以控制应用程序的内存峰值,使其不至过高

比如:

for (int i = 0; i < 10000000; i++) {
	NSString *str = [NSString stringWithFormat:@"%d", i];
}

运行会发现:内存会过高,然后再突然下降

在这里插入图片描述

造成这种的原因是…

想不想知道,想知道还不好好看^ _ ^

for循环会创建很多临时变量,这些对象创建成功后会放在自动释放池里面
虽然,这些是临时变量,我们不使用他们,在方法调用完毕后,这些对象就没释放了,但他们依然存在自动释放池里面,等待系统稍后将其释放并回收。
然后,自动释放池要等线程执行下一次事件循环时才会清空,这就意味着在执行for循环时,会持续有新对象创建出来,并加入自动释放池中。所有这种对象都要等for循环执行完才会释放。
这样一来,在执行for循环时,应用程序所占内存量就会持续上涨,而等到所有临时对象都释放后,内存用量又会突然下降。

可以修改为:

    for (int i = 0; i < 10000000; i++) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"%d", i];
        }
    }

奇迹出现了:
在这里插入图片描述

还是那么高!!!

说好的可以降低内存呢?

35. 用“僵尸对象”调试内存管理问题

向已经回收的对象发送消息是不安全的。这么做有时可以,有时又不可以。
具体是否可行,取决于对象所占内衣有没有为其他内容所覆写。
因此,应用程序只是偶尔崩溃。。。。。。

在没有崩溃的情况下,那块内存可能只复用了其中的一部分,所以对象中的某些二进制数据依然有效。
还有一种可能,内存地址已经被新的对象所占用,那么,消息会被发送给新的对象
如果新对象能处理,则不崩溃
如果新对象不能处理,则崩溃
例如:[NSDictionary lenth]可能原来是个NSString对象,内存已经被一个NSDictionary对象覆盖,从而造成这种崩溃。

可以在编写程序的时候,打开NSZombieEnable功能

在这里插入图片描述

僵尸对象的工作原理:
它的实现根植于OC运行期程序库、Foundation框架、CoreFoundation框架中。系统在即将回收对象时,如果发现通过环境变量启用了僵尸对象功能,那么还将执行一个附加步骤:将对象转化为僵尸对象,而不彻底回收。

36. 不要使用retainCount

ARC中,不能编译、调用retainCount
MRC中,调用这个方法,得到的值不一定准

它返回的引用计数只是某个给定时间点上的值。该方法并未考虑到系统会稍后把自动释放池清空,因而不会将后续的释放操作从返回值里减去,因此,此值未必能够真是的反映实际的引用计数了。

由于对象可能处在自动释放池中,所以其引用计数未必如想象般精确。

第六章 块与大中枢派发

Block与GCD

在开发应用程序的时候,需要留意多线程问题
而多线程的核心就是:block与GCD

块block是一个 “词法闭包”,借由此机制,开发者可以将代码像对象一样传递
GCD是一种与块有关的技术

37. 理解“块”这一概念

使用块,可以实现闭包

块定义在函数里面,并且块与函数共享同一个范围内的东西。
使用“^”符合表示,后面跟着一对花括号,花括号里面是块的实现代码。

^{
	
}

块本身也是对象,块本身和其他对象一样,有引用计数

如果块定义在实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量。

块对象的内存布局:

在这里插入图片描述

翻看之前笔记,找到block定义:

struct __main_block_impl_0 {
  void *isa;//isa指针
  int Flags;
  int Reserved;
  void *FuncPtr;//函数地址
  struct __main_block_desc_0* Desc;
  int age;//函数调用的外部参数
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 

基本一致,只是书上函数地址名为invoke,之前自己的笔记记录的是FuncPtr,应该是改为FuncPtr了

invoke变量,这是个函数指针,指向块的实现代码。

descriptor变量是指向结构体的指针,每个块里面都包含此结构,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针。辅助函数在拷贝及丢弃块对象时运行,其中会执行一些操作,比如:copy要保留捕获的对象,dispose则将之释放。

块还会把它所捕获的所有变量都拷贝一份。这些拷贝放在descriptor变量后面。比如例子中的age

注意块所在的内存区域:栈、堆、全局

38. 为常用的块类型创建typedef

每个块都有 固有类型,可以将其赋给适当类型的变量。
块的类型由接受的参数以及返回值组成

使用C语言中的“类型定义”特性,typedef为Block起个别名
例如:

int (^blockName)(BOOL flag, int value) = {
	^(BOOL flag, int value){
		return someInt;
	}
}

该block接受两个类型分别为BOOL和int的参数,并返回类型为int的值。

return_type(^block_name)(parameters)

使用typedef定义block类型:
typedef int(^blockName)(BOOL flag, int value);

调用的时候:

SomeBlock bloc = ^(BOOL flag, int value){

};

使用typedef这项特性,可以使块的API做得更易用。

当block做函数的参数时,就可以使用这种操作。

  • 在定义类型名的时候,最好把这个类的名字加在新的类名前面

39. 用handler块降低代码分散程度

  • iOS系统发现某个应用程序的主线程已经阻塞了一段时间之后,就会令其终止。
  • completion handler指的是:完成的回调
  • 网络设计中,大量用到了block回调

40. 用块引用其所属对象时不要出现保留环

使用block的时候,要注意不要产生循环引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值