美文网首页
壹、面试复习之OC篇(二)

壹、面试复习之OC篇(二)

作者: 默默_David | 来源:发表于2018-03-18 13:25 被阅读34次

    壹、面试复习之OC篇(一)

    13.内存管理

      每个OC对象里都有一个引用计数器,用来统计正在被引用多少次,每个引用计数器占4个字节,对象刚刚创建时引用计数器默认为1。如果OC对象引用计数器为0时,系统就可以回收这个对象了。

     引用计数器的操作:

      (1).给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)

      (2).给对象发送一条release消息,可以使引用计数器-1

      (3).给对象发送一条retainCount消息,可以获得当前的引用计数器值

     对象的销毁:

      (1).当一个对象的引用计数器的值为0时,那么它将被销毁,其占用的内存会被系统回收

      (2).当一个对象被销毁时,系统会自动向对象发送一条dealloc消息

      (3).一般会重写dealloc方法,在这里释放相关资源,dealloc就像是对象的遗言

      (4).一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用(ARC中不需要调用[super dealloc],因为系统会帮我们做)

      (5).不要直接调用dealloc方法,它只能重写

      (6).一旦对象被回收了,它占用的内存不再可用,坚持使用就会导致程序崩溃(因为已经是僵尸对象)

     概念:

      僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用

      野指针:指向僵尸对象的指针,给野指针发送消息会报错

      空指针:没有指向任何东西的指针(存储的东西是nil,null,0),给空指针发送消息不会报错(所以如果一个对象内存已经被释放的话,一定要将它的指针置为空指针,以免出现野指针)

      备注:nil和null不同,null是一个宏定义,值为0,nil表示无值,任何指针变量在没有赋值之前都是nil,对于真假判断,只有nil和false表示假,其他皆为真

     内存管理原则:

      (1).谁创建,谁release:如果你通过alloc、new或者[mutable]copy来创建一个对象,那么必须调用release或autorelease

      (2).谁retain,谁release: 只要你调用了retain,无论这个对象如何生成的,你都要调用release

    14.setter方法内存管理

      Setter方法中,会对一个retain修饰的属性进行retain操作,那么,我们必须保证在对象销毁前,对这个属性进行release操作.

      比如一个人person有一个属性book:

    - (void)dealloc{

        [_book release];//b引用计数器减1

        [super dealloc];//这个方法写在最后

    }

    - (void)test{

        Person *p = [Person new];//alloc,p引用计数器为1

        Book *b = [Book new];//alloc,b引用计数器为1

        p.book = b;//调用setter方法,b引用计数器为2

        [p release];//p应用计数器0,将被系统回收,系统会给p发送dealloc消息

        p = nil;//置为空指针,防止野指针出现

        /**

         由于调用setter方法时,p对b进行了引用计数器加1的操作,那么我们得在dealloc中将b的引用计数器减1

         */

    }

    - (void)setBook:(Book *)book{

        //判断是否传进来的是旧对象

        if (_book != book) {

            [_book release];//旧对象引用计数器减1

            _book = book.retain;//新对象引用计数器+1

        }

    }

    在工程中同时支持ARC与MRC,方法:

      在Build Phase里面的Compile Source中找到需要特殊处理的文件,双击加上编译选项(Complier Flags):

      (1)项目是ARC,文件需要支持MRC,那么加上”-fno-objc-arc”

      (2)项目是MRC,文件需要支持ARC,那么加上”-fobjc-arc”

    如果对于代码中要分别处理ARC和MRC,可以加上条件编译这样写:

    - (void)dealloc{

    #if !__has_feature(objc_arc)//下方写MRC的处理

        [_book release];//b引用计数器减1

        [super dealloc];//这个方法写在最后

    #else

        /**这里写ARC时候的处理 */

    #endif

    }

    15.@class

    @class是对一个类的声明,它仅仅是告诉编译器有这么一个类。它主要用于在一个类的.h文件中声明另一个类,防止两个类同时在.h文件中#import对方造成的循环拷贝.

    在实际开发中,使用如下:

      (1).在.h文件中用@class来声明类

      (2).在.m文件中#import类

    16.两个类的循环引用

     在实际开发中,两个类中会出现都要声明对方为自己的属性的情况,如果都用strong/retain,那么会出现”你包含我,我包含你”问题,在对对象发送release消息时,会出现占用内存永远回收不了的问题。解决方法:

    -个修饰符用strong/retain,一个修饰符用weak

    17.autorelease(自动释放池)

    自动释放池是通过以AutoreleasePoolPage为节点的双向链表来实现的。它的行为和栈类似(先进后出)

    autorelease的基本用法:

      (1).会将对象放到一个自动释放池中

      (2).当自动释放池被销毁时,会对池子里面的所有对象做一次release操作

      (3).会返回对象本身

      (4).调用完autorelease方法后,对象的引用计数器值不变

      在ios5.0之前,autorelease写法为

        NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc]init];

        //这里会将该对象加入最新创建的自动释放池里面

        Person *p = [[Person new]autorelease];

        [autoreleasePool release];

      在ios5.0之后

        @autoreleasepool

        {//此处创建自动释放池

            Person *p = [[Person new]autorelease];

        }//此处销毁自动释放池

    autorelease好处:

      (1).不用再关心对象释放的时间

      (2).不用再关心什么时候调用release

    autorelease使用注意:

      (1).占用内存交大的对象不要随便使用autorelease

      (2).占用内存较小的对象使用autorelease,没有太大影响

    autorelease错误用法

      (1).alloc之后调用了autorelease,又调用release

        @autoreleasepool

        {

            Person *p = [[Person new]autorelease];

            [p release];//错误,它会自动释放

        }

      (2).连续调用多次autorelease

        @autoreleasepool

        {

            Person *p = [[[Person new]autorelease]autorelease];//错误,只能调用一次autorelease

        }

    自动释放池

      (1).在ios程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)

      (2).当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池

    备注:系统自带的方法中,如果不包含alloc、new、copy,那么这些方法返回的对象都是已经autorelease过的,比如:

        [NSString stringWithFormat:@""];

        [NSDate date];

      开发中经常写一些类方法快速创建一个autorelease对象(创建对象的时候不要直接使用类名,使用self),例如:

    //以后可以只用使用这个类方法类创建对象

    + (instancetype)person{

        return [[[self alloc]init]autorelease];

    }

    18.ARC(内存回收)

    ARC是编译器特性,可以理解为XCode的功能,它主要用来帮助用户做内存管理工作。

    ARC判断准则:

      只有没有强指针指向对象,就会释放对象。同时系统还会根据弱指针的情况及时释放弱指针对象,避免野指针的产生

    指针分两种:

      (1).强指针:默认情况下,所有指针都是强指针(__strong,属性修饰符为strong)

      (2).弱指针:定义一个指针是弱指针,只需要在定义钱加上__weak声明即可(属性修饰符为weak)

    ARC特点:

      (1).不允许调用release、retain、retainCount

      (2).允许重写dealloc,但是不允许调用[super dealloc]

      (3).@property的参数

          ①strong:成员变量是强指针(适用于OC对象类型)

          ②weak:成员变量是弱指针(适用于OC对象类型)

          ③assign:适用于非OC对象类型

      (4).以前的retain改为用strong

    19.Block

    定义:

    返回值类型 (^block名称)(参数表) =

        ^(参数表){

          //代码块部分

        };

        int (^sumBlock)(int,int) = ^(int a,int b){

            return a+b;

        };

        int sum = sumBlock(10,2);

    Block和函数很像,Block特点:

      (1).可以保存代码

      (2).可以有返回值

      (3).可以有形式参数

      (4).调用方式和调用函数一样

      (5).可以像方法一样调用外面的变量

      (6).默认条件下,Block不能修改外面的局部变量,但是在变量名签名加上__block修饰就可以使Block代码块中修改这个变量。(Block默认不能修改局部变量,但是Block可以修改全局变量)

      (7).实际开发中,常用typedef定义Block以减少代码量

    typedef int (^MyBlock) (int,int);

    @property (nonatomic,copy) MyBlock block;

        //调用

        self.block = ^int(int a, int b) {

            return a % b;

        };

    备注:当block要用到外卖对象的指针的时候,要把这个指针使用__weak或者__unsafe_unretained声明为弱指针,否则会出现循环引用的问题,声明方法:

        //使用__weak声明,不能用在MRC中,否则会报错

        __weak typeof(self) weakSelf = self;

        //使用__unsafe_unretained声明

        __unsafe_unretained typeof(self) unRetainedSelf = self;

    后面在Block代码块中调用就使用弱指针,就不会出现循环引用了

    20.block底层原理

    关于Block底层实现原理可以看看王威大神的这篇文章:Block技巧与底层解析

    大致的信息如下:

    Block的底层结构为:

    /* Revised new layout. */

    struct Block_descriptor{

        unsigned long int reserved;

        unsigned long int size;

        void (*copy)(void *dst, void *src);

        void (*dispose)(void *);

    };

    struct Block_layout {

        void *isa; int flags;

        int reserved;

        void (*invoke)(void *, ...);

        struct Block_descriptor *descriptor;

        /* Imported variables. */

    };

      (1).block底层实现为一个一个结构体,其中有isa指针,所以block也是一个对象(runtime里面,对象和类都是用结构体表示,并且都有isa指针)

      (2).invoke为block执行时调用的函数指针,记录函数地址,block定义时内部的执行代码都在它指向的函数中

      (3).flags为标志变量,在实现block内部操作时会用到;reserved为保留变量

      (4).Block_descriptor是对block的详细描述

          .copy/dispose,辅助拷贝/销毁函数,处理block范围外的变量时使用

    总结:block就是一个里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体

    Block的类型:(类型看的是isa指针指向哪种)

      (1)._NSConcreteGlobalBlock  全局,创建的位置为全局区

      (2)._NSConcreteStackBlock  栈  创建的位置在函数体内

      (3)._NSConcreteMallocBlock  堆  这种不能直接创建,只能由栈类的block拷贝而来,他会调用_Block_copy_internal函数类拷贝,这个函数的实现中使用memove将栈中的block的内存拷贝到堆中,并将新拷贝过来的block的isa指针指向_NSConcreteMallocBlock

    捕捉变量对block结构的影响:

    局部变量

        int a = 5;

        __block typeof(a) blockA = a;

        ^{blockA = 10;};

      如果直接将a传入block,不能修改,原因是block的实现中,定义了一个和a同类型的成员变量来存储外部变量a的值,这次的拷贝是一个值传递,因为作用域不同,所以直接对a赋值是没有意义的,所以编译器给了错误。所以我们要使用__blcok修饰局部变量

    全局变量

      由于全局变量都是在静态数据存储区,在程序结束前不会被销毁,所以block直接访问到了对应的变量,没有创建中间变量,所以全局变量可以直接在block中进行赋值等修改操作

    局部静态变量

      静态变量和全局变量一样,都是存储在静态数据存储区,和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是要注意的是,局部静态变量的作用域还是在定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问

    __block修饰的变量

      使用__block修饰后,在实现中会创建一个结构体b来包装局部变量a,在block被拷贝到堆中时,拷贝辅助函数会将这个结构体拷贝到堆中,堆中结构体b的__forwarding指针指向自身,栈中的__forwarding指针指向堆中的拷贝,这样就可以保证操作的值始终是堆中的拷贝,而不是栈中的值,这样也就可以对局部变量进行修改了

    相关文章

      网友评论

          本文标题:壹、面试复习之OC篇(二)

          本文链接:https://www.haomeiwen.com/subject/uocsqftx.html