OC-基础总结(二)

OC基础总结

重新回过头看这些基础知识,对许多知识点都有新的认识,拥有坚实的基础才能更快的成长。

OC内存管理 - 基础与MRC

内存管理概述

  1. 内存管理 内存的作用:存储数据。 1). 如何将数据存储到内存之中。 声明1个变量,然后将数据存储进去。 2). 当数据不再被使用的时候,占用的内存空间如何被释放。
  2. 内存中的五大区域 栈: 局部变量,当局部变量的作用域被执行完毕之后,这个局部变量就会被系统立即回收。 堆: OC对象,使用C函数申请的空间。需要我们自己进行内存管理 BSS段: 未初始化的全局变量、静态变量。一旦初始化就回收,并转存到数据段之中。 数据段: 已经初始化的全局变量、静态变量。直到程序结束的时候才会被回收。 代码段: 代码,程序结束的时候系统会自动回收存储在代码段中的数据。

栈、BSS段、数据段、代码段存储在它们中的数据的回收,是由系统自动完成的,不需要我们干预。

  1. 分配在堆区中的OC对象,是肯定需要被回收的。 iPhone 内存机制,会在占用内存40M及45M时警告,达到120M时就会直接闪退。 而存储在堆中的OC对象,系统不会自动回收,直到程序结束的时候才会被回收。
  2. 内存管理的范围: 只需要管理存储在堆中的OC对象的回收,其他区域中的数据的回收是系统自动管理的,不需要我们进行管理。
  3. 对象应该什么时候被回收? 当有人使用这个对象的时候,这个对象就千万不能回收。只有在没有任何人使用这个对象的时候,才可以回收。
  4. 引用计数器 1). 每1个对象都有1个属性,叫做retainCount引用计数器,类型是unsigned long占据8个字节。 引用计数器的作用: 用来记录当前这个对象有多少个人在使用它,默认情况下,创建1个对象出来,这个对象的引用计数器的默认值是1。 2). 当多1个人使用这个对象的时候,应该先让这个对象的引用计数器的值+1代表这个对象多1个人使用。 3). 当这个对象少1个人使用的时候,应该先让这个对象的引用计数器的值-1代表这个对象少1个人使用。 4). 当这个对象的引用计数器变为0的时候,代表这个对象无人使用,这个时候系统就会自动回收这个对象。
  5. 如何操作引用计数器. 1). 为对象发送1条retain消息,对象的引用计数器就会加1,当多1个人使用对象的时候才发。 2). 为对象发送1条release消息,对象的引用计数器就会减1,当少1个人使用对象的时候才发。 3). 为对象发送1条retainCount消息,就可以取到对象的引用计数器的值。 4). 对象的引用计数器变为0的时候,对象就会被系统立即回收,在对象被回收的时候,会自动调用对象的dealloc方法。
  6. 内存管理的分类 MRC: Manual Reference Counting 手动引用计数-手动内存管理-当多1个人使用对象的时候,要求程序员手动的发送retain消息,少1个人使用的时候程序员手动的发送relase消息。 ARC: Automatic Reference Counting 自动引用计数-自动内存管理-系统自动的在合适的地方发送retain relase消息。
  7. MRC 1). 新创建1个对象,这个对象的引用计数器的值默认是1。 2). 当对象的引用计数器变为0的时候,对象就会被系统立即回收并自动调用dealloc方法。 3). 为对象发送retain消息,对象的引用计数器就会+1。 4). 为对象发送release消息,并不是回收对象,而是让对象的引用计数器-1 当对象的引用计数器的值变为0的时候,对象才会被系统立即回收。 MRC中重写dealloc方法的规范: 必须要调用父类的dealloc方法,并且要放在最后一句代码。
  8. 内存管理的重点及原则 1). 有对象的创建,就要匹配1个release。 2). retain的次数和release的次数要匹配。 3). 谁用谁retain,谁不用谁release,谁负责retain谁就负责relase。 4). 只有在多1个人用的时候才retain,少1个人使用的时候才release。有加就有减,有retain就应该匹配1个release一定要平衡。 5). 在ARC机制下,retain release dealloc这些方法方法无法调用.
  9. 野指针 C语言中的野指针: 定义1个指针变量,没有初始化,这个指针变量的值是1个垃圾值,指向1块随机的空间,那么这个指针就叫做野指针。 OC中的野指针: 指针指向的对象已经被回收了,这样的指针就叫做野指针。
  10. 对象及内存回收的本质 申请1个变量,实际上就是向系统申请指定字节数的空间,这些空间系统就不会再分配给别人了。当变量被回收的时候,代表变量占用的字节空间从此以后系统可以分配给别人使用了。但是字节空间中存储的数据还在。 所谓的对象的回收,指的是对象占用的空间可以分配给别人。当这个对象占用的空间没有分配给别人之前 其实对象数据还在.
  11. 僵尸对象 1个已经被释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。 我们通过野指针去访问僵尸对象的时候,如果僵尸对象占用的空间还没有分配给别人的时候,这时是可以的,而当僵尸对象占用的空间分配给了别人使用的时候,是万万不可的。 因此只要对象成为了僵尸对象,无论如何都不允许访问了。 Xcode提供了僵尸对象的实时检查机制,可以将这个机制打开,打开之后,只要访问的是僵尸对象,无论空间是否分配就会报错。
  12. 为什么不默认打开僵尸对象检测. 一旦打开僵尸对象检测那么在每访问1个对象的时候,都会先检查这个对象是否为1个僵尸对象。这样是极其消耗性能的。 打开僵尸对象检测:Product -> Scheme -> EditScheme -> Diagnostict -> Zombie Objects
  13. 使用野指针访问僵尸对象会报错,如何避免僵尸对象错误。 当1个指针称为野指针以后,将这个指针的值设置nil。 当1个指针的值为nil,通过这个指针去调用对象的方法(包括使用点语法)的时候,不会报错,只是没有任何反应,但是如果通过直接访问属性 -> 就会报错。
  14. 内存泄露 指的是1个对象没有被及时的回收,在该回收的时候而没有被回收,一直驻留在内存中,直到程序结束的时候才回收。 单个对象的内存泄露的情况: 1). 有对象的创建,而没有对应的relase。 2). retain的次数和relase的次数不匹配。 3). 在不适当的时候,为指针赋值为nil。 4). 在方法中为传入的对象进行不适当的retain。
  15. 当属性是1个OC对象的时候 setter方法的写法 将传进来的对象赋值给当前对象的属性,代表传入的对象多了1个人使用,所以我们应该先为这个传入的对象发送1条retain消息再赋值。 当为对象的这个属性多次赋值的时候,代表旧对象少1个人用,新对象多1个人使用,应该relase旧的 retain新的。 当当前对象销毁的时候,代表属性指向的对象少1个人使用,就应该在dealloc中relase。 代码写法:
     - (void)setCar:(Car *)car
     {
         if(_car != car)
         {
            [_car release];
            _car = [car retain];
         }
     }
 
     还要重写dealloc方法.
     - (void)dealloc
     {
        [_car release];
        [super delloc];
     }

@property

  1. @property 作用 1). 自动生成私有属性。 2). 自动生成这个属性的getter setter方法的声明。 3). 自动生成这个属性的getter setter方法的实现。
  2. @property参数 1). @property可以带参数的. @property(参数1,参数2,参数3......)数据类型 名称; 2). 介绍一下@property的四组参数.

与多线程相关的两个参数 nonatomic - atomic 与生成的setter方法的实现相关的参数 assign - retain 与生成只读、读写相关的参数 readonly - readwrite 是与生成的getter setter方法名字相关的参数 getter - setter

3). 介绍与多线程相关的参数.

atomic: 默认值,如果写atomic,这个时候生成的setter方法的代码就会被加上一把线程安全锁。 特点: 安全、但是效率低下。 nonatomic: 如果写nonatomic这个时候生成的setter方法的代码就不会加线程安全锁。 特点: 不安全,但是效率高。

4). 与生成的setter方法的实现相关的参数。

assign: 默认值,生成的setter方法的实现就是直接赋值。 retain: 生成的setter方法的实现就是标准的MRC内存管理代码,也就是先判断新旧对象是否为同1个对象,如果不是 release旧的 retain新的。 当属性的类型是OC对象类型的时候,那么就使用retain。 当属性的类型是非OC对象的时候,使用assign。 千万注意: retain参数,只是生成标准的setter方法为标准的MRC内存管理代码,不会自动的再dealloc中生成relase的代码。所以,还要自己手动的在dealloc中release。

5). 与生成只读、读写的封装。

readwrite: 默认值,代表同时生成getter setter。 readonly: 只会生成getter,不会生成setter

6). 生成getter、setter方法名称相关的参数。

默认情况下@property生成的getter setter方法的名字都是最标准的名字。同时我们可以通过参数来指定@property生成的方法的名字。

getter = getter方法名字 用来指定@property生成的getter方法的名字。 setter = setter方法名字 用来指定@property生成的setter方法的名字。注意.setter方法是带参数的所以要加1个冒号。 注意:如果使用getter setter修改了生成的方法的名字,在使用点语法的时候,编译器会转换为调用修改后的名字的代码。

注意:无论什么情况都不要改setter方法的名字,因为默认情况下生成的名字就已经是最标准的了。 当属性的类型是1个BOOL类型的时候,可以修改这个getter的名字以is开头以提高代码的阅读性。

7). 同1组参数只能使用1个,参数的顺序可以随意。

@class与#import的区别

  1. 当两个类相互包含的时会出现循环引用的问题,造成无限递归,而导致无法编译通过。
  2. 解决方案: 其中一边不要使用#import引入对方的头文件,而是使用@class 类名;来标注这是1个类,这样就可以在不引入对方头文件的情况下,告诉编译器这是1个类。 在.m文件中再#import对方的头文件即可。
  3. 区别 1). #import是将指定的文件的内容拷贝到写指令的地方。 2). @class 并不会拷贝任何内容,只是告诉编译器,这是1个类,这样编译器在编译的时候才可以知道这是1个类。

OC内存管理 - ARC与分类

  1. 自动释放池的原理 存入到自动释放池中的对象,在自动释放池被销毁的时候,会自动调用存储在该自动释放池中的所有对象的release方法。 可以解决的问题: 将创建的对象,存入到自动释放池之中,就不再需要手动的relase这个对象了,因为池子销毁的时候,就会自动的调用池中所有的对象的relase。 自动释放池的好处: 将创建的对象存储到自动释放池中,不需要再写release。
  2. 如何创建自动释放池 @autoreleasepool{ } //这对大括弧代表这个自动释放池的范围。
  3. 如何将对象存储到自动释放池之中 在自动释放池之中调用对象的autorelease方法,就会将这个对象存入到当前自动释放池之中。 //autorealse方法返回的是对象本身。 @autoreleasepool{ Person *p1 = [[[Person alloc] init] autorelease]; } 当这个自动释放池执行完毕之后,就会立即为这个自动释放池中的对象发送1条release消息。
  4. 使用注意 1). 只有在自动释放池中调用了对象的autorelease方法,这个对象才会被存储到这个自动释放池之中,如果只是将对象的创建代码写在自动释放之中,而没有调用对象的autorelease方法,是不会将这个对象存储到这个自动释放池之中的。 2). 对象的创建可以在自动释放池的外面,在自动释放池之中,调用对象的autorelease方法,就可以将这个对象存储到这个自动释放池之中。 3). autorelease在外面是无法将对象存在自动释放池之中的,当自动释放池结束的时候,仅仅是对存储在自动释放池中的对象发送1条release消息,而不是销毁对象。 4). 如果在自动释放池中,调用同1个对象的autorelease方法多次,就会将对象存储多次到自动释放池之中。在自动释放池结束的时候,会为对象发送多条release消息,那么这个是就会出现僵尸对象错误。所以,1个自动释放池之中,只autorelease1次,只将这个对象放1次,否则就会出现野指针错误。 5). 如果在自动释放池中,调用了存储到自动释放池中的对象的release方法,在自动释放池结束的时候,还会再调用对象的release方法。这个时候就有可能会造成野指针操作。 也可以调用存储在自动释放池中的对象的retain方法. 6). 将对象存储到自动释放池,并不会使对象的引用计数器+1 所以其好处就是:创建对象将对象存储在自动释放池,就不需要在写个release了。 省略创建对象匹配的那个release 7). 自动释放池可以嵌套 调用对象的autorelease方法,会将对象加入到当前自动释放池之中,只有在当前自动释放池结束的时候才会像对象发送release消息。
  5. autorelease的规范 使用类方法创建的对象,要求这个对象在方法中就已经被autorelease过了,这样,我们只要在自动释放池中,调用类方法来创建对象,那么创建的对象就会被自动的加入到自动释放池中。 而使用对象方法不会自动autorelease。 提供1个类方法来快速的得到1个对象. 规范 a. 这个类方法以类名开头. 如果没有参数就直接是类名 如果有参数就是 类名WithXX: b. 使用类方法得到的对象,要求这个对象就已经被autorelease过了. + (instancetype)person { return [[[self alloc] init] autorelease]; } 这样,我们直接调用类方法.就可以得到1个已经被autorelease过的对象. @autoreleasepool { Person *p1 = [Person person]; //这个p1对象已经被autorelase过了.不需要再调用autorelase //这个p1对象就被存储到当前自动释放池之中. }//当自动释放池结束.就会为存储在其中的p1对象发送release消息.

Apple的框架中的类也是遵守这个规范的。通过类方法创建的对象都是已经被autorelease过的了。 所以,我们也要遵守这个规范,类方法返回的对象也要被autorealse过。我们凡是创建对象是调用类方法创建的对象,这个对象已经是被autorelease过的。

ARC

  1. ARC - Automatic Reference Counting,自动引用计数,即ARC。系统自动的帮助我们去计算对象的引用计数器的值。 在程序中使用ARC非常简单,只需要像往常那样编写代码,只不过永远不要写retain、release、autorelease 永远不要手动的调用 dealloc 这是ARC的最基本的原则。 特别注意的是ARC是编译器机制,当ARC开启时,编译器会自动的在合适的地方插入retain、release、autorelase代码,自动为对象做引用计数。
  2. ARC机制下,对象何时被释放 本质: 对象的引用计数器为0的时候,自动释放。 表象: 只要没有强指针指向这个对象,这个对象就会立即回收。
  3. 强指针与弱指针 强指针: 默认情况下,声明1个指针,这个指针就是1个强指针。 我们也可以使用__strong来显示的声明这是1个强指针。 Person *p1; // 这是1个强指针. 指针默认情况下都是1个强指针。 __strong Person *p2; // 这也是1个强指针.使用__strong来显示的声明强指针。 弱指针: 使用__weak标识的指针就叫做弱指针。 无论是强指针还是弱指针,都是指针,都可以用来存储地址,这1点没有任何区别,都可以通过这个指针访问对象的成员。 唯一的区别就是在ARC模式下,他们用来作为回收对象的基准。 如果1个对象没有任何强类型的指针指向这个对象的时候,对象就会被立即自动释放

ARC下的单个对象的内存管理

在ARC的机制下: 当1个对象没有任何的强指针指向它的时候,这个对象就会被立即回收。

1). 当1个对象没有任何的强指针指向它的时候,这个对象就会被立即回收。

    int main(int argc, const char * argv[])
    {
        @autoreleasepool
        {
           Person *p1 = [Person new];//p1是1个强指针.
           Person *p2 = p1;//p2也是个强指针.p1和p2都指向Person对象.
           //因为我们说过,每1个指针变量默认情况下都是1个强指针变量.
           NSLog(@"------");
        }//当执行到这里的时候.p1指针被回收,p2指针也被回收.那么Person对象就没有任何
        //强指针指向它了. 对象就在这被回收.
        return 0;
    }

2).将所有指向对象的强指针赋值为nil的时候.对象就会被立即回收.

 int main(int argc, const char * argv[])
 {
     @autoreleasepool
     {
         Person *p1 = [Person new];//p1是1个强指针.
         //因为我们说过,每1个指针变量默认情况下都是1个强指针变量.        
         p1 = nil;//当执行到这句话的时候.p1赋值为nil.
         //p1指针不再指向Person对象.
         //Person对象没有被任何的指针所指向,所以.Person对象在这里被释放.
         NSLog(@"------");
     }
     return 0;
 } 
 这两种情况就叫做没有任何强指针指向对象.
 1). 指向对象的所有强指针被回收掉
 2). 指向对象的所有的强指针赋值为nil

ARC机制下释放1个对象的标准是: 没有任何强指针指向对象的时候,对象就会被释放,如果这个时候有弱指针指向,也会被释放.

 int main(int argc, const char * argv[])
 {
     @autoreleasepool
     {
         //使用__strong来标识p1指针是强类型的,其实不写__strong也是强类型的.
         __strong Person *p1 = [[Person alloc] init];
         
         //使用__weak标识指针p2的类型是弱类型指针.
         __weak Person *p2 = p1;
         //这个时候,p2指针和p1指针都指向Person对象.
         
         //这个时候如果设置p1的值为nil
         p1 = nil;
         //这个时候Person对象只有被1个弱指针p2指向,没有任何强指针指向
         //所以Person对象在这里被回收.
     }
     return 0;
 }

3). 最重要的1点:不能创建对象用1个弱指针存储这个对象的指针,这样的话,刚创建出来的对象,就没有任何强指针指向,创建出来就会被回收。

 int main(int argc, const char * argv[])
 {
     @autoreleasepool
     {
         //创建1个对象,将这个对象的地址赋值给1个弱指针
         //后果就是创建出来的这个对象没有被任何强指针指向.
         //刚创建出来就会被释放.
         __weak Person *p1 = [[Person alloc] init];
         
     }
     return 0;
 }

4). 在ARC机制下,当对象被回收的时候,原来指向这个对象的弱指针会被自动设置为nil。

 Person *p1 = [Person new];
 __weak Person p2 = p1;
 p1 = nil;
 [p2 sayHi];

ARC机制下的重点

当1个类的属性是1个OC对象的时候,这个属性应该声明为强类型的还是弱类型的。很明显,应该声明为1个强类型的。 使用参数,strong和weak控制@property生成的私有属性是1个强类型的还是1个弱类型的。

 @property(nonatomic,strong)Car *car;
 代表生成的私有属性_car 是1个强类型的. 
 @property(nonatomic,weak)Car *car;
 代表生成的私有属性_car 是1个弱类型的.

如果不写,默认是strong.

使用建议 1). 在ARC机制下,如果属性的类型是OC对象类型的,绝大多数场景下使用strong。 2). 在ARC机制下,如果属性的类型不是OC对象类型的,使用assign。 3). strong和weak都是应用在属性的类型是OC对象的时候,属性的类型不是OC对象的时候就使用assign。 4). 在ARC机制下,当两个对象相互引用的时候,如果两边都使用strong 那么就会内存泄露。解决方案: 1端使用strong 1端使用weak。 5). 使用命令-fno-objc-arc设置部分类使用MRC。 6). 可以将整个MRC程序,转换为ARC程序; Edit -> Convert ->To Objective-C ARC (不建议使用)

分类 - category

将1个类分为多个模块 使用分类的几个注意点:

  1. 分类只能增加方法,不能增加属性
  2. 在分类之中可以写@property 但是不会自动生成私有属性,也不会自动生成getter setter的实现,只会生成getter setter的声明。 所以需要自己写getter 和 setter的声明,如果也需要自己定义属性,这个属性就必须在本类中。
  3. 在分类的方法实现中不可以直接访问本类的真私有属性(定义在本类的@implementation之中),但是可以调用本类的getter setter来访问属性。
  4. 分类中可以存在和本类同名方法的 当分类中有和本类中同名的方法的时候,优先调用分类的方法,哪怕没有引入分类的头文件。 如果多个分类中有相同的方法,优先调用最后编译的分类。
  5. 什么时候需要使用分类 当1个类的方法很多很杂的时候,当1个类很臃肿的时候。那么这个时候我们就可以使用分类,将这个类分为多个模块,将功能相似的方法写在同1个模块之中。
  6. 非正式协议 - 为系统自带的类写分类就叫做非正式协议。

ARC机制与垃圾回收机制的区别

垃圾回收机制 - GC: 程序在运行的期间,有1个东西叫做垃圾回收器,不断的扫描堆中的对象是否无人使用。 ARC: 不是运行时,而在编译的时候就在合适的地方插入retain 等操作,插入的代码足以让对象无人使用的时候,引用计数器为0,对象被回收。


文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

C/C++中inline用法详解

(一)inline函数(摘自C++ Primer的第三版) 在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。       in...

2643
来自专栏python3

python 列表(List)

Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。

1231
来自专栏osc同步分享

Timer类的schedule()方法

timer.schedule(new MyTask(),long time1,long timer2); 第一个参数,是 TimerTask 类,在包:impo...

35211
来自专栏编程

python的函数(二):作用域

我们在写函数时,时常需要引用全局的变量,或对全局变量赋值。又或者偶尔遇到局部变量与全局变量同名。在处理这些问题时,python语言的游戏规则是怎样的?今天我们就...

1885
来自专栏安恒网络空间安全讲武堂

二进制学习系列-堆溢出

在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。 对于子类,最开始的内存...

1723
来自专栏架构之路

JAVA基础知识点:内存、比较和Final

1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题。(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对...

4314
来自专栏java一日一条

深入浅出JavaScript之闭包(Closure)

闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实...

942
来自专栏Crossin的编程教室

【Python 第67课】函数的参数传递(1)

本篇面向读者:有一点点 Python 基础 关键字:函数,参数,默认值 先说下上次课最后留的那题,我自己的解法: print ';'.join([str(i) ...

2855
来自专栏前端知识分享

第205天:面向对象知识点总结

JSON全称为JavaScript对象简单表示法(JavaScript Object Notation)

883
来自专栏Hongten

J2SE 接口 [interface]

/**  * 接口  *  接口 [interface]是抽象方法和常量值的定义的集合  *    *  从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含...

822

扫码关注云+社区