iOS学习-OC内存管理

一、内存

1、什么是内存?

ios所讲的内存也就是RAM:运行内存

2、内存的几大区域

image.png
  • 栈区:编译器自动分配并释放
  • 堆区:程序员分配和释放,ios中存放新建的对象

上面的提到的几个变量:

局部变量:函数内部的变量
全局变量:函数外部定义的变量
静态变量:用static修饰,程序执行前系统就为之静态分配(也即在运行时中不再改变分配情况)存储空间的一类变量,只能在本类中使用,
常量:常量是固定值,int ,float等等。

int a = 10; // 全局初始化区
char *p; //    全局未初始化区
 
main{
   int b; // 栈区
   char s[] = "abc" // 栈区
   char *p1; //    栈区
   char *p2 = "123456" //123456 在常量区,p2在栈上
   static int c = 0; // 全局(静态)初始化区
    
   w1 = (char *)malloc(10);
   w2 = (char *)malloc(20);
   分配得来的10和20字节的区域就在堆区。
}

二、OC内存管理

1、Tagged pointer: 带标记的指针

1、 从64位开始,iOS引入Tagged pointer技术,用于优化NSNumber, NSDate, NSString等小对象的储存。
2、如果没有Tagged pointer技术,NSNumber就是一个普通对象,需要动态分配内存、维护引用计数。使用了Tagged pointer技术之后,NSNumber指针里储存的数据变成了:tag+ data,也就是将数据存在了指针中。Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已

image.png

假如储存的是10,在0xb000a1中,b和最后面的1可能就是tag,a就是十六进制的10.
3、当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。
4、objc_msgSend能识别Tagged Pointer,直接从指针提取数据,节省了以前的数据开销。

2、如何判断一个指针为Tagged Pointer?

_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

A = 地址值&一个值
iOS平台:A的最高有效位是1
MAC平台:A的最低有效位是1

2、内存管理机制

1、引用计数机制

OC 使用了引用计数的机制来管理对象,每一个对象都有一个retainCount(引用计数),当一个对象被持有的时候,retainCount +1,不再被持有时,retainCount - 1,当retainCount 为0的时候,对象就被释放了。

2、MRC(手动引用计数)

程序员去管理内存,创建对象以及释放对象。调用 [xxx alloc] init 方法、new、retain、copy等方法, retainCount会+1,调用release方法会-1

3、ARC(自动引用计数)

1、相比MRC,ARC更加方便,不需要我们自己去管理内存,编译器会在合适的地方自动插入retain、release、autorelease代码

2、ARC帮我们做了什么?

LLVM(编译器)+Runtime共同作用来实现自动引用计数管理。

4、引用计数存在了哪里?

在64位中,引用计数可以直接存储在优化过的isa指针中,也可能存在sideTable类中。

5、autoreleasePool

1、是什么?

自动释放池。在自动释放池中调用autorelease代码,就会将这个对象放入到自动释放池中。当自动释放池执行完毕之后,就会立即向自动释放池
的对象发送一条release消息。让我们更自由的管理内存。

2、什么时候要用@autoreleasepool?

当有大量中间临时变量产生时,避免内存使用峰值过高,就要用autoreleasePool。

 NSArray *urlsArr = @[@"如果里面有很多的url"];
    for (NSURL *url in urlsArr) {
        
        @autoreleasepool {
            
            NSError *error;
            NSString *fils = [NSString stringWithContentsOfFile:url encoding:NSUTF8StringEncoding error:&error];
        }
        
    }

这个for循环里如果不使用@autoreleasepool,那临时变量内存可能是爆发式的,但是使用了@autoreleasepool,在每个@autoreleasepool结束时,里面的临时变量都会回收,内存使用更加合理。

3、autoreleasepool的原理/本质/对象到底是什么时候释放的?

从代码层面上来说

当@autoreleasepool结束时,就是代码执行到最后一个大括号那里,里面的内存就会回收释放。

从底层来说, 对象什么时候调用releas,是由runloop来控制的。
1、iOS在主线程注册了2个Observer,第一个Observer监听了RunloopEntry(就是runloop在进入之前),会调用一次autoreleasePoolPush方法

2、第二个Observer监听RunLoopBeforeWaiting(就是runloop在休眠之前)会调用autoreleasePoolPop方法和autoreleasePoolPush方法

3、第二个Observer监听RunLoopExit(runLoop在退出之前),调用autoreleasePoolPop

单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)

5、autoreleasePoolPush、autoreleasePoolPop干了什么?

1、自动释放池的主要底层结构是autoreleasePoolPush、AutoreleasePoolPage。
autoreleasePoolPush会调用push和pop方法,而push和pop方法又会调用AutoreleasePoolPage这个管理对象。

2、调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

3、AutoreleasePoolPage的数据结构


image.png

4、每个AutoreleasePoolPage对象都会占用4096个字节,除了用来存放自己内部的变量,剩下的空间用来存放autorelease对象的地址,如果autorelease对象不够放,会创建一个新的AutoreleasePoolPage对象。

5、所有的AutoreleasePoolPage对象是通过双向链表的形式连接在一起。

6、调用push方法里会调用一个begin方法,去计算出从哪里开始存放第一个autorelease对象,同时也会将一个POOL_BOUNDARY(边界)入栈(如下图所示),并且返回其存放的内存地址,在这个地址之后,就开始存放假如有1000个person对象,不够的话会再创建一个AutoreleasePoolPage对象存放autorelease对象

7、当调用pop方法的时候,也会根据POOL_BOUNDARY这个地址值,从perso1000开始,从后往前调用这1000个person对象的release方法进行释放,直到遇到POOL_BOUNDARY标记的地方。

8、id *next指向了下一个能存放autorelease对象地址的区域。

image.png

5、weak关键字

1、weak的使用场景?

设置代理属性、连线的UI控件、循环引用的情况

2、weak指针实现的原理?

一个对象销毁后,会自动调用dealloc。
将弱引用存到一个哈希表里面,当对象销毁的时候 ,取出当前对象的弱引用表,把弱引用表存储的弱引用清除掉。

3、weak和strong的区别?

weak不会使对象的引用计数加1,strong会

6、Copy关键字

1、Copy的目的:

产生一个副本,跟原对象互不影响

2、Copy和mutableCopy的区别?
  • Copy:会产生一个不可变的副本
    (注:这个不可变是指mutable和非mutable类型,不是值改变赋值)
  • mutableCopy:会产生一个可变的副本
3、浅拷贝和深拷贝

浅拷贝:指针复制,增加引用计数
深拷贝:内存拷贝,不增加引用计数


image.jpeg

只有不可变的copy是浅拷贝,所以对于不可变的都用copy修饰。这是因为不可变使用strong时,如果用可变数组给其赋值,会出现可变数组改变时,array也会改变的情况

4、在属性的set方法中是怎么写的?
@property (nonatomic , copy) NSString *name;

-(void)setName:(NSString *)name{

    if (_name != name) {
        [_name release];//释放旧值
        _name = [name copy];//copy之后赋值
    
}
5、copy的使用
  • copy修饰不可变类型
  • block 也经常使用 copy 关键字
6、自定义对象怎么使用copy?

实现NSCopying协议,调用copyWithZone方法

@interface Person : NSObject

@property (nonatomic , copy) NSString *name;
@property (nonatomic , assign) int age;

@end

-(id)copyWithZone:(NSZone *)zone{

    Person *person = [[Person allocWithZone:zone] init];
    person.age = self.age;
    person.name = self.name;
    return  person;
}
6、NSString为什么用copy修饰,不用strong修饰?

如果用strong修饰NSString,当NSMutablestring去给NSString赋值的时候,如果修改NSMutablestring的值,NSString的值也会修改。而copy不会

7、block为什么用copy?

block 使用 copy 是从 MRC 遗留下来的传统,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区。在 ARC 中写不写都行,对于 block 使用 copy 还是 strong 效果是一样的。

8、masonry block 不需要weakSelf?

https://www.jianshu.com/p/9540e2bdf242?utm_campaign=maleskine

5、术语

1、内存泄露:该释放的对象没有释放
2、野指针:指针指向的对象已经销毁

你可能感兴趣的:(iOS学习-OC内存管理)