2016-4-29 第1次修改
2016-4-27 创建
1. 内存管理
- 堆和栈
- 栈 (操作系统) : 由操作系统自动分配释放, 存放函数的参数值(形参),局部变量的值等. 其操作方式类似于数据结构中的栈 (先进后出)
- 堆 (操作系统) : 一般由程序员分配释放, 若程序员不释放, 程序结束时可能由OS回收, 分配方式类似于链表
- 只要是alloc init创造的, 都放在堆里
- OC对象存放在堆里,堆里的数据系统不会自动释放, 需要手动释放
- 非OC对象一般放在栈里, 栈内存会被系统自动回收
- 内存泄漏 - - - 如果一个OC对象,占据了一块内存, 但又没有被使用. 那么这个现象就是内存泄露
- 引用计数器的作用 - - - 引用计数器表示有多少人正在使用这个对象
- 引用计数器的操作
- 对象被创建, 引用计数器初始就是1
- 给对象发送一条retain消息, 引用计数器就+1
- 给对象发一条release消息, 引用计数器 - 1
- 给对象发retainCount, 可以获得当前引用计数器的值 (获得的值并不准确)
- dealloc
- 当一个对象引用计数器为0, 系统会自动给对象发一条dealloc消息.(因此从dealloc方法有没有被调用, 就知道对象是否被销毁)
- 一般会dealloc方法的重写, 在这里释放相关的资源, dealloc就是对象的遗言
- 一旦重写了dealloc方法, 就必须调用[super dealloc] 并且放在最后调用
- 不能直接调用dealloc方法
- 一旦对象被回收了, 它占用的内存就不可再用, 坚持使用会导致程序崩溃(野指针错误)
- 野指针和空指针
- 野指针
- 只要一个对象被释放了, 我们就称这个对象为 “僵尸对象”
- 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针
- 只要给一个野指针发送消息就会报错
- EXC_BAD_ACCESS: 坏内存访问(已经被回收, 已经不可用的内存) ,也叫 “野指针错误”
- 空指针
- 为了避免给野指针发送消息会报错, 一般情况下, 当一个对象被释放后我们会将这个对象的指针设置为空指针
- 因为在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对象也会随之释放, 避免内存泄露
- 总结一句话: 有增就有减
- set方法的内存管理
// set方法的实现
- (void)setCar:(Car *)car{
if (car != _car){
[_car release];
_car = [car retain];
}
}
// dealloc方法的实现 (不要直接调用dealloc)
- (void)dealloc{
[_car dealloc];
[super dealloc];
}
2. 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
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
- 如果A对用要拥有B对象, 而B对应又要拥有A对象, 此时会形成循环retain
- 如何解决这个问题: 不要让A retain B, B retain A
- 让其中一方不要做retain操作即可
5. autoRelease
- autorelease的基本用法
- 会将对象放到一个自动释放池中
- 当自动释放池被销毁时,会对池子里所有的对象做一次release操作
- 会返回对象本身
- 调用完autorelease方法后, 对象的计数器不变
- 一个程序中可以创建多个自动释放池, 并且自动释放池还可以嵌套
- autorelease的好处
- 不用再关心对象释放的时间
- 不用再关心什么时候调用release
- autorelease的使用注意
- 占用内存较大的对象不要随便使用autorelease
- 占用内存较小的对象使用autorelease, 没有太大影响
- 一个alloc / new 对应一个autorelease 或者 release
- 如果写了autorelease,就不要写release
- 系统自带的方法里没有包含alloc/ new/ copy, 说明返回的对象都是autorelease的
- 自动释放池
- 在IOS程序运行过程中, 会创建无数个池子. 这些池子都是以栈结构存在 (栈特点: 先进后出)
- 当一个对象调用autorelease方法时, 会将这个对象放到栈顶的释放池
- 错误写法
// (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
- MRC, A对象想要拥有B对象, 需要对B对象进行一个retain
7.分类Category
- 基本概念
- 只能在类别中添加方法, 但不能添加变量
- 写法
@interface ClassName (CategoryName) NewMethod;
1. ClassName 分类的名称
2. CategoryName 扩充的方法
3. NewMethod 扩充的方法
- 匿名分类 - - - (也叫 “延展” “类扩展”)
- 作用 - - - 可以为某个类扩充一些私有的成员变量和方法
- 实际上, 匿名分类, 就是在.m文件里, 写上的@interface 类名() @end
- 要多多积累自己的分类控件, 便于积累, 以提升开发效率 - 注意事项
- 分类是用于给原有类添加方法的, 它只能添加方法, 不能添加属性(成员变量),
- 分类中的@property, 只会生成setter/getter方法的声明, 不会生成实现以及私有的成员变量
- 可以在分类中访问原有类中.h的属性
- 方法调用的优先级: 分类(最后参与编译的分类优先) —> 原来类 —> 父类
- 注意
- 如果分类中有和原有类同名的方法, 会调用分类中的方法 (忽略原有类的方法) ———建议不要这么写
- 如果多个分类都有和原有类同名的方法, 那么调用该方法的时候执行谁, 由编译器决定( 会执行最后一个参与编译的分类的方法)
8.Block
- 定义 - - - Block是iOS中一种比较特殊的数据类型, 和”指向函数”相似
- 有参数
int(^sumBlock)(int int);
- 没参数
void(^myBlock)();
- 有参数
- 应用场景 - - - 当发现代码的前面和后面都是一样的时候,这个时候就可以用Block
- 动画
- 多线程
- 集合遍历
- 网络请求回调
- 作用
- 用来保存某一段代码,可以再恰当的时间再取出来调用
- 功能类似于函数和方法
- 格式
- 返回值类型(^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的注意事项
- 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