内存分区、功能

Objectve-C内存管理

2015-12-08  本文已影响213人  天天想念
为什么进行内存管理?

由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。如果内存占用过大,用户会感觉界面卡顿,app运行速度慢等,甚至系统可能会强制退出app已释放资源。这时候app就会发生闪退现象,给用户造成影响。

那么问题来了,内存管理范围有那些呢?

  • 本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。

知道了需要管理内存的范围,我们就需要知道如何管理那些不需要使用的对象。这时候我们需要引入一个概念。

引用计数器
  • 简单来说, 可以理解为:引用计数器表示有多少人正在使用这个对象 当没有任何人使用这个对象时, 系统才会回收这个对象。 也就是说,当对象的引用计数器为0时,对象占用的内存就会被系统回收;如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出 )。
引用计数器的操作
dealloc方法基本概念

当对象销毁的时候,有一个常用的方法(dealloc)也需要我们了解。

例子:

// 类声明文件
#import <Foundation/Foundation.h>

@interface Person : NSObject

@end
// 类实现文件
#import "Person.h"

@implementation Person

- (void)dealloc
{
    
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {

    // 默认情况下所有的指针都是强指针
    Person *p = [[Person alloc] init];// 引用计数为1
    // 调用release方法,引用计数会减一,
    [p release]; // 引用计数为0,自动调用delloc方法
    return 0;
}

输出结果:
[849:245988] -[Person dealloc]
MRC和ARC
#import <Foundation/Foundation.h>
@interface Person : NSObject

@end


#import "Person.h"

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
    [super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        // 只要创建一个对象默认引用计数器的值就是1
        Person *p = [[Person alloc] init];
       
        NSLog(@"retainCount = %lu", [p retainCount]); // 1
        
        // 只要给对象发送一个retain消息, 对象的引用计数器就会+1
        [p retain];
        
        NSLog(@"retainCount = %lu", [p retainCount]); // 2

        // 通过指针变量p,给p指向的对象发送一条release消息
        // 只要对象接收到release消息, 引用计数器就会-1
        // 只要一个对象的引用计数器为0, 系统就会释放对象
        [p release];
        // 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
        NSLog(@"retainCount = %lu", [p retainCount]); // 1
        
        [p release]; // 0
        NSLog(@"--------");
    }
 
    return 0;
}

输出结果:
[928:316970] retainCount = 1
[928:316970] retainCount = 2
[928:316970] retainCount = 1
[928:316970] Person dealloc
[928:316970] --------

MRC手动引用计数管理在2011苹果推出ARC之后开发的过程中基本不再使用了,但是我们还是要了解其中的原理,方便我们对ARC的理解。

 Person *p1 = [[Person alloc] init];
 __strong  Person *p2 = [[Person alloc] init];
__weak  Person *p = [[Person alloc] init];

在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写retain,release和autorelease三个关键字就好~这是ARC的基本原则。当ARC开启时,编译器在编辑阶段将自动在代码合适的地方插入retain, release和autorelease,而作为开发者,完全不需要担心编译器会做错(除非开发者自己错用ARC了),在默认情况下ARC机制是自动开启的。上边的MRC的代码用ARC写就简单多了。

#import <Foundation/Foundation.h>
@interface Person : NSObject

@end
#import "Person.h"
@implementation Person

@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc] init];
    NSLog(@"--------"); 
    return 0;
}

在main方法中,使用代码Person *p = [[Person alloc] init];创建了一个Person对象,拿一个指针p来指向这个对象的内存地址,此时对象的引用计数 = 1。开启了ARC手动内存管理机制后,在程序编译的时候编译器发现 Person这个对在初始化了之后没有其他的对象对它进行引用,那么编译器会自动在return之前添加一句[p release];的代码,引用计数 - 1 = 0,当main的大括号代码执行完的时候Person这个对象的内存会被释放。这一点和java的垃圾回收机制不太一样,在java中的垃圾回收是jvm中的gc进行处理的(不知道是否准确),而OC是编译器处理的。

int main(int argc, const char * argv[]) {
   @autoreleasepool {
        Person *p = [[Person alloc] init];
    } // 执行到这一行局部变量p释放
    // 由于没有强指针指向对象, 所以对象也释放
    return 0;
}
int main(int argc, const char * argv[]) {
   @autoreleasepool {
        Person *p = [[Person alloc] init];
        p = nil; // 执行到这一行, 由于没有强指针指向对象, 所以对象被释放
    }
    return 0;
}
int main(int argc, const char * argv[]) {
   @autoreleasepool {
        // p1和p2都是强指针
        Person *p1 = [[Person alloc] init];
        __strong Person *p2 = [[Person alloc] init];
    }
    return 0;
}
int main(int argc, const char * argv[]) {
   @autoreleasepool {
        // p是弱指针, 对象会被立即释放
        __weak Person *p1 = [[Person alloc] init];
    }
    return 0;
}

了解更多请点击:苹果官网文档-关于内存管理

@property修饰符内存管理
autorelease
Person *p = [Person new];
p = [p autorelease];
Person *p = [Person new];
p = [p autorelease];
NSLog(@"count = %lu", [p retainCount]); // 1
自动释放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 创建自动释放池
[pool release]; // [pool drain]; 销毁自动释放池
@autoreleasepool
{ //开始代表创建自动释放池

} //结束代表销毁自动释放池

autorelease基本使用
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];

Person *p = [[[Person alloc] init] autorelease];

[autoreleasePool drain];
@autoreleasepool
{ // 创建一个自动释放池
        Person *p = [[Person new] autorelease];
} // 销毁自动释放池(会给池子中所有对象发送一条release消息)
autorelease使用注意
 @autoreleasepool {
    // 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
    Person *p = [[Person alloc] init];
    [p run];
}
 @autoreleasepool {
 }
 // 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
 Person *p = [[[Person alloc] init] autorelease];
 [p run];

 // 正确写法
  @autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
 }

 // 正确写法
 Person *p = [[Person alloc] init];
  @autoreleasepool {
    [p autorelease];
 }
@autoreleasepool { // 栈底自动释放池
        @autoreleasepool {
            @autoreleasepool { // 栈顶自动释放池
                Person *p = [[[Person alloc] init] autorelease];
            }
            Person *p = [[[Person alloc] init] autorelease];
        }
    }
// 内存暴涨
    @autoreleasepool {
        for (int i = 0; i < 99999; ++i) {
            Person *p = [[[Person alloc] init] autorelease];
        }
    }
// 内存不会暴涨
 for (int i = 0; i < 99999; ++i) {
        @autoreleasepool {
            Person *p = [[[Person alloc] init] autorelease];
        }
    }

autorelease错误用法

 @autoreleasepool {
 // 错误写法, 过度释放
    Person *p = [[[[Person alloc] init] autorelease] autorelease];
 }
 @autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
    [p release]; // 错误写法, 过度释放
 }
集合对象内存管理
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        
        Person *p = [Person new]; // 1
        NSLog(@"reatinCount = %lu", [p retainCount]);
        NSMutableArray *arrM = [[NSMutableArray alloc] init];
        [arrM addObject:p]; // 2
        NSLog(@"reatinCount = %lu", [p retainCount]);
        
        [p release]; // 1
        // 当数组移除一个对象之后, 会给这个对象发送一条release消息
        [arrM removeObject:p];
    }
    return 0;
}
copy内存管理
    char *cstr = "this is a c string";
    NSString *str1 = [[NSString alloc] initWithUTF8String:cstr];
    NSLog(@"str = %lu", [str1 retainCount]);
    NSString *str2 = [str1 copy];
    NSLog(@"str = %lu", [str1 retainCount]);
    [str2 release];// 必须做一次release
    char *cstr = "this is a c string";
    NSString *str1 = [[NSString alloc] initWithUTF8String:cstr];
    NSLog(@"str = %lu", [str1 retainCount]);
    NSMutableString *str2 = [str1 mutableCopy];
    NSLog(@"str = %lu", [str1 retainCount]);
    [str2 release]; // 必须做一次release
block内存管理

block就是一段可以灵活使用的代码,你可以把它当变量传递,赋值,甚至可以把它声明到函数体里,更灵活的是你可以在里面引用外部的环境。 最后一条使得block要有更多的考虑,既然block可以引用外部环境,那如何保证block被调用的时候当时的环境变量不被释放呢?(block调用的时机可能是随意的)在说明block的内存管理之前需要先理解几个概念。

int sum(int value1, int value2)
{
    return value1 + value2;
}

int minus(int value1, int value2)
{
    return value1 - value2;
}

int main(int argc, const char * argv[]) {
    int (*sumP) (int, int) = sum;
    int res = sumP(10, 20);
    NSLog(@"res = %i", res);

    int (*minusP) (int , int) = minus;
    res = minusP(10, 20);
    NSLog(@"res = %i", res);
    return 0;
}

该代码中定义了指向函数的指针sumP和minusP,然后利用这两个指针调用对应的函数,返回函数计算后的结果。

typedef int (*calculate) (int, int);
int main(int argc, const char * argv[]) {
    calculate sumP = sum;
    int res = sumP(10, 20);
    NSLog(@"res = %i", res);
    calculate minusP = minus;
    res = minusP(10, 20);
    NSLog(@"res = %i", res);
    return 0;
}

typedef关键字:给函数或者变量起别名,就像给我们某个人起个外号一样,叫这个外号就是叫那个人。typedef的作用个人理解主要是为了简化代码,方法代码编写。
typedef int (*calculate) (int, int);给函数起别名,定义calculate是一个函数指针,有两个int类型的参数,并且有一个int类型的返回值。
calculate sumP = sum; 定义calculate类型的函数指针,该指针指向sum函数。
int res = sumP(10, 20);利用函数指针调用该函数,根据传递的参数值函数返回对应计算结果。

int main(int argc, const char * argv[]) {
    int (^sumBlock) (int, int) = ^(int value1, int value2){
        return value1 + value2;
    };
    int res = sumBlock(10 , 20);
    NSLog(@"res = %i", res);

    int (^minusBlock) (int, int) = ^(int value1, int value2){
        return value1 - value2;
    };
    res = minusBlock(10 , 20);
    NSLog(@"res = %i", res);
    return 0;
}
int main(int argc, const char * argv[]) {
    typedef int (^calculteBlock)(int , int);
    calculateBlock sumBlock = ^(int value1, int value2){
        return value1 + value2;
    };
    int res = sumBlock(10, 20);
    NSLog(@"res = %i", res);
    calculateBlock minusBlock = ^(int value1, int value2){
        return value1 - value2;
    };
    res = minusBlock(10, 20);
    NSLog(@"res = %i", res);

    return 0;
}

利用typedef给block起别名, 和指向函数的指针一样, block变量的名称就是别名。

{
      int  a = 10;
      void (^myBlock)() = ^{
            NSLog(@"a = %i", a);
      }
      myBlock();
}
输出结果: 10
{
        static int base = 100;
        void (^sum)(int, int) = ^ void (int a, int b) {
            base++;
            NSLog(@"block内部base = %d",base + a + b);
        };
        
        base = 0;
        sum(1,2);
        NSLog(@"base = %d",base);
        输出结果:
        [1410:348974] block内部base = 4
        [1410:348974] base = 1
 }

static修饰的全局变量在内存中只有一份,在Block中使用全局静态变量的时候会去当前该变量的最新的值。

int  a = 10;
void (^myBlock)() = ^{
    int a = 50;
    NSLog(@"a = %i", a);
    }
myBlock();
输出结果: 50
int b = 5;
void (^myBlock)() = ^{
    b = 20; // 报错
    NSLog(@"b = %i", b);
    };
myBlock();

 __block int b = 5;
void (^myBlock)() = ^{
    b = 20;
    NSLog(@"b = %i", b);
    };
myBlock();
输出结果: 20

局部变量加上__block修饰符之后Block内部为什么可以改变局部变量的值呢?
默认情况下, 不可以在block中修改外界变量的值,因为block中的变量和外界的变量并不是同一个变量。
如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中。因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值。

代码如下

    int a = 10;
    NSLog(@"外部a的地址:&a = %p", &a);
    void (^myBlock)() = ^{
//        a = 50;
        NSLog(@"Block内部a的地址:&a = %p", &a);
        NSLog(@"Block内部a的值:a = %i", a);
    };
    a = 20;
    NSLog(@"Block内部a的值:a = %i", a);

    myBlock();

    运行结果:
    [854:137804] 外部a的地址:&a = 0x7fff5fbff7fc
    [854:137804] Block内部a的地址:&a = 0x7fff5fbff7e8
    [854:137804] Block内部a的值:a = 10
    [854:137804] 外部a的值:a = 20


如果将上边代码做如下修改

int a = 10; => __block int a = 10;
运行结果如下:
[1029:184764] 外部a的地址:&a = 0x7fff5fbff7f8
[1029:184764] Block内部a的地址:&a = 0x7fff5fbff7f8
[1029:184764] Block内部a的值:a = 10
[1029:184764] 外部a的值:a = 20

运行结果我们可以发现,Block内部a的地址和外部a的地址是一样的,
因此我们可以说加上__block之后是地址传递,而不加__block使用外部的变量是做了某些操作分配了新的内存地址。而这个操作就是copy。

直接看代码

typedef void (^blk) (void);
{
    __block int val = 10;
    __strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
    NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1
    __weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
    NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2
    NSLog(@"copyBlock: %@", [weakPointerBlock copy]); //3    
    NSLog(@"默认test %@", ^{NSLog(@"val = %d", ++val);}); //4
}
运行结果:
[1437:366203] strongPointerBlock: <__NSMallocBlock__: 0x7fbe7a6762e0>
[1437:366203] weakPointerBlock: <__NSStackBlock__: 0x7fff5746d9e0>
[1437:366203] copyBlock: <__NSMallocBlock__: 0x7fbe7a743240>
[1437:366203] 默认test <__NSStackBlock__: 0x7fff5746d9b8>

从运行结果可以看出
1>默认情况下,Block是在栈内存中。
2>strong指针指向的block已经放到堆上了。
3>weak指针指向的block还在栈上(这种声明方法只在block上有效,正常的weak指针指向堆上对象,直接就会变nil,需要用strong指针过一道。)
4>copy了Block之后就会从栈转移到堆上

在ARC下的另外一种情况,将block作为参数返回

- (__unsafe_unretained blk) blockTest {
    int val = 11;
    return ^{NSLog(@"val = %d", val);};
}
调用方
NSLog(@"block return from function: %@", [self blockTest]);

输出结果:
block return from function: <__NSMallocBlock__: 0x7685640>

5>在ARC环境下,当block作为参数返回的时候,block也会自动被移到堆上。

所有我们可以做出如下总结:
如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作。
如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain。(在ARC下没有retain的概念,这里只是为了后面循环引用的理解)。

了解更多:Objective-C Block的实现
[Block内存管理](http://www.tanhao.me/pieces/310.html/

@property(nonatomic, readwrite, copy) completionBlock completionBlock;

self.completionBlock = ^ {
        if (self.success) {
            self.success(self.responseData);
        }
    }
};

//对象有一个Block属性,然而这个Block属性中又引用了对象的其他
//成员变量,那么就会对这个变量本身产生强应用,那么变量本身和他
//自己的Block属性就形成了循环引用。在ARC下需要修改成这样:
@property(nonatomic, readwrite, copy) completionBlock completionBlock;

__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
    if (weakSelf.success) {
        weakSelf.success(weakSelf.responseData);
    }
};

也就是生成一个对自身对象的弱引用,如果是倒霉催的项目还需要支持iOS4.3(好low啊),就用__unsafe_unretained替代__weak。如果是non-ARC环境下就将__weak替换为__block即可。non-ARC情况下,__block变量的含义是在Block中引入一个新的结构体成员变量指向这个__block变量,那么__block typeof(self) weakSelf = self;就表示Block别再对self对象retain啦,这就打破了循环引用。

循环引用问题
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
//@property(nonatomic, retain)Person *owner;
@property(nonatomic, assign)Person *owner;
@end

#import "Dog.h"
#import "Person.h"
@implementation Dog
-(void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end

Person类

#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end

#import "Person.h"
#import "Dog.h"
@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
    self.dog = nil;
    [super dealloc];
}
@end

main函数

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"

int main(int argc, const char * argv[]) {

    Person *p = [Person new];
    Dog *d = [Dog new];
    p.dog = d; // retain
    d.owner = p; // retain  assign
    
    [p release];
    [d release];
    return 0;
}

Person要拥有dog对象, 而dog对应又要拥有Person对象, 此时会形成循环retain,在main执行完的时候,dealloc方法不会自动被调用,造成内存泄露。

引用关系如图


解决办法: 不要让A retain B, B retain A,让其中一方不要做retain操作即可。
替换前:@property(nonatomic, retain)Person *owner;
替换后:@property(nonatomic, assign)Person *owner;

  • 此处替换不用weak的原因,weak是ARC提供的防止循环引用的关键字。此处代码是MRC的,所有使用assign关键字。

修正后引用关系


@interface Person : NSObject

//@property (nonatomic, retain) Dog *dog;
@property (nonatomic, strong) Dog *dog;

@end

@interface Dog : NSObject

// 错误写法, 循环引用会导致内存泄露
//@property (nonatomic, strong) Person *owner;

// 正确写法, 当如果保存对象建议使用weak
//@property (nonatomic, assign) Person *owner;
@property (nonatomic, weak) Person *owner;
@end
其他
1.MRC下的内存管理

倘若不使用ARC,手动管理内存,思路比较清晰,使用完,release转换后的对象即可。

//NSString 转 CFStringRef  
CFStringRef aCFString = (CFStringRef) [[NSString alloc] initWithFormat:@"%@", string];  
//...  
CFRelease(aCFString);  
  
  
//CFStringRef 转 NSString  
CFStringRef aCFString = CFStringCreateWithCString(kCFAllocatorDefault,  
                                                  bytes,  
                                                  NSUTF8StringEncoding);  
NSString *aNSString = (NSString *)aCFString;  
//...  
[aNSString release];  
2.ARC下的内存管理

ARC :只会对OC对象进行内存管理,苹果有句名言:ARC is only for NSObject。但是对c对象或是CF开头的对象,即存在于 Core Foundation框架 (CoreFoundation.framework 是一组C语言接口)中的对象无效,需要手动的retain 和release(CF中没有autorelease)。

CocoaFoundation指针与CoreFoundation指针转换,需要考虑的是所指向对象所有权的归属。ARC提供了3个修饰符来管理。

使用__bridge_retained 或者 CFBridgingRetain()

- (void)viewDidLoad  
{  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
      
    (void)aCFString;  
      
    //正确的做法应该执行CFRelease  
    //CFRelease(aCFString);   
} 

使用__bridge_transfer 或者 CFBridgingRelease()

- (void)viewDidLoad  
{  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
    aNSString = (__bridge_transfer NSString *)aCFString;  
} 

使用__bridge

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
    CFStringRef aCFString = (__bridge CFStringRef)aNSString;
    
    (void)aCFString;
}
转换方式 转换结果
__bridge 不改变对象所有权
__bridge_retained 或者 CFBridgingRetain() 解除 ARC 所有权
__bridge_transfer 或者 CFBridgingRelease() 给予 ARC 所有权

了解更多:Toll-Free Bridging

iOS中内存分析

1>在XCode Product菜单下,点击Analyze对工程进行静态分析(shift+command+b)。

2>在开启arc的环境下,输入以下一段代码:

+(UIImage*)getSubImage:(unsigned long)ulUserHeader
{
    UIImage * sourceImage = [UIImage imageNamed:@"header.png"];
    CGFloat height = sourceImage.size.height;
    CGRect rect = CGRectMake(0 + ulUserHeader*height, 0, height, height);
 
    CGImageRef imageRef = CGImageCreateWithImageInRect([sourceImage CGImage], rect);
    UIImage* smallImage = [UIImage imageWithCGImage:imageRef];
    //CGImageRelease(imageRef);
 
    return smallImage;
}

3>使用Analyze进行分析,在导航栏Analyze选择Analyzer查看分析结果:


上一篇下一篇

猜你喜欢

热点阅读