高性能应用开发——内存优化

87 阅读5分钟

内存问题:
1、应用的内存使用超过了单个进程的上限,应用就会被操作系统终止
2、90%的应用崩溃与内存管理有关,包括错误的内存访问和循环引用引起的内存泄露
3、Objective-C和swift运行时是用引用计数,Java和Koltin基于垃圾回收。如果开发过程对引用计数不够小心,会导致内存重复释放或者循环引用(没有及时释放)的问题

应用的内存消耗(RAM)包括栈和堆的内存消耗,内存优化的两个方向是对栈大小和堆大小的使用优化

栈大小:
1、应用中每个线程有独立的栈空间
2、线程的栈空间很小,递归调用的方法数、一个方法的全部变量都会压栈占用栈帧,递归过深和方法过多会导致栈溢出;视图渲染的时候对视图层级树递归调用layoutSubviews和drawRect方法,视图层级树过深也会导致栈溢出

堆大小:
1、一个进程所有线程共享一个堆空间,分配给应用的堆大小由操作系统分配
2、使用String、图片、JSON/XML、视图会消耗大量堆内存
3、类创建的对象的实例变量(iVars)都放在堆中
4、对象创建并被赋值时,数据可能会从栈赋值到堆;方法中使用对象的成员变量时,数据可能会从堆赋值到栈,例如下面的例子

@inteface Photo
@property (nonatomic, assign) NSInteger tag;
@property (nonatomic, copy) NSString *url;
@end

- (Photo)createPhotoWithTag:(NSInteger)tag url:(NSString *)url {
  Photo *p = [[Photo alloc] init]; //p在堆上创建
  p.tag = tag; //从tag复制到堆
  p.url = url; //url标记成copy, 可能从栈复制到堆,这取决于copyWithZone:的实现
 }
 
- (void)calTagTotal:(NSArray *)items {
  NSInteger total = 0;
  NSMutableString *combineURL = [NSMutableString string];
  for (Photo *obj in items) {
    total += obj.tag; //从堆复制到栈
    [combineURL appendString:obj.url];
  }
}

内存管理模型:
1、应用在运行时内存是如何管理的
2、内存管理模型基于引用计数,就是这个对象被持有的次数
3、引用计数不为0占用的内存不会被回收,为0则会被回收
4、一个对象在方法内部创建,这个方法就持有该对象;如果对象从方法返回,则调用者持有这个对象;这个值可以赋值给其他变量,那么其他变量也持有了这个对象
5、于某个对象相关的任务全部完成,就是放弃了持有关系
6、持有对象retain,释放对象release

自动释放对象和自动释放池:
1、autorelease可以放弃对象持有关系,同时延迟对象的销毁
2、autoreleasepool代码块中的所有对象会在代码块执行完时收到autorelease消息,每个autorelease消息会调用一个release消息
3、main函数、runloop自动创建了autoreleasepool,通常不需要手动创建autoreleasepool
4、需要手动autoreleasepool的可能场景:存在生成多个临时变量的循环;在自定义的子线程中;可以有效防止内存均值过高

ARC:
1、2011年WWDC发布的一种编译器特性
2、Objc支持MRC和ARC,Xcode默认开启ARC;Swift不支持MRC
3、项目设定:Building Settings->Apple Clang-Language- Objective-C->Objective-C Automatic Reference Counting
4、文件设定:Building Phase->Compiler Sources->Compiler Flags->-fno-obj-arc/-f

ARC规则:
1、不能显示调用retain\release\autorelease\retainCount,包括[obj retain]和@selector(retain)都会带来编译错误
2、只能实现dealloc方法,但不能显示调用它
3、不能使用NSAuatoreleasePool, 需要使用@autorelease {..}
4、不能使用NSZone 5、id类型和void *只能显示转行而不能自动转行
6、支持使用MRC和ARC混合使用

ARC变量限定符:
指明了变量和被引用对象的持有关系和生命周期
1、__strong默认限定符,变量将长时间驻留内存,等同retain
2、__weak,不保存被引用对象的存活,当没有强引用指向对象时,弱引用会被置为nil
3、__unsafe_unretained,与weak相同不保存被引用对象的存活,但不会被自动设置成nil
4、__autorelease延长被引用对象的生命到本方法调用结束

Person * __strong p1 = [[Person] alloc] init]; //对象引用计数1 在p1引用期间不会被释放
Person * __weak p2 = [[Person] alloc] init]; //对象引用计数0 会被立即释放 p2=nil
Person * __unsafe_unretained p3 = [[Person] alloc] init]; //对象引用计数0 会被立即释放 p3不会置为nil
Person * __autorelease p4 = [[Person] alloc] init]; //对象引用计数1 当前方法返回时对象被立即释放

循环引用场景:
1、委托。被委托对象强引用,委托对象弱引用
2、块。块被外部对象持有,块内部又捕获了外部对象。
3、线程与计时器

//委托导致循环引用的例子:
@interface ViewController: UIViewController
@property (strong) UpdateOperation *updateOp;
@end

@interface UpdateOperation: NSObject
- (void)setDelegate:(id)del withSelector:(SEL)sel;
@end

@implementation ViewController

- (IBAction)onRefreshClick:(id)sender {
  [self.updateOp setDelegate:self withSelector:@selector(refreshData:)];
}

- (void)refreshData {
  ///
}
@end

@implementation UpdateOperation
- (void)setDelegate:(id)del withSelector:(SEL)sel {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
      dispatch_async(dispatch_get_main_queue(), ^{
          if ([del isResponseToSelector:sel]) {
            [del perfomanceSelector:sel withObject:nil];
          }
      });
   });
 }
@end

//解决循环引用
@interface ViewController: UIViewController 
@property (strong) UpdateOperation *updateOp;
@end

@interface UpdateOperation: NSObject
@property (weak) id<UpdateOperationDelegate> delegate;
- (void)setDelegate:(id)del withSelector:(SEL)sel;
@end

@implementation ViewController

- (IBAction)onRefreshClick:(id)sender {
  [self.updateOp setDelegate:self withSelector:@selector(refreshData:)];
}

- (void)refreshData {
  ///
}
@end

@implementation UpdateOperation
- (void)setDelegate:(id)del withSelector:(SEL)sel {
  self.delegate = del;
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
      dispatch_async(dispatch_get_main_queue(), ^{
      __strong id<UpdateOperationDelegate> del = self.delegate;//块中防止对象被释放,需要等块执行完才释放使用强引用这个对象
          if ([del isResponseToSelector:sel]) {
            [del perfomanceSelector:sel withObject:nil];
          }
      });
   });
 }
@end

//block捕获外部变量导致循环引用
- (void)doSomething {
 ViewController *vc = [[ViewController alloc] init];
 [self presentViewController:vc animated:YES completion:^{
   self.data = vc.data;//blk捕获了父视图控制器 而父视图控制器持有了blk
   [self dismissViewControllerAnimated:YES completion:nil];
 }];
}

//解决循环引用
- (void)doSomething {
 ViewController *vc = [[ViewController alloc] init];
 __weak typeof(self) weakSelf = self;//弱引用防止blk持有父视图控制器的强引用
 [self presentViewController:vc animated:YES completion:^{
     __strong typeof(self) sself = weakSelf; //blk中防止父视图控制器被释放需要强引用weakSelf
     sself.data = vc.data;
     [sself dismissViewControllerAnimated:YES completion:nil];
 }];