<iOS与OS X多线程和内存管理>__文字笔记

本文深入讲解Objective-C中的内存管理机制,包括对象的生成与持有、 autorelease的使用、引用计数管理、ARC特性、block的内存管理等内容。

1,如果一个对象使用的是alloc/new/copy/mutablecopy方法生成的话,那就生成并且持有了该对象。如果像id obj = [NSMutableArray array]这样的方法生成的,那就是取得对象的存在,但是不持有对象,如果想要持有该对象,那就要调用[obj retain]这个方法,这样通过retain方法,非自己生成的对象跟用alloc/new/copy/mutablecopy方法生成并持有的对象一样,成为了自己所持有的对象。 2,用alloc/new/copy/mutablecopy方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。并且释放了的对象不可再被访问,如果向一个已经释放的对象发送消息,就会崩溃。 3,使用NSMutableArray类的array类方法等可以取得谁都不持有的对象,这些方法是通过autorelease而实现的,因为autorelease就是使得对象只能在指定的生存范围中生存,超过之后就会自动释放。 4,对于使用alloc/new/copy/mutablecopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象的时候需要将其释放。而由此以外所的得到的对象绝对不能释放。倘若在应用程序中释放了非自己所持有的对象就会崩溃。有个简单的例子,比如id obj = [NSMutableArray array]这个方法,obj就是取得了对象的存在,但是实际上它并没有持有对象,只是取得对象的存在(除非调用retain方法才可以持有),如果这个时候你调用[obj release]这个方法的话,程序马上就会崩溃。 5,通常在使用oc,也就是foundation框架的时候,无论调用哪一个对象的autorelease实例方法,实际上调用的都是NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,autorelease实例方法已被该类重载,因此如果你在NSAutoreleasePool的对象调用autorelease的方法时,程序就会出错。 6,同一个程序中按文件单位可以选择ARC有效/无效 7,引用计数式内存管理的思考方式: a:自己生成的对象,自己所持有 b:非自己生成的对象,自己也能持有 c:自己持有的对象不在需要时要释放 d:非自己持有的对象无法释放 8,所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。 9,使用weak修饰符可避免循环引用。通过检查附有weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废除。遗憾的是,weak修饰符只用用于ios5以上版本的应用程序,在ios4的应用程序中可以使用_unsafe_unretained修饰符来代替。 10,要把对象型变量加入到C语言结构体成员中时,可强制转换为void*或是附加_unsafe_unretained修饰符。 struct Data { NSMutableArray _unsafe_unreatained *array; } 附有_unsafe_unretained修饰符的变量不属于编译器的内存管理对象。 11,苹果的官方说明中称,arc是"由编译器进行内存管理"的,但实际上只有编译器是无法完全胜任的,在此基础上还需要oc运行时库的协助。也就是说,arc由以下工具,库来实现: a: clang(LLVM编译器)3.0以上 b: objc4 Object-C 运行时库493.9以上 12,我们知道如果附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量,但是大量使用附有__weak修饰符的变量,则会消耗相应的cpu资源,良策是只在需要避免循环引用时使用__weak修饰符。 13,id __weak obj = [[NSObject alloc]init];使用__weak修饰符修饰obj这个变量时,会引起编译器警告,因为该源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量中,所以自己不能持有该对象,这时会被释放并废弃,所以会有警告;虽然自己生成并持有的对象通过objc_initweak函数被赋值给附有__weak修饰符的变量中,但编译器判断其没有持有者,故对象立即通过objc_release函数释放和废弃,这样一来,nil就会被赋值给引用废弃对象的附有__weak修饰符的变量中。 14,{ id __strong obj =[[NSObject alloc] init] ; id __weak o = objc; NSLog(@"1 %@",o); NSLog(@"2 %@",o); NSLog(@"3 %@",o); NSLog(@"4 %@",o); NSLog(@"5 %@",o); } 由于最开始生成并持有的对象为附有__strong修饰符变量obj所持有的强引用,所以在该变量作用域结束之前始终存在。因此在后面的几个打印代码中,在变量作用域结束之前,可以持续使用附有__weak修饰符的变量o所引用的对象。 15,将对象赋值给附有__autoreleasing修饰符的变量等同于arc无效时调用autorelease方法。ex:id __autoreleasding objc = [[NSObject alloc]init] 16,若想在block语法表达式中将值赋给在block语法外声明的自动变量,需要在该自动变量上附加__block说明符。使用附有__block说明符的自动变量可在block表达式中赋值,该变量称为__block变量。 17,在block的表达式中,如果在表达式中赋值给截获的没有__block的变量,则会产生编译错误,但是使用截获的值却不会有任何问题。同时在blocks中,截获自动变量的方法并没有实现对c语言数组的截获,如果在表达式中使用就会产生编译错误,这时可以使用指针来解决该问题。 a; const char text[] = "hello"; b; const char *text = "hello"; void (^blk) (void) = ^ { printf("%c\n",text[2]); } 如上面例子,在上面的block表达式中,如果是用a这样的声明,就会产生编译错误,因为在blocks中,截获自动变量的方法并没有实现对c语言数组的截获,这时可以使用指针来解决该问题(如以上b的声明,在block表达式中就不会有编译错误) 18,其实block的就是object-c的对象,因为如果你用编译器看他的源代码你就会发现他的实质就是各种结构体组成,其实oc类对象的实质也是c的各种结构体,结构体里面有各个相关的储存信息的变量。所以block的实质也是oc的对象。 19,Block的实质就是栈上block的结构体实例;__block变量的实质就是栈上__block变量的结构体实例。 20,block的存储域: a:记述全局变量的地方有block语法时(也就是用typedef定义block时),或者block语法的表达式中不使用应截获的自动变量时(没有使用在那个作用域里面的变量),在以上这2种情况下,block为_NSConcreteGlobalBlock类对象,即block配置在程序的数据区域中,除此之外的block语法生成的block为__NSConcreteStackBlock类对象,并且设置在栈上。 b:blocks提供了copy方法,可以将block和__block变量从栈上复制到堆上,将配置在栈上的block复制到堆上,这样即使block语法记述的变量作用域结束,堆上的block还可以继续存在。 21,配置在全局变量上的block,从变量作用域外也可以通过指针安全的使用;但设置在栈上的block,如果其所属的变量作用域结束,该block就被废弃。 22,不管block配置在何处(堆,栈,还是程序的数据区域),用copy方法都不会引起任何的问题,在不确定时调用copy方法即可。但是在arc中不能显式的release,那么多次调用copy方法进行复制会不会有问题呢?其实完全没有问题。 23,__block变量存储域: 1,若在1个block中使用__block变量,则当该block从栈复制到堆时,使用的__block变量配置在栈上,这些__block变量也全部被从栈复制到堆。此时,block持有__block变量,即使在该block已复制到堆的情形下,复制block也对所使用的__block变量没有任何影响。 2,在多个block中使用__block变量时,因为最先会将所有的block配置在栈上,所以__block变量也会配置在栈上,在任何一个block从栈复制到堆时,__block变量也会一并从栈复制到堆并被block所持有,当剩下的block从栈复制到堆时,被复制的block持有__block变量,并增加__block变量的引用计数。如果配置在堆上的block被废弃,那么它所使用的__block变量也就被释放。 24,使用__block变量用结构体成员变量__forwarding的原因:栈上的__block变量用结构体实例在__block变量从栈上复制到堆上时,会将成员变量__forwarding的值替换为复制目标堆上的__block变量用结构体实例的地址。这样无论在block语法中,block语法外使用__block变量,还是__block变量配置在栈上或者堆上,都可以顺利地防伪同一个__block变量。 25,什么时候栈上的block会复制到堆上呢? 在调用block的copy实例方法时,如果block配置在栈上,那么该block会从栈复制到堆上。block作为函数返回值返回时,将block赋值给附有__strong修饰符id类型的类或block类型成员变量时,编译器会自动地将对象的block作为参数并调用_block_copy函数,这与调用block的copy实例方法效果相同,在方法名中含有usingblock的cocoa框架方法或者gcd的api中传递block时,在该方法或者函数内部对传递过来的block调用block的copy实例方法或者_block_copy函数。有了这种构造,通过使用附有__strong修饰符的自动变量,block中截获的对象就能超出其变量作用域而存在。 26,因为在block使用中,只有调用了_block_copy函数才能截获附有__strong修饰符的对象类型的自动变量,所以如果不调用_block_copy函数的情况下,即使截获了对象,它也会随着变量的作用域的结束而被废弃。因此。block中使用对象类型自动变量时,除以下情形外,推荐调用block的copy实例方法: a:block作为函数返回值返回时 b:将block赋值给类的附有__strong修饰符的id类型或block类型成员变量时 c:向方法名中含有usingblock的cocoa框架方法或者gcd的api中传递block时 27,arc有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为附有__strong修饰符的变量。 28,变量作用域,是一个不可忽视的点; 29,使用__block变量来避免循环引用的优缺点如下: 优点:通过__block变量可以控制对象的持有时间;在不使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可,在执行block时可动态地决定是否将nil或者其他对象赋值在__block变量中 缺点:为了避免循环引用必须执行block,存在执行了block语法却不执行block路径时无法避免循环引用问题。 若由于block引发了循环引用,根据block的用途选择使用__block变量,__weak修饰符或者__unsafe_unretained修饰符来避免循环引用。 30,arc无效时,一般需要手动将block从栈复制到堆,另外,由于arc无效,所以肯定要释放复制的block,这时我们用copy实例方法来复制,用release实例方法来释放。只要block有一次复制并配置在堆上,就可以通过retain方法持有,但是对于配置在栈上的block调用retain实例方法则不起任何作用。因此推荐使用copy实例方法来持有block. 31,arc无效时,__block说明符被用来避免block中的循环引用,这是由于当block从栈复制到堆时,若block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain,若block使用变量为没有__block说明符的id类型或对象类型的自动变量,则被retain。 32,由于arc有效时和无效时__block说明符的用途有很大的区别,因此在编写源代码时,必须知道该源代码是在arc有效情况下编译的还是无效情况下编译的。 33,使用dispatch_queue_create创建的队列,尽管有arc这一通过编译器自动管理内存的优秀技术,但生成的dispatch queue必须由程序员负责释放,因为dispatch queue并没有像block那样具有作为oc对象来处理的技术。所以通过dispatch_queue_create 函数生成的dispatch queue在使用结束后通过dispatch_release函数释放 34,dispatch queue也像oc引用计数式内存管理一样,需要通过dispatch_retain函数和dispatch_release函数引用计数来管理内存。 35,在dispatch_asyn函数中追加block到dispatch queue后,即使立即通过dispatch_release函数释放dispatch queue,该dispatch queue由于被block所持有也不会被废弃,因此block能执行,block执行结束后会释放dispatch queue。这时谁都不持有dispatch queue,因此它会被废弃。因此如果需要释放queue的话,在编写了queue添加block的代码后,就可以马上编写dispatch_release函数释放queue。 36,在dispatch queue的使用中,如果使用了含有create的api创建了queue,通过函数或者方法获取queue,有必要通过dispatch_retain函数持有,并在不需要的时候通过dispatch_release函数释放。 37,主线程只有1个,所以mian dispatch queue就是一个serial dispatch queue,所有追加到mian dispatch queue处理的block都是在主线程runloop中一个接着一个执行的。 38,dispatch_set_target_queue这个api不仅可以变更queue的block任务的执行优先级,还可以把原来并行执行的多个由create创建的串行任务,通过把所有的queue都设置为跟一个目标queue一样的执行优先级,这样就可以由并行变串行了,因为她们的执行优先级一样,所以就按添加的顺序一个一个执行任务。 39,如果想在指定时间后执行处理的情况,可使用dispatch_after函数,不过这个只是个大概的时间,并不是十分精确的时间。这个主要看当前是不是刚好在runloop运行的时候,如果是在runloop运行的间隙,就要加上间隙的时候,如果本身执行任务比较多,或者本身runloop执行时就有延迟,那时间就会更长了,所以那个指定的时间算是最小等待时间,实际的时候只会比这个时间长。 40,使用concurrent dispatch queue(用create创建并行队列) 和 dispatch_barrier_async函数可以实现高效率的数据库和文件访问。把读和写放在不同的线程中,几个读操作可以并行执行,因为几个线程一起读数据不会造成数据错误,不过在读的时候不可以有写,同时写的时候不可以有读,这样就把读写分开了,用dispatch_barrier_async就可以做到,先读,再写,再读,完美。 41,sync函数 + mainqueue 会造成死锁,原因就是因为在等待一个永远都不可能完成的任务执行完。 42,用于实现dispatch queue而使用的软件组件: a:libdispatch b:libc(pthreads) c:XNU内核 43,我们一般使用的gcd的api全部为包含在libdispatch库中的c语言函数,dispatch queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的block。block并不是直接加入FIFO队列,而是先加入dispatch continuation这一dispatch_continuation_t类型的结构体中,然后再加入FIFO队列,该dispatch continuation用于记忆block所属的dispatch group和其他一些信息,相当于一般常说的执行上下文。 44,GCD函数的底层调用和队列的处理是在XNU内核这样的层面上运行的,所以说,它的性能是屌爆的。 45,GCD中除了dispatch queue外,还有不太引人注目的dispatch source。它是bsd系内核惯有功能kqueue的包装。kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术,其cpu负荷非常小,尽量不占用资源,kqueue可以说是应用程序处理XNU内核中发生各种事件的方法中最优秀的一种。 46,dispatch queue没有取消这个概念,一旦将处理追加到queue中,就没有方法可以将该处理去除,也没有方法可在执行中取消该处理,编程人员要么在处理中导入取消这个概念,要么放弃取消,或者使用NSOperationQueue等其他方法。dispatch source与dispatch queue不同,是可以取消的,而且取消时必须执行的处理可指定为回调的block形式,因此使用dispatch source实现XNU内核中发生的事件处理要比直接使用kqueue实现更为简单,在必须使用kqueue的情况下希望大家还是使用dispatch source,它比较简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值