IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> OC 学习记录随笔 之内存管理 -> 正文阅读

[移动开发]OC 学习记录随笔 之内存管理

总资料
全是随笔 笔记 与 学习资料。没有规律。

一、 CADisplayLink、NSTimer

  • CADisplayLink、NSTimer 都会对target产生强引用、如果target又对他们强引用,则会循坏引用。

以下是循环引用的解决方法。

1.1 循环引用

1.1.1 解决方法1 - block

//没有循环引用
   self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
    }];


//有循环引用, 因为timer 内部包含有target属性,造成循环引用
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(xxx) userInfo:nil repeats:YES];

1.1.2 解决方法- proxy

  • 创建 中间代理对象
  • 代理对象弱引用 target
  • 代理对于将自身的所有实例方法,全部转发到target对象本身

1.1.2.1 继承与NSProxy 的实现(推荐)

  • 直接继承与NSproxy的实现
  • 直接进入消息转发,无中间查找过程,效率高
  • NSProxy 本身就是用于消息转发的类
    头文件
//MYProxy1 头文件
@interface MYProxy1 : NSProxy
@property(nonatomic, weak) id target;

+ (instancetype)proxyWithTarget:(id)target;

@end

实现文件

//MYProxy.m 文件
#import "MYProxy1.h"

@implementation MYProxy1
+ (instancetype)proxyWithTarget:(id)target {
    //NSProxy 没有init方法
    MYProxy1 *proxy = [MYProxy1 alloc];
    proxy.target = target;
    return  proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

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

@end

1.1.2.2 继承与NSObject 的实现 (效率低)

  • 直接继承与NSObject的实现
  • 效率低,里面有消息的查找过程,查找转发的步骤比较多
  • 需要在父类里面查找方法,当所有的搜索完毕都找不到的时候,才会进入消息转发阶段

头文件

//MYProxy 头文件
#import <Foundation/Foundation.h>

@interface MYProxy : NSObject
@property(nonatomic, weak) id target; //弱引用

+ (instancetype)proxyWithTarget:(id)target;
@end

实现文件

//MYProxy.m 文件
#import "MYProxy.h"

@implementation MYProxy

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

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return  self.target; //将原本消息全部转发
}

@end

1.1.2.3 通用代码调用的实现

代码使用位置

//使用位置,target里面使用了代理对象,然后弱引用target
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:[MYProxy proxyWithTarget:self]
                                                selector:@selector(test)
                                                userInfo:nil
                                                 repeats:YES];


- (void)test {
    NSLog(@"%s", __FUNCTION__);
}
-(void)dealloc {
    NSLog(@"%s", __FUNCTION__);
    if (self.timer) {
        [self.timer invalidate];
    }
}

1.2 准确度

  • NSTimer 依赖于RunLoop,而当RunLoop任务繁重的时候,可能导致RunLoop比较耗时,导致timer触发不准确

GCD的计时器更加准时,不依赖与RunLoop

2. GCD 计时器 dispatch_source_t

  • GCD的计时器更加准时,不依赖与RunLoop
//必须强引用,不然GCD不会帮自己管理内存,所以添加为属性
@property(nonatomic, strong) dispatch_source_t timer_source;
---
    //队列 可以是主队列或者异步队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    self.timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    //时间
    int64_t start = 2.0; //2秒后开始
    int64_t interval = 1.0; //间隔
    dispatch_source_set_timer(self.timer_source, dispatch_time(DISPATCH_TIME_NOW, start*NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
    
    //回调
    dispatch_source_set_event_handler(self.timer_source, ^{
    //这里注意循环引用
        NSLog(@"ssss");
    });
    
    //默认是暂停的,所以需要开始
    dispatch_resume(self.timer_source);


-(void)dealloc {
    if(self.timer_source){
    //需要停止操作, 这个方法调用后,timer是不能被恢复的。
  		  dispatch_source_cancel(self.timer_source);
  		  self.timer_source = nil;
    }
}

3 内存布局

在这里插入图片描述

4. Tagged Pointer

Tagged Pointer 是64位引进的技术,iPhone5s是首个使用的手机,主要是为了节省内存和提高使用效率。

主要优化:NSNumber、NSDate、NSString 等小对象的存储

内存布局

NSNumber *a = @(1);为例,

  • 如果不适用Tagged Pointer,则需要栈上8字节指针,加上堆栈16字节的对象。还需要进行对象的创建销毁与引用计数的维护等
  • 使用了Tagged Pointer后,实质是一个伪指针,对象的指针中存储的数据变成了 Tag+Data 形式,并不是真实的堆地址(因为并没有创建),只需要一个指针8字节大小。
  • objc_msgSend() 在方法内部会先识别是否是Tagged Pointer, 如果是的话,就不进行方法调用,直接取值,节省了开销。
  • 当存储的值大的导致不能存下的时候,才会进行对象开辟,使用以前的方法。

代码混淆

设置环境变量 OBJC_DISABLE_TAG_OBFUSCATION 为 YES, 为关闭 Tagged Pointer 的数据混淆;
不然地址混淆后,不能看见真实的值。

  • 在 NSNumber 验证中,代码混淆变量有效。
  • 在 NSString 验证中,代码混淆变量有效。

具体见6. NSString 和 NSNumber 的存储位置及方式 章节查看对比。

标志位

//objc_internal.h
static inline bool _objc_isTaggedPointerOrNil(const void * _Nullable ptr)
{
    // this function is here so that clang can turn this into
    // a comparison with NULL when this is appropriate
    // it turns out it's not able to in many cases without this
    return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

在上面方法 可以看见是 可以通过某一二进制位 来判断是会否是Tagged Pointer 变量的。

  • MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标志位。
  • iOS下则采用 MSB(Most Significant Bit,即最高有效位)为Tagged Pointer标志位。

crash 例子

@property(nonatomic, copy) NSString *name;
dispatch_queue_t q = dispatch_queue_create("0000", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i <1000; i++) {
    dispatch_async(q, ^{
        self.name = [NSString stringWithFormat:@"abcdefghij"];
    });
}
  • crash 报错 *** -[CFString release]: message sent to deallocated instance 0x6000020ce340
  • 因为 self.name 的字符串内容 超过 tagged pointer 长度,所以使用的是原始存储方法,用的是堆内存。
  • 在多线程过程中,setter方法的_name 释放了在其他线程已经释放的内存导致crash。(可参考下节的strong的的setter代码)

解决方法:选其一

  1. 使用atomic 修饰,保证set方法线程安全
  2. 不适用长度超长的字符串,如:[NSString stringWithFormat:@"abcdefghij"];
  3. 直接使用 self.name = @“abcdefghijklm” 赋值,因为代码放在的是在常量区,直接取就行了,不用拷贝
  4. dispatch_async 里面加锁

关于 2 和 3 不会cash的结论可以 去 6. NSString 和 NSNumber 的存储位置及方式 章节查看内存位置

5. Strong 和 Copy 修饰NSString

@property(nonatomic, copy) NSString *name;
@property(nonatomic, strong) NSString *name;

两者有什么不一样呢。
从 setter 方法的角度来看, set 本质来说都是转换成MRC的。

//strong 
- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

//copy
- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}
  • 可以看出,其实setter方法里面,最主要的是对 传入的形参的 处理方式不一样,一个调用的是copy。一个是retain 来添加引用计数。

6. NSString 和 NSNumber 的存储位置及方式

NSSTring

可通过地址 和 类对象 进行区别判断

    NSString *s = [NSString stringWithFormat:@"abcdefghijklm"];  //0x600002066740
    NSString *s1 = [NSString stringWithFormat:@"abc"];           //0xa000000006362613
    NSString *s2 = @"abc";                                       //0x10422f0f0
    
    NSLog(@"\n%p\n%p\n%p", s, s1, s2);
    
    NSLog(@"%@ %@ %@", [s class], [s1 class], [s2 class]);
  • s 堆区 __NSCFString

  • s1 tagged pointer NSTaggedPointerString

  • s2 字符常量区 __NSCFConstantString
    在这里插入图片描述

  • 其他:
    s1 的值 代码 正常打印,即混淆 为: 0xc95dbca6c28acc21
    禁用混淆后打印为 0xa00000000636261361a62b63为`c‘、;;;;、

NSNumber

NSNumber *n1 = [NSNumber numberWithInt:4];
NSNumber *n2 = @4;
NSLog(@"%p %p", n1,  n2);
//混淆中   [48479:2297830] 0xada560a21cde7f6c 0x1063593b0
//取消混淆 [48499:2298606] 0xb000000000000042 0x1074b43b0
可见同 NSSTring 一样,使用 @4 创建的对象,是直接存在常量区的,

备注:部分笔记包含有MJ老师的学习资料。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-03 16:13:34  更:2022-01-03 16:15:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:48:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码