iOS内存管理和block| 青训营笔记

616 阅读4分钟

这是我参与「第四届青训营 -iOS场」笔记创作活动的的第2篇笔记

内存管理

堆和栈

  • 栈,stack,存储push,销毁pop;寄存器中保存stack pointer,指向栈顶
  • 存储参数、局部变量、函数调用后返回地址,线程安全
  • 由高地址向低地址扩展

  • swift,由双向链表组成
  • 存储数组和对象,线程共享
  • 手动分配,低地址向高地址扩展

引用计数

  • 记录当前内存资源有多少指针在引用
  • 新增加一个可以访问这个资源的引用,计数器就会+1,反之就会-1;到0的时候就可以被回收

MRC

  • 需要手动进行引用计数的管理
  • alloc/new/copy/mutableCopy,创建的方法,引用计数=1
  • retain 引用计数+1
  • release 引用计数-1,到0时,会进行内存回收

Autorelease-Pool

  • 把对象放入到自动释放池,会进行延迟统一释放
  • 减少内存的峰值

ARC

  • 编译器自动加入内存管理代码,不需要手动维护引用计数
  • 只要有一个强指针指向内存对象,对象就不能被释放,ARC销毁的时间为强引用的个数为0,若指针需要显示声明

循环引用

  • 最常出现的是代理模式,A类引用B类,A类的代理指向C类,C中的属性再指向A

修饰符

  • strong,强引用
  • weak,弱引用
  • copy,在setter中通过深拷贝进行设置对象,调用了copy的方法

代码中用copy修饰不可变对象,保证自己拥有的是一份新的地址空间的值,保证不被外界修改,内存安全

  • assign,指定setter进行简单赋值,用于基本类型。NSInteger/CGRect,默认修饰符

深浅拷贝

  • 浅拷贝,指针拷贝,复制一个新的指针,复制后指向同一块内存区域
  • 深拷贝,内容拷贝,内存中的对象复制了一份,放在一块新的区域,复制后指向的是不同的区域

NSString和NSMutableString

  • NSString调用copy后,发生的是浅拷贝,指向的是同一个地址;调用mutableCopy,发生的是深拷贝,指向的不同的地址,创建的是新的NSMutableString对象,再赋值给NSString
  • NSMutableString调用copy和mutableCopy,发生的都是深拷贝,指向的是不同的地址
  • 对于NString,修饰符无论是strong还是copy,发生的都是浅拷贝,因为是不可变的,因此浅拷贝也没事
  • 对于NSMutableString,copy修饰后,发生深拷贝;strong发生的是浅拷贝,因为是直接赋值

内存引起的crash

  • 野指针,当所指向的对象被提前释放或者收回,但是该指针没有任何修改,以至于该指针仍指向已经被修改或回收的内存区域
  • OOM,某个应用因为内存占用过高,而被操作系统强制终止

block

  • 可以分配在栈和对上,也可以是全局的,分配在栈上的块,可以拷贝到堆上,具有引用计数
  • 本质上是一个OC对象

使用

  • 声明,可以用typedef定一个block类型,方便实用
// 返回值 (^blockname)(参数);
void (^voidBlockType)(void);
NSInteger (^compute)(NSInteger a, NSInteger b);
  • 放入函数中
+ (NSInteger) blockTest1: (voidBlockType)myBlock :(compute)mycompute;
+ (NSInteger) blockTest2: (NSInteger (^)(NSInteger a, NSInteger b))compute;
  • 函数调用
+ (NSInteger) blockTest1: (voidBlockType)myBlock :(compute)mycompute {
    myBlock();
    return mycompute(10, 20);
}
// 作为函数参数
+ (NSInteger) blockTest2: (NSInteger (^)(NSInteger a, NSInteger b))compute {
    return compute(10, 20);
}

内存

  • 根据存储方式位置分为三种
  1. 全局 __NSGlobalBlock
  2. __NSStackBlock
  3. __NSMallocBlock
  • ARC模式下,会默认调用block的copy方法,拷贝到堆区,保证使用安全
  1. block作为函数返回值
  2. 将block复制给strong指针
  3. 作为系统方法参数
  4. 在MRC下,要用copy修饰block属性

变量捕获

  • block默认是值引用,再生成block时,会复制一份block捕获的变量,其中会脱离上下文使用

无论是OC对象还是基本数据类型,都是值拷贝

  • block中引用__block修饰的值,不再捕获变量,而是对象进行名称引用,随着外部值的变化而变化
  • block通过self进行变量引用使用时,捕获的是self的值,会给self发送消息,获取变量

分类

  • 值捕获,复制对应的值,block中的变量脱离上下文
  • 名称引用,跟踪值变化
  1. 添加__block
  2. 捕获对象属性

循环引用

  • 在block中通过self引用变量,那么就会给self+,此时self也引用block,产生循环引用
  • 在block内部进行引用,需要通过weak self,但是在使用的时候要用__strong引用变量,防止在block执行过程中,self被释放

应用

  • 数组变量,enumerate方法簇
  • 传递数据,进行处理回调
  • 链式调用,block返回值为原类对象
// .h
@interface Calculate : NSObject
typedef Calculate* _Nullable (^CalculateBlock)(NSInteger num);
@property (nonatomic) NSInteger result;
- (CalculateBlock)add;
- (CalculateBlock)mul;
@end

// .m
@implementation Calculate
- (CalculateBlock)add {
    return ^(NSInteger num) {
        self.result += num;
        return self;
    };
}
- (CalculateBlock)mul {
    return ^(NSInteger num) {
        self.result *= num;
        return self;
    };
}
@end

// 调用
Calculate *calculate = [Calculate new];
// 调用add返回的闭包
calculate.add(2).mul(3);
NSLog(@"%ld", calculate.result);
  • 异步操作,GCD