iOS五大块知识总结之内存管理

1.1 管理的原因

  • 只有OC对象才需要管理内存,非OC对象(如:char、int、folat)则不需要管理内存的本质原因:
    OC对象是放在堆内存里,非OC对象是放在栈内存里,栈内存里的东西系统会自动管理

1.2 释放过渡

  • 当一个对象创建出来,执行了多次release操作,就会报错--释放过渡
  • 简单来说就是
    //引用计数器是1
    Person *p = [[Person alloc]init];
    
    //此时p的引用计数器是0,(p指向的内存已经是坏内存了,称person对象为僵尸对象)
    [p release];
    
    //注意:此时再次调用relesase方法,则会报错`message sent to deallocated instance 0x110201950`(向一个已经释放了的对象发送消息)
    [p release];
    
    //此时p指向僵尸对象(坏内存),则称p为野指针    //给空指针发送消息不会报错,

1.3 野指针、空指针

  • 僵尸对象:已经被销毁的对象(不能再使用的对象)
  • 野指针:指向僵尸对象(不可用内存)的指针
  • 给野指针发消息会报EXC_BAD_ACCESS错误
  • 空指针:没有指向存储空间的指针(里面存的是nil,也就是0)
  • 给空指针发消息是没有任何反应的
  • 为了避免野指针错误的常见办法是在对象被销毁后,将指向对象的指针变为空指针
  • 默认情况下,Xcode是不会管僵尸对象的,使用一块被释放的内存也不会报错,为了方便调试,应该开启僵尸对象监控。如下图:


    开启僵尸对象检测

1.4 dealloc方法的重写

  • 当一个对象的引用计数器值为0时,这个对象即将别销毁,其占用的内存被系统回收,系统会自动给对象发送一条dealloc消息
  • 一旦重写了dealloc方法,就必须调用[supper dealloc],且放在最后面调用

1.5 重写setter方法

- (void)set:Room:(Room *)toom
{
    //传进来的room和_room不一样的时候
    if(_room != room){
    //对旧值(当前正在使用的房间)做一次release
    [_room release];
    
    //对新房间做一次retain操作
    [room retain];
    _room = room; 
    //后两步,也可以简化成_room = [room retain]
    }
}
//getter方法
- (Room*)room
{
  return _room;
}

原因:举例子来说,试想这样的场景:一个名叫张三的人想开一间房,比方说最开始他开了房号为01的房子,等张三打算入住的时候发现房间01采光不好,想要换一间房子,此时张三该怎么做呢?他应该是先退掉01号房间,再去开02房子(喜新厌旧可以,但是需要对旧的东西负责),还有加上判断条件,是为了防止重复赋值的时候 出现野指针的错误

1.6 @property属性定义

如果只是用@property修饰一个属性,默认生成对应的setter方法和getter方法。但具体实现是这样的:

//声明属性
@property Dog *dog;
//默认的setter方法实现
- (void)setDog:(Dog *)dog
{
  _dog = dog;
}

显然不能这么干,所以需要加一些修饰属性的关键字,如retain、assign、copy等

  • retain:(MRC下)系统默认重写setter和getter方法,具体实现和1.5的getter、setter方法一样,修饰OC对象,release旧值,retain新值。
  • assign:直接赋值,不做任何内存管理(默认,用于非OC对象)。
  • copy:release旧值,copy新值(一般用于字符串NSString*)。

1.7 @class和#import

  • 作用:#import会包含引用类的所有信息(内容),包括引用类的变量和方法。@class仅仅是告诉编译器有这么一个类,具体这个类里有什么信息,完全不知。
  • 效率:如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,编译效率非常低。相对来说,使用@class方式就不会出现这种问题。
  • ** 补充三点:**
  • <1>. #import跟#include都能完整的包含某个文件的内容,#import能防止同一个文件被包含多次;
  • <2>. @class仅仅是声明一个类,并不会包含类的完整声明;@class还能解决循环包含的问题;
  • <3>. #import<>用来包含系统自带的文件,#import""用来包含自定义的文件

1.8 autorelease

  • 给对象发送一条autorelease消息,会将对象放到一个自动释放池中
  • 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
  • 会返回对象本身,调用完autorelease方法后,对象的计数器不变
//自动释放池什么时候销毁?
kCFRunLoopEntry//创建一个自动释放池
kCFRunLoopBeforeWaiting//销毁自动释放池,创建一个新的自动释放池
kCFRunLoopExit//销毁自动释放池
  • 简述一下自动释放池底层怎么实现?

自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶,当一个对象收到发送autorelease消息时,他被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,他们从栈中被删除,并且会给池子里所有的对象都会做一次release操作。

1.9 string的内存管理

NSString *str1 = @"Jack";
NSString *str2 = [NSString stringWithFormat:@"Rose"];
NSString *str3 = @"Jack";
NSString *str4 = [NSString stringWithFormat:@"Rose"];

注意:直接赋值的,则相应的字符串会放到常量区,常量区的字符串有且只有一份,即str3和str1指向的是同一个字符常量。但通过类方法stringWithFormat自定义创建的字符串会放在堆里面,即使字符串内容相同,也会再次开辟新空间,然后指针就是在栈里面,保存对象的地址。内存分布空间如下:

  • + stringWithFormat:类方法,返回一个autorelease的NSString实例,不用手动Release,在自动释放池中会自动释放。
  • – initWithFormat:实例方法,返回一个自己Alloc申请内存的NSString实例,根据OC内存管理黄金法则,管杀管埋,它则需要自己手动Release。

2.0 copy的内存管理

首先理解一下深复制和浅复制:
深复制

  • 源对象和副本对象是不同的两个对象;
  • 源对象引用计数器不变,副本对象计数器为1(因为是新产生的);
  • 本质是:产生了新的对象。
    浅复制
  • 源对象和副本对象是同一个对象
  • 源对象(副本对象)引用计数器+1,相当于做一次retain操作;
  • 本质是:没有产生新对象。
    注意:只有源对象和副本对象都不可变时,才是浅拷贝,其他都是深拷贝
NSString *str1 = [NSString stringWithFormat:@"address is only one"];
  NSString *str2 = [str1 copy];
  NSLog(@"%p ,%p",str1,str2);
//结果发现两个地址一样,都是0x610000052990。
/**
  1.copy:产生的肯定是不可变副本
  2. 如果是不可变对象调用copy方法产生出不可变副本,那么不会产生新的对象 
*/

然后数组、字典的情况和字符串类似。总结字符串调用copy和mutableCopy的情况如下:

1. NSMutableString调用mutableCopy : 深复制。
2. NSMutableString调用copy : 深复制。
3. NSString调用mutableCopy : 深复制。
4. NSString调用copy : 浅复制

2.1 单例模式

  • 系统单例如:UIApplication 、NSUserDefaults、UIDevice....公用一份,省内存,方便管理,一些整个程序都用的上的数据
  • 单例工具类的简单写法
static NetworkTools *_networkTools;
@implementation NetworkTools

+ (instancetype)sharedNetworkTools
{
    return [[self alloc]init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    if (_networkTools == nil) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _networkTools = [super allocWithZone:zone];
        });
    }
    return _networkTools;
}

- (instancetype)init
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _networkTools = [super init];
    });
    return _networkTools;
}

2.2 KVO

假设有两个类A和B,其中B有个属性age(int类型),现在让A类监听B类的age属性变化,当发生变化时打印一句话。

/**
 *  A对象监听B对象的age属性发生变化
 *  @param options  值变化
    1. NSKeyValueObservingOptionNew:新值、
    2. NSKeyValueObservingOptionOld:旧值
 *  @param event  
 */
[B addObserver:A forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

//KVO实现过程,其代码在运行时会创建一个B类的派生类NSKVONotifying_B,并在NSKVONotifying_B类里重写属性age的setter方法
- (void)setAge:(int)age
{
    [supper setAge:age];
    //并在sette方法里调用这两个方法,当调用了这两个方法,就会通知B类执行那个监听方法
    [self willChangeValueForKey:@"age"];
    [self didChangeValueForKey:@"age"];
}


//MARK:- 在B类里
/**
 *  属性发生改变时执行
 *
 *  @param keyPath 检测的属性 此时是age
 *  @param object  谁的属性
 *  @param change  改变(oldValue、newValue)
 *  @param context
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"监听到%@属性发生改变了,change= %@,context= %@",object,change,context);
}
  • KCO的底层实现原理:

KVO是基于runtime机制实现的。当某个类的对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类的中任何被观察属性的setter方法。派生类在被重写的setter方法实现真正的通知机制(Person -> NSKVONotifying_Person)

2.3 block的内存管理

  • 默认情况下block的内存是在栈中(不需要手动去管理block内存),它不会对所引用的对象进行任何操作
  • 如果对block进行了copy操作, block的内存会搬到堆里面,它会对所引用的对象做一次retain操作
  • 对于普通的局部变量,block只会引用它的初值,不能跟踪它的改变;block内部能够一直应用被__block修饰的变量,block内部能够一直引用被static修饰的变量,block内部能够一直引用全局变量;
  • block的本质是“带有自动变量的匿名函数”,其实就是一段代码块的内存的指针
  • 非ARC: 如果所引用的对象用了__block修饰,就不会做retain操作。
  • ARC: 如果所引用的对象用了__unsafe_unretained、__weak修饰,就不会做retain操作。(注意:__unsafe_unretained和__weak的相同点是两者都不持有该对象,当他拥有的对象被释放的时候,那此时这个若引用也会自动失效,区别是__weak会被置为nil的状态。__unsafe_unretained不会置nil,会有野指针的风险)
  • 为什么加上 __block就可以修改外部的变量了?

真正的原因是这样的:我们都知道:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

本文参考资料:
[iOS] stringWithFormat 和 initWithFormat 有何不同?

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

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,108评论 29 470
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,928评论 1 16
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,780评论 10 69
  • 11.看下面的程序,第一个NSLog会输出什么?这时str的retainCount是多少?第二个和第三个呢? 为什...
    AlanGe阅读 716评论 1 4
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,942评论 0 7