iOS之内存管理内存管理

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

2016-09-07  本文已影响3608人  只敲代码不偷桃

1.1 管理的原因

1.2 释放过渡

    //引用计数器是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 野指针、空指针

1.4 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等

1.7 @class和#import

1.8 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自定义创建的字符串会放在堆里面,即使字符串内容相同,也会再次开辟新空间,然后指针就是在栈里面,保存对象的地址。内存分布空间如下:

2.0 copy的内存管理

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

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 单例模式

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);
}

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

2.3 block的内存管理

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

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

上一篇 下一篇

猜你喜欢

热点阅读