快到年底了 无心工作,哎
1.讲了dealloc方法的使用。平时的使用过程中要注意下面几点内容:
- 在dealloc方法中,需要做的仅仅是释放指向其他对象的引用,并且取消原来的订阅(KVO)或者是NSNotificationCenter等通知,其他的事情不要做。
- 不论是执行异步任务还是执行同步任务,都不要再dealloc方法中调用,因为此时对象已经处在即将被回收的状态了
2.许多时下流行的语言都提供了“异常”(exception)这一特性。纯C中没有异常,而C++与OC都支持异常。实际上,C++和OC的异常相互兼容,也就是说,从其中一门语言里抛出的异常能用另外一门语言来捕获。
发生异常时应该如何管理内存是个值得研究的问题。看下面的代码
@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
}
@catch(...)
{
NSLog(@"Whoops there was an error.oh well");
}
如果doSomethingThatMayThrow抛出异常,由于异常会跳至catch块,因为那行release代码不会运行。在这种情况下,如果代码抛出异常,那么对象就泄露了。解决办法是使用@finally块,无论是否抛出异常,其中的代码都保证会运行,且只运行一次,如下所示:
@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
[object release];
}
@catch(...)
{
NSLog(@"Whoops there was an error.oh well");
}
@finally{
[object release];
}
很遗憾,在ARC下,由于不是手动release,所以上面的情况ARC不会自动处理。因为这样做要插入大量样板代码。
● 捕获异常时,一定要注意将try块内所创立的对象清理干净
●默认情况下ARC不会生成安全处理异常所需要的清理代码。开启编译选项后,可以插入代码,不过会导致应用程序过大,而且会降低效率。
3.最简单的循环引用由两个对象构成,他们相互引用对方。如图所示:
这样的引用,导致谁也不会被释放掉。还有多个对象的循环应用,如图:
避免循环引用的最佳方式就是弱引用。这种引用经常用来表示“非拥有关系”在MRC下使用unsafe_unratain和ARC下的weak。
上图中的虚线就是weak,
● 将某些引用设为weak,可避免出现“循环引用”。
● weak引用可以自动清空,也可以不清空。自动情况(autonilling)是随着ARC而引入的新特性,由运行期系统来实现。在具备自动清空的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象
4.考虑下面代码:
for(int i=0;i<100000;i++){
[self doSomethingWithInt:i];
}
如果“doSomethingWithInt”方法要创建临时对象,那么这些对象很可能会被放到自动释放池里。这样一来在执行for循环时,应用程序所占用内存就会持续上涨,而等到临界值对象都释放后,内存用量又会突然下降。
for(int i=0;i<100000;i++){
@autoreleasepool{
[self doSomethingWithInt:i];
}
}
加上这个自动释放池之后,应用程序循环时的内存峰值就会降低。自动释放池就像“栈”(stack)系统创建好自动释放池后,将其推入栈中,而清空自动释放池,则相当于从栈中弹出。
“自动释放池”本身又有开销,所以是否使用“自动释放池”取决于应用程序。
● 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池子里
● 合理运用自动释放池,可以降低应用程序的内存峰值
● @autoreleasepool这种新式写法能创建出更为轻便的自动释放池
5.Cocoa提供了“僵尸对象”(Zombie Object)这个非常方便的功能。它的实现代码深植于OC的运行期程序库、Foundation框架以及CoreFoundation框架中。他的原理是这样的:系统在即将回收对象时,如果发现xcode启用了对僵尸象功能,那么还将执行一个附加步骤:把对象转化为僵尸对象,而不彻底回收。
即便是使用了ARC,也依然会出现这种内存bug
● 系统在回收对象时,可以不将其真的回收,而是把它转化位僵尸对象。通过环境变量NSZombieEnable可以开启此功能。
● 系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子,响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。
6.这里我们禁止使用retainCount,无论是ARC环境还是MRC环境。
NSObject协议中定义了下列方法,用于查询对象的保留计数:
-(NSUInterger)retainCount;
然而ARC已经将此方法废弃了。实际上,如果在ARC中调用,编译器就会报错,跟ARC中调用retain、release、autorelease的情况是一样的。但是在非ARC下还是可以调用retainCount接口。为啥不要使用retainCount呢?
此方法之所以无用,首要原因在于:它返回的保留计数只是某个给定时间点上的值。该方法并未考虑到系统会稍后把自动释放池清空,所以未必真实反应实际的保留计数了。
下面的写法是非常糟糕的:
while([object retainCount]){
[object release];
}
第二个错误在于:retainCount可能永远不返回0,因为有时候系统会优化对象的释放行为。
第三种情况:看下面的代码
NSString *string = @"Some string";
NSLog(@"string retainCount=%lu",[string retainCount]);
NSNumber *numberI = @1;
NSLog(@"numberI retainCount = %lu",[numberI retainCount]);
NSNumber *numberF = @3.141f;
NSLog(@"numberF retainCount=%lu",[number retainCount]);
在64位MAC OSX 10.8.2系统中,用Clang 4.1编译后,这段代码输出的消息如下:
string retainCount = 18446744073709551615 //2^64-1
numberI retainCount = 923372036854775807 //2^63-1
number retainCount = 1
string是个常量,编译器把NSString对象所表示的数据放到应用程序的二进制文件里,这样运行程序时就可以直接用了,无须再创建NSString对象。NSNumber也类似,它使用了一种叫做“标签指针”(tagged pointer)的概念来标注特定类型的数值。这总做法不使用NSNumber对象,而是把数值有关的全部消息放到指针值里面。运行期系统会在消息派发期间检测到这种标签指针,并对它志向相应操作,使其行为看上去和真正的NSNumber一样。这种优化在某些场合使用,但是浮点数就没有这个优化,保留计数还是1
7.OC中多线程编程的核心就是block与gcd。这虽然是两种不同的技术,但他们是一并引入的。block是一种可在C、C++及OC代码中使用的“词法闭包”(lexical closure),它极为有用,借此机制,开发者可将代码像对象一样传递,令其在不同环境(context)下运行。在block的范围内,它可以访问到其中的全部变量。
gcd是一种与block有关的技术,它提供了对线程的抽象,而这种抽象基于“派发队列”(dispatch queue)。开发者可将block排入队列中,有gcd负责处理所有调度事宜。gcd会根据系统资源情况,适时得创建、复用、摧毁后台线程,以便处理每个队列。此外,使用GCD还可以方便的完成常见编程任务,比如编写“只执行一次的线程安全代码”(thread-safe single-code execution),或者根据可用的系统资源来并发执行多个操作。
block和gcd是当前OC的编程基石。因此必须理解其工作原理及功能
block可以实现闭包。这项语言特性是作为扩展而加入GCC编译器中的,在近期版本的Clang中都可以使用。从技术上讲,这是位于C语言层面的特性,因此只要有支持此特性的编译器以及能执行block的运行期组件,就可以在C、C++、OC,PC++代码中使用它。
下面是block的一个基本写法
void (^someBlock)() = ^{
//Block implementation here
};
block的强大之处是:在声明它的范围内,所有变量都可以为其所捕获。例如下面代码:
int additional = 5;
int(^addBlock)(int a ,int b) = ^(int a,int b){
return a+b+additional;
};
int add = addBlock(2,5);//add = 12
默认情况下,为block捕获的变量,是不可以修改的。如果在block中需要修改需要使用__block修饰符,修饰变量。
NSArray *array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count =0;
[array enumerateObjectUsingBlock:^(NSNumber* number,NSUInteger idx, BOOLBOOL *stop){
if([number compare:@2]==NSOrderAscending){
count++;
}
}];
//count =2
block中直接使用count的值。如果block捕获的变量是对象类型,那么就会自动保留它。系统在释放这个block的时候也会将其一并释放,block本身也是变量,有引用计数。
block还可以使用self变量。block总能修改实例变量,所以在声明时无需加__block。但是self却被保留了。如果self所指代的哪个对象同时页保留了块,那么这种情况就会导致“循环引用”。
每个OC对象都占据者某个内存区域。block本身也是对象,在存放对象的内存区域中,首个变量是指向Class对象的指针,该指针也叫isa。其余内存里含有block对象正常运转所需的各种信息,block对象的内部实现参考:点击打开链接 如图:
在内存布局中,最重要的是invoke变量,这是个函数指针,指向block的实现代码。descriptor变量是指向结构体的指针,每个block都包含了此结构体,其中声明了block对象的总体大小,还声明了copy与dispose两个辅助番薯所对应的函数指针。前者是保留捕获的对象,后者则将之释放。
block还会把它所捕获的所有变量都拷贝一份。这些拷贝放到descriptor变量后面,捕获了多少对象,就要占据多少内存。请注意,拷贝的并不是对象本身,而是指向这些变量的指针变量。invoke函数为何要把block对象作为参数传进来呢?原因就在于,执行block时,要从内存中把这些捕获的变量读出来。
全局block,栈block,堆block
定义block的时候,是分配在栈上的,block只在定义它的范围有效。下面的写法很危险:
void (^block)();
if(/*some condition*/){
block = ^{NSLog(@"Block A");};
}else {
block = ^{NSLog(@"Block B");};
}
block();
定义在if-else中的两个block都是在栈上的,作用范围只限于两个大括号之内。所以上述可能运行正确,可能错误。解决这个问题的办法是给block对象发送copy消息。这样就可以把block复制到堆上了。修改后代码:
void (^block)();
if(/*some condition*/){
block = [^{NSLog(@"Block A");} copy];
}else {
block = [^{NSLog(@"Block B");}copy];
}
block();
采用手动计数的,需要将其release,采用ARC则不用。
除了“栈block”、“堆block”之外,还有一类block叫做“全局Block”。这种block不会捕捉任何状态(比如外围的变量等),运行时也无需有状态来参与。block所需要的整个内存区域,在编译期已经完全确定了,因此,全局block可以声明在全局内存里,而不需要在每次用到的时候于栈中创建。另外,全局block的拷贝操作是一个空操作,因为全局block绝不可能为系统回收。这种block相当于单例。
更多block存储区域参考点击打开链接
【本节要点】
● block是C、C++、OC中的词法闭包
● block可以接收参数、也可以返回值
● block可以在栈上、堆上、全局。分配在栈上的block可以拷贝到堆上。
8.类似于一种语法糖,看起来比较舒服
//before
-(void)startWithCompletionHandler:(void (^) (NSData *data,NSError *error))completion;
//after
typedef void(^EOCCompletionHandler) (NSData *data,NSError *error);
-(void)startWithCompletionHandler:(EOCCompletionHandler)completion;
9.没有什么可说的,讲了一下block相对于delegate的优势,摘抄一下总结:
● 在创建对象时,可以使用内联的handler block将相关业务逻辑一并声明。
● 在有多个实例需要监视时,如果采用delegate模式,那么经常需要根据传入的对象来切换。而偌该用handler实现,则可直接将block与相关对象放在一起。
● 涉及API时如果用到了handler block,那么可以增加一个参数,使调用者可通过此参数来决定鹰把block安排在哪个队列上执行。
10.注意循环引用的问题,防止内存泄露
在没有出现block copy的情况下,是不会出现循环引用的。因为:栈上的block,虽然引用了self,构成环形引用,但是,最终栈上的block是需要释放的,这是一个出口。