内存管理/run time机制

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,

  1. 在.h文件中使用@class来声明类
  2. 在.m文件中真正要使用到的时候,使用#import来包含类中的所有东西

两端循环引用的解决方法

循环retain的场景
比如A对象retain了B对象,B对象retain了A对象

循环retain的弊端
这样会导致A对象和B对象永远无法释放

循环retain的解决方案
当两端互相引用时,应该一端用retain、一端用assign(使用assign的在dealloc中也不用再release)

7 Autorelease

7.1 基本用法

  1. 会将对象放到一个自动释放池中
  2. 当自动释放池被销毁时,会对池子里的所有对象做一次release
  3. 会返回对象本身
  4. 调用完autorelease方法后,对象的计数器不受影响(销毁时影响)

7.2 好处

  • 不需要再关心对象释放的时间
  • 不需要再关心什么时候调用release

7.3 错误写法

连续调用多次autorelease,释放池销毁时执行两次release(-1吗?)

Alloc之后调用了autorelease,之后又调用了release。

7.4 自动释放池

在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。
当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中

7.5 Autorelease注意

  1. 系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate date];
  2. 开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self
  3. 占用内存较大的对象,不要随便使用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的特点总结

  1. 不允许调用release,retain,retainCount
  2. 允许重写dealloc,但是不允许调用[super dealloc]
  3. @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 的相关实现

首先,第一个问题,

  1. 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”)
  1. 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机制首先要了解下面几个问题

  1. 相关的头文件和函数
    利用头文件,我们可以查看到runtime中的各个方法!

  2. 相关应用
    NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)
    字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)
    KVO(利用runtime动态产生一个类)
    用于封装框架(想怎么改就怎么改) 这就是我们runtime机制的只要运用方向

  3. 相关函数
    objc_msgSend : 给对象发送消息
    class_copyMethodList : 遍历某个类所有的方法
    class_copyIvarList : 遍历某个类所有的成员变量
    class_….. 这是我们学习runtime必须知道的函数!

  4. 必备常识 1> Ivar : 成员变量 2> Method : 成员方法 从上面例子中我们看到我们定义的成员变量,如果要是动态创建方法,可以使用Method.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容