iOS底层day10 - 内存管理

Example1:NSTimer 、CADisplayLink 循环引用问题

先看以下代码:

self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(TimerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

self.timer为强引用,这里Timer与控制器形成了循环引用,如果要解决这个问题,一可以使用block代替即:

[NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        // todo  使用weakSelf
    }]

当类没有block时,亦或者使用中间变量 : proxy,可以让 Timer强引用Proxy,让Proxy弱引用 Target
请看以下代码:

self.timer = [NSTimer timerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(TimerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
@interface MJProxy : NSObject
+ (MJProxy *)proxyWithTarget:(id)target;
@property (nonatomic, weak)id target;
@end
+ (MJProxy *)proxyWithTarget:(id)target {
    MJProxy *proxy = [MJProxy new];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

这里运用了runtime的消息转发,当Timer的Target为MJProxy对象时,找不到TimerTest方法,即进入动态方法解析,再进入消息转发,会调用forwardingTargetForSelector方法,返回target对象即传进来的Target,调用TargetTimerTest方法
系统还有另外一个类NSProxy,我们集成它,他的原理和刚刚的MJProxy是一样的,只是在找不到方法的时候,他会直接进入:

@interface MJProxy1 : NSProxy
+ (MJProxy1 *)proxyWithTarget:(id)target;
@property (nonatomic, weak)id target;
@end
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

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

省去了objc_msgSend 前面的动态方法解析,以及forwardingTargetForSelector,性能更佳

CADisplayLink也是一个定时器,跟NSTimer的解决方法是一样的

Example2:GCD定时器

NSTimer有可能会出现不准时的情况:因为NSTImer是基于RunLoop实现的,而RunLoop是循环执行的,有可能在繁忙的时候,循环执行出现延迟
解决方案:使用GCD定时器
使用方法:

   // 定时器的执行队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 设置开始时间与间隔
    uint64_t start = 2.0f;
    uint64_t interval = 1.0f;
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC,
                              0);
    // 设置回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"111222333");
    });
    // 启动定时器
    dispatch_resume(timer);

Example3:内存布局

内存布局

如图所示,内存地址由低往高分别是 代码段数据段,栈的内存分配地址顺序是由高往低,堆的内存分配地址是由低往高

Tagged Point

NSNumber为栗子

    NSNumber *number = [NSNumber numberWithInt:10];
    NSNumber *number1 = [NSNumber numberWithInt:11];
    NSNumber *number2 = @(0xFFFFFFFFFFFFFFF);
    
    NSLog(@"%p ---%p---- %p ",number,number1,number2);

我们看他的输出:

0xb0000000000000a2 ---0xb0000000000000b2---- 0x604000035e60

我们可以看到他们的内存有很大的区别,这就是Tagged Point的作用,在使用Tagged Point 之前,像NSNumberNSStringNSData这些数据都是以对象的形式存储于堆空间,这样无疑是浪费内存空间的,而使用了Tagged Point之后,数据会存储在指针内,以标记+数据的形式存在
如:
0xb0000000000000a2b2即为NSNumber的标记,而a则对应数字10的存储

如何判断一个内存是否为Tagged Point ?
mac 下最低有效位 &11
iOS 下最高有效位 &1<<63 (64位) 为 1<<63

Copy

请看以下代码:

    NSString *str = @"123";
    NSString *str2 = [str copy];
    NSMutableString *str3 = [str mutableCopy];
    NSLog(@"%p --- %p --- %p",str,str2,str3);
    
    NSMutableString *str4 = [NSMutableString stringWithFormat:@"33333"];
    NSString *str5 = [str copy];
    NSMutableString *str6 = [str mutableCopy];
    NSLog(@"%p --- %p --- %p",str4,str5,str6);

打印输出:

0x10b3090a0 --- 0x10b3090a0 --- 0x6000004492d0
0x604000442760 --- 0x10b3090a0 --- 0x604000442940

我们可以看到内存地址的变化,当类进行copy时返回的都是不可变的类,而进行MutableCopy时,返回的都是可变的类
当不可变类进行copy时,只是对指针进行复制,指向同一个对象(不可变对象copy不需要多一块内存地址,可以节省内存空间),而mutableCopy时则拷贝了一块新内存地址
无论可变类进行copy/mutableCopy,都是拷贝一份新的内存地址

copy

不可变内存图
可变内存图

同理 数组字典也是一样的
用一张图总结:

image.png

由于copyFoundation框架的方法,当对象要进行copy时,对象则需要遵守NSCopying协议,并实现copyWithZone方法

内存总结

image.png

weak 、 unsafe_unretain

weakunsafe_unretain都不会对对象进行强引用,但是weak是相对安全的,在对象释放后,指向对象的指针会自动置nil,而unsafe_unretain不会

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

推荐阅读更多精彩内容