Foundation => Objective-C _ Part4


2016-4-29 第1次修改
2016-4-27 创建


1. 内存管理

  1. 堆和栈
    • 栈 (操作系统) : 由操作系统自动分配释放, 存放函数的参数值(形参),局部变量的值等. 其操作方式类似于数据结构中的栈 (先进后出)
    • 堆 (操作系统) : 一般由程序员分配释放, 若程序员不释放, 程序结束时可能由OS回收, 分配方式类似于链表
    • 只要是alloc init创造的, 都放在堆里
    • OC对象存放在堆里,堆里的数据系统不会自动释放, 需要手动释放
    • 非OC对象一般放在栈里, 栈内存会被系统自动回收
  2. 内存泄漏 - - - 如果一个OC对象,占据了一块内存, 但又没有被使用. 那么这个现象就是内存泄露
  3. 引用计数器的作用 - - - 引用计数器表示有多少人正在使用这个对象
  4. 引用计数器的操作
    • 对象被创建, 引用计数器初始就是1
    • 给对象发送一条retain消息, 引用计数器就+1
    • 给对象发一条release消息, 引用计数器 - 1
    • 给对象发retainCount, 可以获得当前引用计数器的值 (获得的值并不准确)
  5. dealloc
    • 当一个对象引用计数器为0, 系统会自动给对象发一条dealloc消息.(因此从dealloc方法有没有被调用, 就知道对象是否被销毁)
    • 一般会dealloc方法的重写, 在这里释放相关的资源, dealloc就是对象的遗言
    • 一旦重写了dealloc方法, 就必须调用[super dealloc] 并且放在最后调用
    • 不能直接调用dealloc方法
    • 一旦对象被回收了, 它占用的内存就不可再用, 坚持使用会导致程序崩溃(野指针错误)
  6. 野指针和空指针
    • 野指针
      • 只要一个对象被释放了, 我们就称这个对象为 “僵尸对象”
      • 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针
      • 只要给一个野指针发送消息就会报错
      • EXC_BAD_ACCESS: 坏内存访问(已经被回收, 已经不可用的内存) ,也叫 “野指针错误”
    • 空指针
      • 为了避免给野指针发送消息会报错, 一般情况下, 当一个对象被释放后我们会将这个对象的指针设置为空指针
      • 因为在OC中给空指针发送消息是不会报错的
  7. ARC 和 MRC

    • ARC : Automatic(自动) Reference(引用) Counting(计数)

      • 什么是自动引用计数?
      • 不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码
      • 注意点: OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干的, 而OC中的ARC是编译器干的
    • MRC: Manul(手动) Reference(引用) Counting(计数)

      • 什么是手动引用计数?
      • 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
  8. 内存管理的原则 - - - 有加就有减
    • 例如: 一次alloc对应一次release, 一次retain对应一次relese
  9. 多对象内存管理
    • 当A对象想使用B对象一定要对B对象进行一次retain, 这样才能保证A对象存在B对象就存在, 也就是说这样才能保证无论在什么时候在A对象中都可以使用B对象
    • 当A对象释放的时候, 一定要对B对象进行一次release, 这样才能保证A对象释放了, B对象也会随之释放, 避免内存泄露
    • 总结一句话: 有增就有减
  10. set方法的内存管理
// set方法的实现
- (void)setCar:(Car *)car{
    if (car != _car){
        [_car release];
        _car = [car retain];
    }
}

// dealloc方法的实现 (不要直接调用dealloc)
- (void)dealloc{
    [_car dealloc];
    [super dealloc];
}

2. property修饰符

  1. 相同类型的property修饰符不能同时使用
  2. 不同类型的property修饰符可以多个结合在一起使用, 多个之间用,号隔开
  3. iOS开发中只要写上property, 那么就立刻写上nonatomic
  4. 多线程
    • atomic:性能低(默认)
    • nonatiomic:性能高
    • 在iOS开发中99.99%都是写nonatomic
readonly只会生成getter方法
readwrite: 既会生成getter也会生成setter, 默认什么都不写就是readwrite
getter: 可以给生成的getter方法起一个名称
setter: 可以给生成的setter方法起一个名称
retain: 就会自动帮我们生成getter/setter方法内存管理的代码
assign: 不会帮我们生成set方法内存管理的代码, 仅仅只会生成普通的getter/setter方法, 默认什么都不写就是assign

3. @class

  • 提高编译效率
    #import"Car.h"
    - 由于import是一个预编译指令, 他会将""中的文件拷贝到import所在的位置
    - 并且import有一个特点, 只要""中的文件发生了变化, 那么import就会重新拷贝一次(更新操作)

    @class Car;
    @class仅仅是告诉编译器, @class后面的名称是一个类, 不会做任何拷贝操作

    注意 : 由于@class仅仅是告诉编译器后面的名称是一个类, 所以编译器并不知道这个类中有哪些属性和方法, 所以在.m中使用这个类时需要import这个类, 才能使用

    总结 : 
    1. 如果都在.h中import, 假如A拷贝了B, B拷贝了C ,  如果C被修改了, 那么B和A都需要重新拷贝. 因为C修改了那么B就会重新拷贝, 而B重新拷贝之后相当于B也被修改了, 那么A也需要重新拷贝. 也就是说如果都在.h中拷贝, 只要有间接关系都会重新拷贝
    2. 如果在.h中用@class, 在.m中用import, 那么如果一个文件发生了变化, 只有和这个文件有直接关系的那个文件才会重新拷贝
    3. 所以在.h中用@class可以提升编译效率
  • 相互拷贝变为可行
    • 如果两个类相互拷贝, 例如A拷贝B, B拷贝A, 这样会报错
    • 如何解决: 在.h中用@class, 在.m中用import
    • 因为如果.h中都用import, 那么A拷贝B, B又拷贝A, 会形成死循环
    • 如果在.h中用@class, 那么不会做任何拷贝操作, 而在.m中用import只会拷贝对应的文件, 并不会形成死循环

4. 循环retain

  1. 如果A对用要拥有B对象, 而B对应又要拥有A对象, 此时会形成循环retain
  2. 如何解决这个问题: 不要让A retain B, B retain A
  3. 让其中一方不要做retain操作即可

5. autoRelease

  1. autorelease的基本用法
    1. 会将对象放到一个自动释放池中
    2. 当自动释放池被销毁时,会对池子里所有的对象做一次release操作
    3. 会返回对象本身
    4. 调用完autorelease方法后, 对象的计数器不变
    5. 一个程序中可以创建多个自动释放池, 并且自动释放池还可以嵌套
  2. autorelease的好处
    1. 不用再关心对象释放的时间
    2. 不用再关心什么时候调用release
  3. autorelease的使用注意
    1. 占用内存较大的对象不要随便使用autorelease
    2. 占用内存较小的对象使用autorelease, 没有太大影响
    3. 一个alloc / new 对应一个autorelease 或者 release
    4. 如果写了autorelease,就不要写release
    5. 系统自带的方法里没有包含alloc/ new/ copy, 说明返回的对象都是autorelease的
  4. 自动释放池
    1. 在IOS程序运行过程中, 会创建无数个池子. 这些池子都是以栈结构存在 (栈特点: 先进后出)
    2. 当一个对象调用autorelease方法时, 会将这个对象放到栈顶的释放池
  5. 错误写法
    // (1) alloc之后调用了autorelease, 又调用release
    @autorelease{
        Person *p = [[[Person alloc]init] autorelease];
        [p release];
    }

    // (2) 连续调用多次autorelease
    @autorelease{
        Person *p = [[[[Person alloc]init] autorelease]autorelease];
    }

6.ARC

  • ARC的判断标准: 只要没有强指针指向对象, 对象就会被释放
  • 默认情况下所有的指针都是强指针
    __strong Person *p = [[Person alloc]init];      //这个是强指针创建的
    __weak  Person *p = [[Person alloc]init];       //这个是弱指针创建的,  会被立即释放
  • 在ARC中如果保存对象, 不要用assign, 要用weak - - - assign是专门用于保存基本数据类型的. 保存对象要用
  • ARC不允许调用release \ retain \ retainCount
  • ARC允许重写dealloc, 但不允许调用[super dealloc]
  • ARC和MRC的区别
    • MRC, A对象想要拥有B对象, 需要对B对象进行一个retain
      • A对象不用B对象了, 需要对B对象进行一个release
      • property的时候进行retain, dealloc的时候进行release
    • ARC, A对象想拥有B对象, 需要用一个强指针指向B对象
      • A对象不用B对象了, 什么都不用做. 编译器自动帮们我做
    • 在ARC中保存一个对象用strong, 相当于MRC中的retain

7.分类Category

  • 基本概念
    • 只能在类别中添加方法, 但不能添加变量
    • 写法
        @interface ClassName (CategoryName)  NewMethod;
        1. ClassName  分类的名称
        2. CategoryName  扩充的方法
        3. NewMethod  扩充的方法
  • 匿名分类 - - - (也叫 “延展” “类扩展”)
    - 作用 - - - 可以为某个类扩充一些私有的成员变量和方法
    - 实际上, 匿名分类, 就是在.m文件里, 写上的@interface 类名() @end
    - 要多多积累自己的分类控件, 便于积累, 以提升开发效率
  • 注意事项
    • 分类是用于给原有类添加方法的, 它只能添加方法, 不能添加属性(成员变量),
    • 分类中的@property, 只会生成setter/getter方法的声明, 不会生成实现以及私有的成员变量
    • 可以在分类中访问原有类中.h的属性
    • 方法调用的优先级: 分类(最后参与编译的分类优先) —> 原来类 —> 父类
    • 注意
      • 如果分类中有和原有类同名的方法, 会调用分类中的方法 (忽略原有类的方法) ———建议不要这么写
      • 如果多个分类都有和原有类同名的方法, 那么调用该方法的时候执行谁, 由编译器决定( 会执行最后一个参与编译的分类的方法)

8.Block

  1. 定义 - - - Block是iOS中一种比较特殊的数据类型, 和”指向函数”相似
    • 有参数 int(^sumBlock)(int int);
    • 没参数 void(^myBlock)();
  2. 应用场景 - - - 当发现代码的前面和后面都是一样的时候,这个时候就可以用Block
    • 动画
    • 多线程
    • 集合遍历
    • 网络请求回调
  3. 作用
    • 用来保存某一段代码,可以再恰当的时间再取出来调用
    • 功能类似于函数和方法
  4. 格式
    • 返回值类型(^block变量名)(形参列表) = ^(形参列表){ 代码段 };
[1] 例如
void(^roseBlock)();
1. ()代表block将来保存的代码没有形参
2. (^roseBlock)代表roseBlock是一个block变量,可以用于保存一段block代码
3. 如果block没有参数, 那么^后面的()可以省略

[2] 例如:
int multiplier = 7;
int (^myBlock)(int) = ^(int num){ return num *multiplier ;};

1. ^符号将myBlock声明为一个块对象
2. myBlock 是块对象, 返回整型值
3. (int)  代表有一个参数,参数的类型也是整型值
4. ^(int num)   参数的名称是num
5. { return num *multiplier ;}  这是块对象的主体部分
6. ^(int num){ return num *multiplier ;}    这是定义块对象的语法结构, 这部分就是赋给myBlock变量的值

[3] 利用typedef定义block类型
typedef int(^myBlock)(int, int);        //可以用myBlock这种类型, 定义block变量
myBlock b1, b2;                         //定义了2个myBlock类型的变量 : b1,  b2
b1 = ^(int a, int b){                   //写出b1变量里的内容
    return a - b;
}
myBlock b3 = ^(int a, int b){           //也可以在定义myBlock类型的变量时, 直接写出b3变量里的内容
    return a - b;
}

[4] block共有3种写法
[4.1] 第1种写法(主要看参数写法和返回值写法)  ^(int a, int b){return a + b;} 
[4.2] 第2种写法(虽没参数,但可以保留空括号)  ^(){NSLog(@"-----test-----");}
[4.3] 第3种写法(没有参数可以直接省略空括号)  ^{NSLog(@"-----test-----");}

9. Block的注意事项

  1. Block和typedef
    • 注意: 利用typedef给block其别名,和指向函数的指针一样, block变量的名称就是别名
    • 推荐用typedef定义别名, 在编码中, 用别名会更加简单
  2. Block的注意事项
    • block中可以访问外面的变量
    • block中可以定义和外界同名的变量, 并且如果在block中定义了和外界同名的变量, 在block中访问的是block中的变量
    • 默认情况下, 不可以在block中修改外界变量的值
      • 因为block中的变量和外界的变量并不是同一个变量
      • 如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中
      • 因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值
    • 如果想在block中修改外界变量的值, 必须在外界变量前面加上__block
      • 如果在block中修改了外界变量的值, 会影响到外界变量的值
    • block是存储在堆中还是栈中
      • 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
      • 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
      • 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
      • 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
      • 如果是在ARC开发中就需要在前面加上__weak
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值