1. 内存管理
- 堆和栈
- 栈 (操作系统) : 由操作系统自动分配释放, 存放函数的参数值(形参),局部变量的值等. 其操作方式类似于数据结构中的栈 (先进后出)
- 堆 (操作系统) : 一般由程序员分配释放, 若程序员不释放, 程序结束时可能由OS回收, 分配方式类似于链表
- 只要是alloc init创造的, 都放在堆里
- OC对象存放在堆里,堆里的数据系统不会自动释放, 需要手动释放
- 非OC对象一般放在栈里, 栈内存会被系统自动回收
- 内存泄漏 - - - 如果一个OC对象,占据了一块内存, 但又没有被使用. 那么这个现象就是内存泄露
- 引用计数器的作用 - - - 引用计数器表示有多少人正在使用这个对象
- 引用计数器的操作
- 对象被创建, 引用计数器初始就是1
- 给对象发送一条retain消息, 引用计数器就+1
- 给对象发一条release消息, 引用计数器 - 1
- 给对象发retainCount, 可以获得当前引用计数器的值 (获得的值并不准确)
- dealloc
- 当一个对象引用计数器为0, 系统会自动给对象发一条dealloc消息.(因此从dealloc方法有没有被调用, 就知道对象是否被销毁)
- 一般会dealloc方法的重写, 在这里释放相关的资源, dealloc就是对象的遗言
- 一旦重写了dealloc方法, 就必须调用[super dealloc] 并且放在最后调用
- 不能直接调用dealloc方法
- 一旦对象被回收了, 它占用的内存就不可再用, 坚持使用会导致程序崩溃(野指针错误)
- 野指针和空指针
- 野指针
- 只要一个对象被释放了, 我们就称这个对象为 “僵尸对象”
- 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针
- 只要给一个野指针发送消息就会报错
- [Person release]: message sent to deallocated instance 0x1001146b0
- 空指针
- 为了避免给野指针发送消息会报错, 一般情况下, 当一个对象被释放后我们会将这个对象的指针设置为空指针
- 因为在OC中给空指针发送消息是不会报错的
- ARC 和 MRC
- ARC : Automatic(自动) Reference(引用) Counting(计数)
- 什么是自动引用计数?
- 不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码
- 注意点: OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干得, 而OC中的ARC是编译器干得
- MRC: Manul(手动) Reference(引用) Counting(计数)
- 什么是手动引用计数?
- 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
- 内存管理的原则 - - - 有加就有减
- 例如: 一次alloc对应一次release, 一次retain对应一次relese
- 多对象内存管理
- 当A对象想使用B对象一定要对B对象进行一次retain, 这样才能保证A对象存在B对象就存在, 也就是说这样才能保证无论在什么时候在A对象中都可以使用B对象
- 当A对象释放的时候, 一定要对B对象进行一次release, 这样才能保证A对象释放了, B对象也会随之释放, 避免内存泄露
- 总结一句话: 有增就有减
3. property修饰符
- 相同类型的property修饰符不能同时使用
- 不同类型的property修饰符可以多个结合在一起使用, 多个之间用,号隔开
- iOS开发中只要写上property, 那么就立刻写上nonatomic
- 多线程
- atomic:性能低(默认)
- nonatiomic:性能高
- 在iOS开发中99.99%都是写nonatomic
readonly只会生成getter方法
readwrite: 既会生成getter也会生成setter, 默认什么都不写就是readwrite
getter: 可以给生成的getter方法起一个名称
setter: 可以给生成的setter方法起一个名称
retain: 就会自动帮我们生成getter/setter方法内存管理的代码
assign: 不会帮我们生成set方法内存管理的代码, 仅仅只会生成普通的getter/setter方法, 默认什么都不写就是assign
4. @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只会拷贝对应的文件, 并不会形成死循环
5. 循环retain
- 如果A对用要拥有B对象, 而B对应又要拥有A对象, 此时会形成循环retain
- 如何解决这个问题: 不要让A retain B, B retain A
- 让其中一方不要做retain操作即可
6. autoRelease的注意事项
- 一个程序中可以创建多个自动释放池, 并且自动释放池还可以嵌套
- 如果存在多个自动释放池时, 自动释放池是以”栈”的形式存储的 - - - 栈的特点: 先进后出
- 千万不要写多个auturelease
- 一个alloc / new 对应一个autorelease 或者 release
- 如果写了autorelease,就不要写release
7.ARC
- ARC的判断标准: 只要没有强指针指向对象, 对象就会被释放
- 默认情况下所有的指针都是强指针
__strong Person *p = [[Person alloc]init];
__weak Person *p = [[Person alloc]init];
- 在ARC中如果保存对象, 不要用assign, 要用weak - - - assign是专门用于保存基本数据类型的. 保存对象要用weak
- 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
8.分类
@interface ClassName (CategoryName) NewMethod;
1. ClassName 分类的名称
2. CategoryName 扩充的方法
3. NewMethod 扩充的方法
- 匿名分类 - - - (也叫 “延展” “类扩展”)
- 作用 - - - 可以为某个类扩充一些私有的成员变量和方法
- 实际上, 匿名分类, 就是在.m文件里, 写上的@interface 类名() @end
- 要多多积累自己的分类控件, 便于积累, 以提升开发效率
- 注意事项
- 分类是用于给原有类添加方法的, 它只能添加方法, 不能添加属性(成员变量),
- 分类中的@property, 只会生成setter/getter方法的声明, 不会生成实现以及私有的成员变量
- 可以在分类中访问原有类中.h的属性
- 方法调用的
- 注意
- 如果分类中有和原有类同名的方法, 会调用分类中的方法 (忽略原有类的方法) ———建议不要这么写
- 如果多个分类都有和原有类同名的方法, 那么调用该方法的时候执行谁, 由编译器决定( 会执行最后一个参与编译的分类的方法)
9.Block
- 定义 - - - Block是iOS中一种比较特殊的数据类型, 和”指向函数”相似
- 应用场景 - - - 当发现代码的前面和后面都是一样的时候,这个时候就可以用Block
- 作用
- 用来保存某一段代码,可以再恰当的时间再取出来调用
- 功能类似于函数和方法
- 格式
- 返回值类型(^block变量名)(形参列表) = ^(形参列表){ 代码段 };
- 例如
void(^roseBlock)();
1. ()代表block将来保存的代码没有形参
2. (^roseBlock)代表roseBlock是一个block变量,可以用于保存一段block代码
3. 如果block没有参数, 那么^后面的()可以省略
例如:
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变量的值
10. Block的注意事项
- Block和typedef
- 注意: 利用typedef给block其别名,和指向函数的指针一样, block变量的名称就是别名
- 推荐用typedef定义别名, 在编码中, 用别名会更加简单
- 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