1.内存管理
内存
移动设备的内存极其有限,每个app所能占用的内存是有限制的,下列行为都会增加一个app的内存占用
创建一个OC对象
定义一个变量
调用一个函数或者方法
当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
如果app占用内存过大
系统可能会强制关闭app, 造成闪退现象, 影响用户体验
如何回收那些不需要再使用的对象?那就得学会OC的内存管理
所谓内存管理, 就是对内存进行管理, 涉及的操作有:
分配内存 : 比如创建一个对象, 会增加内存占用
清除内存 : 比如销毁一个对象, 能减小内存占用
内存管理的管理范围
任何继承了NSObject的对象
对其他非对象类型无效(int、char、float、double、struct、enum等 )
只有OC对象才需要进行内存管理的本质原因
OC对象存放于堆里面
非OC对象一般放在栈里面(栈内存会被系统自动回收)
堆和栈
什么是引用计数器
系统是如何判断 什么时候需要回收一个对象所占用的内存?根据对象的引用计数器
什么是引用计数器
每个OC对象都有自己的引用计数器,它是一个整数,从字面上, 可以理解为”对象被引用的次数”,也可以理解为: 它表示有多少人正在用这个对象
每个OC对象内部都有4个字节的存储空间来存放引用计数器
引用计数器的作用
简单来说, 可以理解为: 引用计数器表示有多少人正在使用这个对象
当没有任何人使用这个对象时, 系统才会回收这个对象, 也就是说
当对象的引用计数器为0时, 对象占用的内存就会被系统回收
如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出 )
任何一个对象, 刚生下来的时候, 引用计数器都为1
当使用alloc、new或者copy创建一个对象时,对象的引用计数器 + 1
引用计数器的操作
要想管理对象占用的内存, 就得学会操作对象的引用计数器
引用计数器的常见操作
给对象发送一条retain消息, 可以使引用计数器值+1(retain方法返回对象本身)
给对象发送一条release消息, 可以使引用计数器值-1
给对象发送retainCount消息, 可以获得当前的引用计数器值
需要注意的是: release并不代表销毁回收对象, 仅仅是计数器-1
dealloc
当一个对象的引用计数器值为0时
这个对象即将被销毁,其占用的内存被系统回收,系统会自动给对象发送一条dealloc消息
(因此, 从dealloc方法有没有被调用, 就可以判断出对象是否被销毁)
dealloc方法的重写
一般会重写dealloc方法, 在这里释放相关资源, dealloc就是对象的遗言
一旦重写了dealloc方法, 就必须调用[super dealloc],并且放在最后面调用
使用注意
不能直接调用dealloc方法
一旦对象被回收了, 它占用的内存就不再可用, 坚持使用会导致程序崩溃(野指针错误)
野指针空指针
僵尸对象
已经被销毁的对象(不能再使用的对象)
野指针
指向僵尸对象(不可用内存)的指针,给野指针发消息会报EXC_BAD_ACCESS错误
空指针
没有指向存储空间的指针(里面存的是nil, 也就是0),给空指针发消息是没有任何反应的
为了避免野指针错误的常见办法,在对象被销毁之后, 将指向对象的指针变为空指针
关闭ARC功能
要想手动调用retain、release等方法 , 就必须关闭ARC功能
开启僵尸对象监控
默认情况下,Xcode是不会管僵尸对象的,使用一块被释放的内存也不会报错。为了方便调试,应该开启僵尸对象监控
多对象内存管理
单个对象的内存管理, 看起来非常简单
如果对多个对象进行内存管理, 并且对象之间是有联系的, 那么管理就会变得比较复杂
其实, 多个对象的管理思路 跟 很多游戏的房间管理差不多,比如斗地主 QQ堂
总的来说, 有这么几点管理规律
只要还有人在用某个对象,那么这个对象就不会被回收
只要你想用这个对象,就让对象的计数器+1
当你不再使用这个对象时,就让对象的计数器-1
内存管理原则
苹果官方规定的内存管理原则
谁创建谁release : 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
谁retain谁release :只要你调用了retain,就必须调用一次release
总结一下就是
有加就有减
曾经让对象的计数器+1,就必须在最后让对象计数器-1
set方法的内存管理
set方法
- (void)setCar:(Car *)car { if (car != _car) { // 对当前正在使用的车(旧车)做一次release [_car release]; // 对新车做一次retain操作 _car = [car retain]; } }
dealloc方法的内存管理
dealloc方法
- (void)dealloc { // 当人不在了,代表不用车了 // 对车做一次release操作 [_car release]; [super dealloc]; }
错误写法
下面代码都会引发内存泄露
p.dog = [[Dog alloc] init];
[[Dog alloc] init].weight = 20.8;
@property参数
控制set方法的内存管理
retain : release旧值,retain新值(用于OC对象)
assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
copy : release旧值,copy新值(一般用于NSString *)
控制需不需生成set方法
readwrite :同时生成set方法和get方法(默认)
readonly :只会生成get方法
多线程管理
atomic :性能低(默认)
nonatomic :性能高
控制set方法和get方法的名称(起别名)
setter : 设置set方法的名称,一定有个冒号:
getter : 设置get方法的名称
@class
作用:可以简单地引用一个类
简单使用
@class Dog;
仅仅是告诉编译器: Dog是一个类; 并不会包含Dog这个类的所有内容
具体使用
在.h文件中使用@class引用一个类
在.m文件中使用#import包含这个类的.h文件
@class和#import
作用上的区别
#import会包含引用类的所有信息(内容), 包括引用类的变量和方法
@class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知
效率上的区别
如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,
后面引用到这个文件的所有类都需要重新编译一遍 , 编译效率非常低,相对来讲,使用@class方式就不会出现这种问题了
@class
其他使用场景
对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类
这种嵌套包含的代码编译会报错
当使用@class在两个类相互声明,就不会出现编译报错
循环retian
循环retain的场景
比如A对象retain了B对象,B对象retain了A对象
循环retain的弊端
这样会导致A对象和B对象永远无法释放
循环retain的解决方案
当两端互相引用时,应该一端用retain、一端用assign
autorelease简介
autorelease方法的基本作用
给对象发送一条autorelease消息, 会将对象放到一个自动释放池中
当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
会返回对象本身
调用完autorelease方法后,对象的计数器不变
autorelease的好处
不用再关心对象释放的时间
不用再关心什么时候调用release
autorelease的使用注意
占用内存较大的对象不要随便使用autorelease
占用内存较小的对象使用autorelease,没有太大影响
自动释放池
在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
自动释放池的创建方式
iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [pool release]; // [pool drain];
iOS 5.0 开始
@autoreleasepool {
}
autorelease的常见错误
alloc之后调用了autorelease,又调用release
Person *p = [[[Person alloc] init] autorelease];
[p release];
连续调用多次autorelease
Person *p = [[[[Person alloc] init] autorelease] autorelease];
autorelease和release使用对比
使用release
Book *book = [[Book alloc] init];
[book release];
使用autorelease
Book *book = [[[Book alloc] init] autorelease];
// 不要再调用[book release];
autorelease的应用场合
一般可以为类添加一个快速创建对象的类方法
+ (id)book {
return [[[self alloc] init] autorelease];
}
外界调用[Book book]就可以获得和使用新建的Book对象,根本不用考虑在什么时候释放Book对象
一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease
比如下面的对象都已经是autorelease的,不需要再release
NSNumber *n = [NSNumber numberWithInt:100];
NSString *s = [NSString stringWithFormat:@"jack"];
NSString *s2 = @"rose";
2.ARC(自动引用计数)
ARC简介
ARC是自iOS 5之后增加的新特性, 是iOS程序猿的福音
ARC的全称是Automatic Reference Counting(自动引用计数)
ARC的实现细节
编译器会自动在适当的地方插入适当的retain、release、autorelease语句
也就是说, 编译器会自动生成内存管理的代码, 不用程序猿手动编写
如果是手动管理内存, 可以简称MRC (Manual Reference Counting)
ARC的注意点和优点
ARC的注意点
ARC是编译器特性,而不是运行时特性
ARC不是其它语言中的垃圾回收, 有着本质区别
ARC的优点
完全消除了手动管理内存的烦琐, 让程序猿更加专注于app的业务
基本上能够避免内存泄露
有时还能更加快速,因为编译器还可以执行某些优化
ARC的判断原则
ARC的判断原则
只要还有一个强指针变量指向对象,对象就会保持在内存中
强指针
默认所有指针变量都是强指针
被__strong修饰的指针
弱指针
被__weak修饰的指针
ARC的使用细节
不能调用release、retain、autorelease、retainCount
可以重写dealloc,但是不能调用[super dealloc]
ARC中的@property
strong : 用于OC对象, 相当于MRC中的retain
weak : 用于OC对象, 相当于MRC中的assign
assign : 用于基本数据类型, 跟MRC中的assign一样
copy : 一般用于NSString, 跟MRC中的copy一样
MRC转为ARC
3.Category
什么是Category
Category有很多种翻译: 分类 类别 类目 (一般叫分类)
Category是OC特有的语法, 其他语言没有的语法
Category的作用
可以在不修改原来类的基础上, 为这个类扩充一些方法
书写格式
声明
@interface 类名 (分类名称)
@end
实现
@implementation类名 (分类名称)
@end
书写格式举例
声明
#import "Student.h”
@interface Student (Study)
@end
实现
#import "Student+Study.h”
@implementation Student (Study)
@end
使用Xcode创建分类
使用注意
分类只能增加方法, 不能增加成员变量
分类可以访问原来类中的成员变量
如果分类和原来类出现同名的方法, 优先调用分类中的方法, 原来类中的方法会被忽略
方法调用的优先级(从高到低)
分类(最后参与编译的分类优先)
原来类
父类
价值所在
实现了类的相关方法的模块化
把不同的方法分配到了不同的分类文件中
可以用一个分类来代表一类功能(一个模块)
支持团队协作
可以让团队内部的不同成员 共同扩充某个类的功能
可以为系统自带的类扩充功能
类扩展
什么是类扩展
可以为某个类扩充一些私有的成员变量和方法
写在.m文件中
英文名是Class Extension
书写格式
@interface 类名 ()
@end
对比分类, 就少了一个分类名称, 因此也有人称它为”匿名分类”
4.Block
什么是Block
Block是iOS中一种比较特殊的数据类型
Block是苹果官方特别推荐使用的数据类型, 应用场景比较广泛
动画
多线程
集合遍历
网络请求回调
Block的作用
用来保存某一段代码, 可以在恰当的时间再取出来调用, 功能有点类似于函数和方法
基本使用
Block的组成要素跟函数类似
返回值
形式参数
实际参数
Block的定义格式
返回值类型 (^block变量名)(形参列表) = ^(形参列表) {
};
调用Block保存的代码
block变量名(实参);
使用typedef定义Block类型
typedef 返回值类型 (^block类型名称)(形参列表);
使用注意
默认情况下, Block内部不能修改外面的局部变量
Block内部可以修改使用__block修饰的局部变量
5.Protocol(协议)
什么是Protocol
Protocol翻译过来, 叫做”协议”
Protocol的作用
用来声明一些方法
也就说, 一个Protocol是由一系列的方法声明组成的
任何类只要遵守了Protocol, 就相当于拥有了Protocol的所有方法声明
书写格式
Protocol的定义
@protocol 协议名称
// 方法声明列表
@end
类遵守协议
@interface 类名 : 父类 <协议名称1, 协议名称2,…>
@end
协议中有2个关键字可以控制方法是否要实现(默认是@required,在大多数情况下,用途在于程序员之间的交流)
@required:这个方法必须要实现(若不实现,编译器会发出警告)
@optional:这个方法不一定要实现
协议遵守协议
一个协议可以遵守其他多个协议
一个协议遵守了其他协议,就相当于拥有了其他协议中的方法声明
@protocol 协议名称 <协议1, 协议2>
@end
基协议
NSObject是一个基类,最根本最基本的类,任何其他类最终都要继承它
还有名字也叫NSObject的协议,它是一个基协议,最根本最基本的协议
NSObject协议中声明很多最基本的方法
description
retain
release
建议每个新的协议都要遵守NSObject协议