• OC_内存管理(二)对象复制、循环引用问题、自动释放池


     
    循环调用:

    1.循环引用的问题
    两个对象A、B,有可能会出现特殊情况:A中包含B的实例变量;B中也包含A的实例变量,如果这两个实例变量都是强引用(A有着B的实例变量所有权,B也有A的实例变量所有权),然后在两个对象销毁时,会出现A、B都不能正常销毁的情况。下面用代码来演示循环调用问题:

    ClassA的头文件Class.h代码

    1. #import <Foundation/Foundation.h>
    2. //类的前向声明
    3. @class ClassB;
    4. //类的前向声明与包含头文件的区别?Q:
    5. @interface ClassA : NSObject
    6. //retain在ARC下面可以使用,但是最后使用strong
    7. @property(nonatomic,retain)ClassB *b;
    8. @end
    ClassA的实现文件Class.m代码
    1. #import "ClassA.h"
    2. #import "ClassB.h"
    3. @implementation ClassA
    4. -(void)dealloc
    5. {
    6.     NSLog(@"A的dealloc");
    7.     [_b release];
    8.     [super dealloc];
    9. }
    10. @end

    ClassB的头文件ClassB.h代码:

    1. #import <Foundation/Foundation.h>
    2. @class ClassA;
    3. @interface ClassB : NSObject
    4. @property(nonatomic,retain)ClassA *a;
    5. @end

    ClassB的实现文件ClassB.m代码:

    1. #import "ClassB.h"
    2. #import "ClassA.h"
    3. @implementation ClassB
    4. -(void)dealloc
    5. {
    6.     NSLog(@"B的dealloc");
    7.     [_a release];
    8.     [super dealloc];
    9. }
    10. @end

    接下来就是测试文件main.m代码:

    1. #import <Foundation/Foundation.h>
    2. #import "ClassA.h"
    3. #import "ClassB.h"
    4. int main(int argc, const char * argv[]) {
    5.     @autoreleasepool {
    6.         ClassA *a = [[ClassA alloc]init];
    7.         ClassB *b = [[ClassB alloc]init];
    8.         [a setB:b];
    9.         [b setA:a];
    10.         //获得所有权的是强引用
    11.         /*因为对象a,对象b都是强引用,造成循环引用,无法正常释放。
    12.          解决方法:将其中一个改为弱引用。不获取对象所有权
    13.          */
    14.         [a release];
    15.         [b release];
    16.     }
    17.     return 0;
    18. }

      细心地同学会发现,在ClassA和ClassB的头文件中@property的属性这是均为retain,这表明都是强引用会获得对象所有权,这样就会使程序循环调用,而无法正常释放,解决方法为将二者之一的属性改为弱引用,比如改为assign。这样就无法获得对象所有权,从而能顺利解决循环调用问题。

    2.前向引用和#import包含的区别?

      一是import这两个被引用类的头文件,另一个是使用@class申明是类名,二者的区别:

      1). import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你;

      2). 在头文件中,一般只需要知道被引用的类的名称就可以了,不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称,而在类的实现部分,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。

      3). 在编译效率方面考虑,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次被引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用@class则不会。

      4). 如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。

      所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。 在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import在@class中声明的类进来.

     
     

    对象复制:

      1.创建一个新对象,新对象的内容和旧对象的内容是一样的。

      2.给现有的对象发送copy消息,就可以复制出新的对象。

      3.类中的对象类型实例变量,如果不想受外界影响,应该采用copy的方式复制出新的对象。

    对象复制分为浅复制和深复制:

      浅复制:复制对象时,如果对象中包含对象类型的实例变量,只复制指针值,新对象中的对象类型实例变量和旧对象中的对象类型实例变量指的是同一个对象。

      深复制:复制对象时,如果对象中包含对象类型的实例变量,要对对象类型的实例变量也要做对象复制,新对象中的对象类型实例变量和旧对象中的对象类型实例变量指的是不同的对象。

      复制对象有两种copy和mutableCopy:

      1.copy用于不可变对象的复制;

      2.mutableCopy复制出来的是可变对象;

     

    自动释放池使用的五种方式

    方式一:

    1. @autoreleasepool{
    2. Book *book = [Book alloc]initWithTitle:@“Object-C” andAuthor:@“Jobs” andPrice:23.90];
    3. [book print];
    4. }

    方式二:

    1. NSAutoreleasePool *pool = [NSAutoreleasePool new];
    2. Book *book = [Book alloc]initWithTitle:@“Object-C” andAuthor:@“Jobs” andPrice:23.90];
    3. [book print];
    4. [pool drain]; //等效于[pool release];

    方式三:

    1. @autoreleasepool 
    2. {
    3.   Book *book = [Book alloc]initWithTitle:@“Object-C” andAuthor:@“Jobs” andPrice:23.90];
    4.   [book print];
    5.   @autoreleasepool
    6.   {
    7.     Book *book = [Book alloc]initWithTitle:@“Object-C” andAuthor:@“Jobs” andPrice:23.90];
    8.     [book print];
    9.   }
    10. }

    方式四

    1. @autoreleaspool
    2. {
    3.   for(int i = 0;i < 10000;i++)
    4.   {
    5.     @autoreleasepool
    6.     {
    7.       Book *book = [Book bookWithTitle:[NSString stringWithFormat:@“book%d”,i+1] andAuthor:[NSString stringWithFormat:@“author%d”,i+1] andPrice:20+i];
    8.       [book print];
    9.     }
    10.   }
    11. }

    方式五:

    1. @autoreleasepool
    2. {
    3.   NSMutableString *str = [NSMutableString stringWithString:@“hello world”];
    4.   NSLog(@“%@“,str);
    5.   /*凡是不通过new、alloc、copy创建的对象都不拥有对象所有权,这种创建的对象会自动加入自动释放池,由自动释放池进行延迟释放*/
    6. }

      下面对自动释放池的使用进行了总结:

      1.在自动释放池结束时会给每个管理的对象发送一次release消息

      2.[book release]的作用是将对象加入自动释放池

      3.在OC的内置类(NSString、NSArray等)中提供的类方法创建的对象实例都是(延迟释放对象),也就是在对象创建完成后将对象加入自动释放池,这种对象不需要我们去发release消息释放。(方法五)

      4.自动释放池是可以嵌套使用的,对象在加入自动释放池时,选择离它最近的释放池,就近原则,(好聪明啊!都知道懒省事),目的是为了让延迟释放的对象,尽快得到释放,降低程序期间内存的占用。(方式3)

      5.当程序中出现大量创建延迟释放对象的代码时,最好给它加一个独立的自动释放池,保证这些对象在不使用时立刻释放掉(方法四)

     
     
     摘录部分(深复制和浅复制)
    3.深复制和浅复制

    浅 复 制:在复制操作时,对于被复制的对象的每一层复制都是指针复制。

    深 复 制:在复制操作时,对于被复制的对象至少有一层复制是对象复制。

    完全复制:在复制操作时,对于被复制的对象的每一层复制都是对象复制。

    1在复制操作时,对于对象有n层是对象复制,我们可称作n级深复制,此处n应大于等于1。

    2对于完全复制如何实现(目前通用的办法是:迭代法和归档),这里后续是否添加视情况而定,暂时不做讲解。

    3、指针复制俗称指针拷贝,对象复制也俗称内容拷贝。

    4、一般来讲,

    浅层复制:复制引用对象的指针。

    深层复制:复制引用对象内容。

    这种定义在多层复制的时候,就显得模糊。所以本文定义与它并不矛盾。反而是对它的进一步理解和说明。           

    retain:始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。

    copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制,引用计数每次加一。始终返回一个不可变对象。

    mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。

    不可变对象:值发生改变,其内存首地址随之改变。

    可变对象:无论值是否改变,其内存首地址都不随之改变。

    引用计数:为了让使用者清楚的知道,该对象有多少个拥有者(即有多少个指针指向同一内存地址)。

  • 相关阅读:
    [ 原创 ] Java基础9--final throw throws finally的区别
    [ 原创 ] Java基础8--什么叫做重载
    [ 原创 ] Java基础7--Java反射机制主要提供了以下哪些功能?
    [ 转载 ] 什么是正则表达式的贪婪与非贪婪匹配
    [ 原创 ] Java基础6--构造函数和抽象类的性质
    [ 转载 ] Java中常用的设计模式
    [ 转载 ] 超详细:常用的设计模式汇总
    [ 转载 ] Java开发中的23种设计模式详解(转)
    [ 原创 ] Java基础5--abstract class和interface的区别
    MetaWeblog API
  • 原文地址:https://www.cnblogs.com/xjf125/p/4730495.html
Copyright © 2020-2023  润新知