Tables | Are | PS |
---|---|---|
1 | 基本原理 | |
1.1 | 为什么要进行内存管理 | |
1.2 | 对象的基本结构 | |
1.3 | 引用计数器的作用 | |
1.4 | 操作 | |
1.5 | 对象的销毁 | |
2 | 相关概念和使用注意 | |
3 | 内存管理原则 | |
3.1 | 原则 | |
3.2 | 多对象管理 | |
4 | 内存管理代码规范 | |
4.1 | 基本数据类型 | |
4.2 | 内存管理代码规范 | |
4.2 | dealloc方法的代码规范 | |
5 | @property的参数 | |
5.1 | 内存管理相关参数 | |
5.2 | set和get方法的名称 | |
6 | 内存管理中的循环引用问题以及解决 | |
7 | Autorelease | |
7.1 | 基本用法 | |
7.2 | 好处 | |
7.3 | 错误写法 | |
7.4 | 自动释放池 | |
7.5 | 使用注意 | |
8 | ARC内存管理机制 | |
8.1 | ARC的判断准则 | |
8.2 | 指针分类 | |
8.3 | ARC的特点总结 | |
8.4 | NSString的retainCount | |
8.4 | 补充 | |
9 | runtime 机制 | |
9.1 | run time 相关原理 | |
9.2 | runtime 的相关实现 |
内存分配
instruments 里面的leaks和allocation
OC 内存管理
1.基本原理
1.1 为什么要进行内存管理
由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。
下列行为都会增加一个app的内存占用
- 创建一个OC对象
- 定义一个变量
- 调用一个函数或者方法
管理范围:任何继承NSObject的对象,对其他的基本数据类型无效。
本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收.此时对象已经没有指针指向,要是依然存在于内存中,会造成内存泄露。
1.2 对象的基本结构
每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。
在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器。
1.3 引用计数器的作用
判断对象要不要回收的唯一依据就是计数器是否为0,若不为0则存在。为0则调用dealloc函数。
1.4 操作
给对象发送消息,进行相应的计数器操作。
Retain消息:使计数器+1,改方法返回对象本身
Release消息:使计数器-1(并不代表释放对象)
retainCount消息:获得对象当前的引用计数器值
1.5 对象的销毁
当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。
当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。
一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。
2.相关概念和使用注意
野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)。
僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(打开僵尸对象检测)
空指针:没有指向任何东西的指针(存储的东西是0,null,nil),给空指针发送消息不会报错
注意:不能使用[p retaion]让僵尸对象起死复生。
3.内存管理原则
3.1原则
只要还有人在使用某个对象,那么这个对象就不会被回收;
只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;
当你不想使用这个对象时,应该让对象的引用计数器-1;
- 谁创建,谁release
如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法
不是你创建的就不用你去负责 - 谁retain,谁release**
只要你调用了retain,无论这个对象时如何生成的,你都要调用release - 总结
有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1.
3.2 多对象管理
单个对象的内存管理, 看起来非常简单。如果对多个对象进行内存管理, 并且对象之间是有联系的, 那么管理就会变得比较复杂
其实, 多个对象的管理思路 跟 很多游戏的房间管理差不多
比如斗地主 \ QQ堂
有一个房间,有人要用,一号使用retaincount+1 ,release下,二号使用同理。当没人使用的话,释放房间
总的来说, 有这么几点管理规律
只要还有人在用某个对象,那么这个对象就不会被回收
只要你想用这个对象,就让对象的计数器+1
当你不再使用这个对象时,就让对象的计数器-1
4.内存管理代码规范
只要调用了alloc,就必须有release(autorelease)
4.1 基本数据类型:直接复制
-(void)setAge:(int)age
{
_age=age;
}
4.2 OC对象类型
-(void)setCar:(Car *)car
{
//1.先判断是不是新传进来的对象
If(car!=_car)
{
//2 对旧对象做一次release
[_car release];//若没有旧对象,则没有影响
//3.对新对象做一次retain
_car=[car retain];
}
}
4.3 dealloc方法的代码规范
- 一定要[super dealloc],而且要放到最后
- 对self(当前)所拥有的的其他对象做一次release操作
-(void)dealloc
{
[_car release];
[super dealloc];
}
下面代码都会引发内存泄露
p.dog = [[Dog alloc] init];
[[Dog alloc] init].weight = 20.8;
5. @property的参数
5.1内存管理相关参数
Retain:对对象release旧值,retain新值(适用于OC对象类型)完整的set方法,管理内存的参数
Assign:直接赋值(默认,不写参数的都是assign,适用于非oc对象类型,这样对位于栈的系统自动生成的变量)
Copy:release旧值,copy新值,用于NSString\block等类型
ARC中的@property
strong : 用于OC对象, 相当于MRC中的retain
weak : 用于OC对象, 相当于MRC中的assign
assign : 用于基本数据类型, 跟MRC中的assign一样
copy : 一般用于NSString, 跟MRC中的copy一样
是否要生成set方法(若为只读属性,则不生成)
Readonly:只读,只会生成getter的声明和实现
Readwrite:默认的,同时生成setter和getter的声明和实现
多线程管理(苹果在一定程度上屏蔽了多线程操作)
Nonatomic:高性能,一般使用这个
Atomic:低性能
5.2 Set和get方法的名称
修改set和get方法的名称,主要用于布尔类型。因为返回布尔类型的方法名一般以is开头,修改名称一般用在布尔类型中的getter。
@propery(setter=setAbc,getter=isRich) BOOL rich;
BOOL b=p.isRich;// 调用
6. 内存管理中的循环引用问题以及解决
案例:每个人有一张身份证,每张身份证对应一个人,不能使用#import的方式相互包含,这就形成了循环引用。
新的关键字:
@class 类名;——解决循环引用问题,提高性能;
@class仅仅告诉编译器,在进行编译的时候把后面的名字作为一个类来处理。
@class的作用:声明一个类,告诉编译器某个名称是一个类
那么我们怎么使用@class,
- 在.h文件中使用@class来声明类
- 在.m文件中真正要使用到的时候,使用#import来包含类中的所有东西
两端循环引用的解决方法
循环retain的场景
比如A对象retain了B对象,B对象retain了A对象
循环retain的弊端
这样会导致A对象和B对象永远无法释放
循环retain的解决方案
当两端互相引用时,应该一端用retain、一端用assign(使用assign的在dealloc中也不用再release)
7 Autorelease
7.1 基本用法
- 会将对象放到一个自动释放池中
- 当自动释放池被销毁时,会对池子里的所有对象做一次release
- 会返回对象本身
- 调用完autorelease方法后,对象的计数器不受影响(销毁时影响)
7.2 好处
- 不需要再关心对象释放的时间
- 不需要再关心什么时候调用release
7.3 错误写法
连续调用多次autorelease,释放池销毁时执行两次release(-1吗?)
Alloc之后调用了autorelease,之后又调用了release。
7.4 自动释放池
在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。
当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
7.5 Autorelease注意
- 系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate date];
- 开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self
- 占用内存较大的对象,不要随便使用autorelease,应该使用release来精确控制;而占用内存较小的对象使用autorelease,没有太大的影响
8. ARC内存管理机制
8.1 ARC的判断准则
只要没有强指针指向对象,对象就会被释放。
8.2 指针分类
- 强指针:默认的情况下,所有的指针都是强指针,关键字strong
- 弱指针:___weak关键字修饰的指针
声明一个弱指针如下:___weak Person *p;
ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空操作。
___weak Person * p= [[Person alloc] init];
不合理,对象一创建出来就被释放掉,对象释放掉后,ARC把指针自动清零。
ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再[super dealloc]。
@property(nonatomic,strong)Dog *dog;// 意味着生成的成员变量_dog是一个强指针,相当于以前的retain。
如果换成是弱指针,则换成weak,不需要加 ____ 。
8.3 ARC的特点总结
- 不允许调用release,retain,retainCount
- 允许重写dealloc,但是不允许调用[super dealloc]
- @property的参数:
Strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针
Weak:相当于原来的assign,(适用于oc对象类型),成员变量是弱指针
Assign:适用于非OC对象类型(基础类型)
8.4 NSString的retainCount
** 提示:字符串是特殊的对象,但不需要使用release手动释放,这种字符串对象默认就是autorelease的,不用额外的去管内存,存放到堆中。 **
//1. 字符串常量
NSString *str1 = @"string";
NSLog(@"str1: %ld",[str1 retainCount]); // -1或18446744073709551615(即UINT_MAX ( Maximum value an `unsigned int'))
//因为"str"为字符串常量,系统不会收回,也不会对其作引用计数,即使我们对str如何retain或release。就是为-1.
//2. stringWithFormat
NSString *str2 = [NSString stringWithFormat:@"%s", "test"];
NSLog(@"str2:%ld",[str2 retainCount]); // 1
//使用stringWithFormat创建的NSString为变量,系统会进行引用计数。 以前为1,现在为-1.
//3. stringWithString
stringWithString这个方法比较特别:它的retainCount取决于它后面跟的string对象
NSString *str3 = [NSString stringWithString:@"test"];
NSString *str4 = [NSString stringWithString:[NSString stringWithFormat:@"test,%d ,%d",1,3]];
NSLog(@"str3:%ld",[str3 retainCount]); // -1
NSLog(@"str4:%ld",[str4 retainCount]); // 2
//可以看到第一个为"常量"对象,其retainCount方法的实现返回的是maxIntValue。
//第二个为2,这个方法生成的只是一个对另一个对象的引用。一个对象实例,两次的stringWithString,它的retainCount为2,同时都被当前的AutoreleasePool管理。
8.5 补充
** 让程序兼容ARC和非ARC部分。转变为非ARC -fno-objc-arc 转变为ARC的, -f-objc-arc 。**
ARC也需要考虑循环引用问题:一端使用retain,另一端使用assign。
set
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
Car *car = [Car new];
car.speed = 100;
[p setCar:car];
[p drive];
[car release];
/*
//重复调用,已经释放了,要改加上if语句去进行判断
[p setCar:car];
[p drive];
*/
Car *car2 = [Car new];
car2.speed = 200;
[p setCar:car2];
[p drive];
[car2 release];
[p release];
}
return 0;
}
#import <Foundation/Foundation.h>
#import "Car.h"
@interface Person : NSObject
{
Car *_car;
NSString *_name;
}
- (void)setName:(NSString *)name;
- (NSString *)name;
- (void)setCar:(Car *)car;
- (Car *)car;
- (void)drive;
@end
#import "Person.h"
@implementation Person
- (void)dealloc{
//目的是要保证在p对象存在的时候,car对象一定存在
//对象p被销毁的时候
[_car release];
[_name release];
[super dealloc];
NSLog(@"被释放了");
}
- (void)setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
- (NSString *)name
{
return _name;
}
- (void)setCar:(Car *)car{
//第一次是空,所以release无所谓,第二次减一,要是不release的话,会造成内存泄露的问题。
//car retainCount= 1
if (_car != car) {
[car release];
}
//car2 2
_car = [car retain];//引用计数加1 ,返回self
}
- (void)drive{
[_car run];
}
@end
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property int speed;
- (void)run;
@end
#import "Car.h"
@implementation Car
- (void)dealloc{
[super dealloc];
NSLog(@"被释放了 %d",_speed);
}
- (void)run{
NSLog(@"I can run");
}
@end
9. runtime 机制
9.1 run time 相关原理
对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道runtime底层编译成C++语言之后做了什么? 查到一个大牛给资料,顿时对runtime有了一定认识!
我们随便写一个小程序,代码如下: person类头文件如下,
<!-- lang: cpp -->
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
main.m文件如下
<!-- lang: cpp -->
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
NSString *str = @"zhangsan";
p.name = str;
// p.name 等价于
[p setName:str];
p.age = 20;
return 0;
}
然后我们打开终端,在命令行找到cd到文件目录,然后中输入:
clang -rewrite-objc main.m
命令可以将main.m编译成C++的代码,改成不同的文件名,就会生成不同的c++代码 这是就生成了main.cpp这个c++文件,打开文件代码 查看该main.cpp最底下的main函数, 这样我们就可以看到底层具体实现的方式!
这时,我们就需要知道这些方法:
**
objc_msgSend 可以给对象发送消息
objc_getClass(“Person”) 可以获取到指定名称的对象
sel_registerName(“alloc”) 可以调用到对象的方法
**
通过查看,c++代码,我们得出结论:
使用objc_msgSend函数,给objc_getClass函数实例化的对象发送sel_registerName获取到的方法 这么一个消息 代码是给人看的,顺带让机器实现功能。
日常的程序开发过程中,要少用runtime,
那什么时候会使用runtime呢? runtime应用的时机:
1> 当需要非常高的性能开发时,使用runtime,注释:oc的代码已经无法满足性能需求
2> 当我们对系统内部的实现很好奇的时候,可以用clang反编译成c++去看底层的实现机制!
项目讲解的是runtime的底层实现原理, 如果想要知道runtime是怎么用的,可以查看runtime.h头文件查看! 以下是runtime机制方法的一些使用方法介绍,希望对大家有用!
相关技术文档:
http://www.tuicool.com/articles/uimInm http://blog.csdn.net/lengshengren/article/details/17764135
9.2runtime 的相关实现
首先,第一个问题,
- runtime实现的机制是什么?
runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者比如说,下面一个创建对象的方法中, 举例: OC :
[[MJPerson alloc] init]
runtime :
objc_msgSend(objc_msgSend(“MJPerson” , “alloc”), “init”)
- runtime 用来干什么呢??用在那些地方呢?怎么用呢?
runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)
- 在程序运行过程中, 动态创建一个类(比如KVO的底层实现)
- 在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法
- 遍历一个类的所有成员变量(属性)\所有方法
例如:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置!
例如,PYPerson.h的文件如下所示
import
@interface PYPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age2;
@property (nonatomic, assign) int height2;
@property (nonatomic, assign) int age3;
@property (nonatomic, assign) int height3;
@property (nonatomic, assign) int age4;
@property (nonatomic, assign) int height4;
@end
而PYPerson.m实现文件的内容如下
#import "PYPerson.h"
import
@implementation PYPerson
- (void)encodeWithCoder:(NSCoder *)encoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([PYPerson class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量Ivar ivar = ivars[i];
// 查看成员变量const char *name = ivar_getName(ivar);
// 归档NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([PYPerson class], &count);for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量 Ivar ivar = ivars[i];
// 查看成员变量 const char *name = ivar_getName(ivar);
// 归档 NSString *key = [NSString stringWithUTF8String:name]; id value = [decoder decodeObjectForKey:key];
// 设置到成员变量身上 [self setValue:value forKey:key];}free(ivars);
}
return self;
}
@end
这样我们可以看到归档和解档的案例其实是runtime写下的.
学习runtime机制首先要了解下面几个问题
相关的头文件和函数
利用头文件,我们可以查看到runtime中的各个方法!相关应用
NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)
字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)
KVO(利用runtime动态产生一个类)
用于封装框架(想怎么改就怎么改) 这就是我们runtime机制的只要运用方向相关函数
objc_msgSend : 给对象发送消息
class_copyMethodList : 遍历某个类所有的方法
class_copyIvarList : 遍历某个类所有的成员变量
class_….. 这是我们学习runtime必须知道的函数!必备常识 1> Ivar : 成员变量 2> Method : 成员方法 从上面例子中我们看到我们定义的成员变量,如果要是动态创建方法,可以使用Method.