OC底层相关

iOS之内存管理

2019-01-16  本文已影响67人  芝麻酱的简书

1.ARC、MRC

ARC:automatic reference counting 自引用用计数
MRC:manual reference counting 手动引用计数

MRC

在2011年、IOS5之前,iOS的开发只支持MRC模式。
MRC的六个特有方法:

使用new、alloc、copy和mutableCopy产生的对象,在以后不用的时候,也需要release:

# 注意 initWithFormat的字符串一定要长,如果字符串短系统会采用taggepointer优化,引用计数为-1
# str1的引用计数为1
 id str1 = [[NSString alloc] initWithFormat:@"asfdasasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfasdf"]; 
# str1、str2的引用计数为均为2  因为是不可变对象的拷贝,所以str2和str1指向同一块内存空间,即[str1 copy]相当于retain操作,引用计数+1
 NSString *str2 = [str1 copy];
# str3的引用计数为1,因为产生的是可变对象,所以相当于str3指向一个新的内存空间
 NSString *str3 = [str1 mutableCopy];
    
 [str3 release];
 [str2 release];
 [str1 release];

当时每当一个新的指针引用了一块堆空间(也就是对象),
就必须手动的把此块堆空间内的 retainCount + 1。

Person* p = [Person new];//默认就是1 ,所以这里的p不需要手动操作。
Person* p2 = p;
[p2 retain];//将retainCount的值+1;

当p2指针不使用此堆空间了。要手动把 retainCount 值 - 1

[p2 release];

p不用了,也需要release

[p release];

手动计数器使用规则:
谁申请(retain),谁释放(release)

关于内存释放的本质:
当一块内存释放的时候,本质上只是给这部分字节打了标签。并没有把字节里的二进制数据全部清成0或者1.

什么是僵尸对象?
堆空间已经被标记清空,能被其他数据使用。但此时此刻,新的二进制数据还没有进来。
我们此时用一个指针指向已经标记释放了的堆空间。这个就叫僵尸对象和野指针。

ARC

2.AutoreleasePool

iOS系统针对不同场景下提供的内存方案:

在使用@autoreleasePool {}后,编译器会将其改写为:

//@autoreleasePool {
  创建了一个autoreleasePoolPage对象  push进一个标记 然后把数据依次放autoreleasePoolPage对象中

  {代码}

  依次释放掉表中的数据,直到碰到标记位停止
//}

思考 使用autorelease的对象什么时候会被释放?

    NSLog(@"1");
    @autoreleasepool {
        NSObject *str1 = [[NSObject alloc] init];
    }  # 此时释放
 
    NSLog(@"2");

3.循环引用

循环引用分类:

自循环引用

对象的强持有变量指向自身,就会造成自循环引用

相互循环引用
多循环引用
如何破除循环引用?

CADisplayLink、NSTimer的循环引用问题:

CADisplayLink、NSTimer会对target产生强引用,如果target也对他们进行了强引用,就会出现循环引用问题

解决方案1:使用中间人
NSTimer的循环引用问题解决.png

通过创建一个中间对象,令中间对象持有两个弱引用变量分别是原对象和NSTimer,NSTimer的回调是在中间对象中实现的。在中间对象实现的NSTimer的回调方法中,对中间对象持有的weak弱引用target值的判断,如果当前target值存在,则把NSTimer的回调给原对象,如果值为nil,则把NSTimer设为无效即可解除当前runloop对NSTimer的强引用和NSTimer对中间对象的强引用。

解决方案2:使用动态消息解析

ViewController中:

@interface ViewController (){
    NSTimer *_timer;
}
- (void)viewDidLoad {
   [super viewDidLoad];
   _timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: [TimerMiddleware initWithTarget: self] selector: @selector(sayhaha) userInfo: nil repeats: YES];
}
- (void)dealloc {
    [_timer invalidate];
    _timer = nil;
}

新建一个消息解析类:

@interface TimerMiddleware : NSObject
+ (instancetype)initWithTarget:(id)target;
@property (nonatomic,   weak) NSObject *target;
@end

@implementation TimerMiddleware

+ (instancetype)initWithTarget:(id)target {
    TimerMiddleware *mid = [TimerMiddleware new];
    mid.target = target;
    return mid;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}
@end
解决方案3:使用NSProxy转发消息

NSProxy是跟NSObjec一个级别的基类,用来设计做消息转发的。
NSProxy是抽象类,使用时候我们需要使用其子类
NSProxy不会跟NSObject类一样去父类搜索方法实现,会直接进入消息转发流程

@interface MyProxy : NSProxy

+ (instancetype)proxyWithTarget:(id)target;
@property (nonatomic,   weak) NSObject *target;

@end

@implementation MyProxy

+ (instancetype)proxyWithTarget:(id)target {
    MyProxy *proxy = [MyProxy alloc];
    proxy.target = target;
    return proxy;
}

# NSProxy接收到消息会自动进入到调用这个方法 进入消息转发流程
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel  {
    return [self.target methodSignatureForSelector: sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget: self.target];
}
@end

Demo详见:https://www.jianshu.com/p/9c91ee60b0dc


面试总结:

1.什么是ARC?
ARC是由LLVM和runtime共同协作来为我们实现自动引用计数管理

2.为什么weak指针指向的对象在被废弃之后会被自动置为nil?
当对象被废弃之后,dealloc的内部实现当中会调用清除弱引用的一个方法。然后在清楚弱引用的方法当中,会通过哈希算法来查找被废弃对象在弱引用表当中的位置,来提取所对应的弱引用指针的列表数组,然后进行for循环遍历,把每一个weak指针都置为nil

3.苹果是如何实现AutoreleasePool的?
AutoreleasePool是以栈为节点,以双向链表形式合成的一个数据结构

上一篇下一篇

猜你喜欢

热点阅读