Objective C 总结(十四):内存管理

Objective-C的对象都是动态创建的,Cocoa 采用了引用计数的技术进行对象生存周期的管理,对象新创建时引用计数为1,发送remain消息后对象引用计数+1,发送release消息后对象引用计数-1,当对象引用计数为0时,Objective-C运行会向对象发送dealloc消息进行销毁对象回收内存。注意:自己可以重写dealloc方法,但不要自己调用它。

Objective-C提供了三种内存管理方式:manual retain-release(MRR,手动管理),automatic reference counting(ARC,自动引用计数),garbage collection(垃圾回收)。iOS不支持垃圾回收;ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存。

NSObject提供了基本的引用计数管理方法,

- (id) retain;
- (void) release;
- (unsigned) retainCount;
- (id) autorelease;
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
 
// do something
 
[pool release];
return (0);
} // main

NSAutoreleasePool相当于一个对象集合类,在发送release消息时会向池中所有对象发送release消息。提供了简洁的写法

@autoreleasepool {
    // Code that creates autoreleased objects.
}

管理规则:

  1. 用new, alloc, copy创建的对象,初始引用计数为1,当不再需要对象时,需要向对象发送release消息。
  2. retain了某个对象,要负责release这个对象。
  3. 工厂方法创建的对象,要应用autorelease消息,不需要向对象发送release了。 
    + nameWithString: (NSString *)str
    {
        return [[NSString alloc] initWithString: str] autorelease];
    }
  4. NSAutoreleasePool的release时间是确定的,要么在收到release时release,要么在事件循环结束时使用AppKit release。
  5. 在Mac OS X平台上提供的垃圾回收器,启用了后内存管理的指令都会变成空指令。垃圾回收器与NSAutoreleasePool一样,也是在事件循环结束时触发的。

ARC

ARC is supported in Xcode 4.2 for OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5. Weak references are not supported in OS X v10.6 and iOS 4.

ARC的一个基本原则: 只要某个对象被任一strong指针引用,那么它将不会被销毁。当对象没有被任何strong指针引用时,那么就将被销毁。

ARC强制执行一些新的规则:

  1. 你不能再调用 dealloc 或者实现、调用 retain, release, retainCount, autorelease, 同样@selector(retain), @selector(release)也是不允许的, 当然你可以实现 dealloc 方法,来管理你的实例变量,或者你会调用 [systemClassInstance setDelegate:nil]等.定制的 dealloc 方法不需要写 [super dealloc],这个动作会默认调用。
    ARC只在Cocoa框架中有效,其它框架仍然需要自己管理,如 CFRetain, CFRelease 和相关Core Foundation方法。 
  2. 你不能使用 NSAllocateObject 或 NSDeallocateObject,创建对象用 alloc,运行时负责release对象。
  3. 你不能在 C 结构体中使用对象指针,因此下面代码是不可用的
    typedef struct {
        UIImage *selectedImage;
        UIImage *disabledImage;
    } ButtonImages;
  4. 不能随意在 id 与 void * 之间随意转换。编译器同样是无法管理 void * 这类 Core Foundation类型的东东,都要用 Managing Toll-Free Bridging 进行生命同期的管理。
  5. 你不能再使用 NSAutoreleasePool 对象,ARC 提供了性能更好的 @autoreleasepool block 替换原来这类使用方式。
  6. 你不能使用内存区。你不需要再使用 NSZone 等这类对象,因为现在Objective-C运行时已经忽略NSZone了,所以没必要再使用NSZone了
  7. 不能使用 new 开头的属性名,但你可以手动指定 getter 方法名
    // 非法:
    @property NSString *newTitle;
    
    // OK:
    @property (getter=theNewTitle) NSString *newTitle;

ARC引入了新的对象生存周期修饰

__strong
__weak
__unsafe_unretained
__autoreleasing
// 修饰属性时不需要__前缀,修饰局部变量是要加__前缀
@property(strong) MyClass *myObject; @property(weak) MyClass *myObject;
  1. strong。是默认使用的修饰。对象保持alive,与strong指针一样的生存周期。
  2. weak。指定一个引用,但不会保持对象alive,当weak引用到一个没有strong引用的对象时,weak引用会设置为nil。
  3. unsafe_unretained。指定一个引用,但不会保持对象alive,当引用的对象没有strong引用时,不会设置为nil。
  4. autoreleasing。用于指示参数按引用传递(id *)并且在返回时autoreleased。

当在stack上使用__weak变量时要小心,看下面代码

NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);

字符串在实例化后没有strong指针引用它,因此它马上就被释放了,在NSLog中string变量的值就是nil。

按引用传递对象时也要小心,看下面代码

NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // Report the error.
    // ...

error默认是

NSError * __strong e;

方法声明是这样的

- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

编译器重写后的代码就是

NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // Report the error.
    // ...

当本地变量声明(strong *error)和函数的参数((NSError * autoreleasing )error)不匹配的时候,编译器会创建一个临时变量。当你获得一个strong变量的地址时,你可以初始化一个id strong 的指针来声明 ,这样你就可以获得指针的原型,或者你可以声明一个变量为 __autoreleasing。

避免strong引用循环

你可以使用生命周期修饰符来避免Strong引用周期。例如,当你制作了一组父子结构的对象,而且父类要引用子类,则会出现Strong引用周期;反之,当 你将一个父类指向子类为strong引用,子类指向父类为weak引用,就可以避免出现Strong引用周期。当对象包含block objects时,这样的情况会变的更加隐性。

在手动内存管理模式下, __block id x; x不会被 retaining,在ARC模式下,__block id x , 默认被retaining。为了使手动内存管理模式代码可以在ARC模式下正常工作, 你可以用 __unsafe_unretained 来修饰__block id x;。就和”unsafe_unretained”字面上的意思一样, 不过,这样一个non-retained变量是危险的(因为它会变成一个野指 针) 会带来不良后果。有两种更好一点的方法来处理,一是使用weak (当你不需要支持iOS 4或OS X v10.6), 二是设__block值为nil,结束他的生命周期。

MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];

你可以使用 __block修饰符然后设置myController的值为nil 替代上面的方式:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;
};

再者,你也可以使用一个__weak临时变量

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

有些情况,如果类不兼容__weak时可以使用__unafe_unretained,

Stack变量初始化为nil

strong, weak, autoreleasing stack变量隐式初始化为nil,一般就是指局部变量

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}

使用编译器标记启用或禁用ARC

启用-fobjc-arc

禁用-fno-objc-arc

Managing Toll-Free Bridging

由于ARC不能管理Core Foundation Object的生命周期,所以在Core Foundation和ARC之间,我们需要使用到bridge,bridge_retained和__bridge_transfer三个转换关键字。

  1. __bridge,转换类型指针,不转换内存管理权
  2. __bridge_retained 或 CFBridgingRetain 将Objective-C类型转换成Core Foundation类型,同样也转移内存管理权,后续需要使用CFRelease或者相关方法来释放对象;
  3. __bridge_transfer or CFBridgingRelease 将非Objective-C类型转换成Objective-C类型,同时转移内存管理到ARC。
- (void)logFirstNameOfPerson:(ABRecordRef)person {
 
    NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSLog(@"Person's first name: %@", name);
    [name release];
}

可以用下面代码替换

- (void)logFirstNameOfPerson:(ABRecordRef)person {
 
    NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    NSLog(@"Person's first name: %@", name);
}

编译器会处理从Cocoa返回的CF对象

NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
[colors addObject:(id)[[UIColor lightGrayColor] CGColor]];

使用Ownership Keywords转换方法参数

NSArray *colors = [NSArray arrayWithObjects: [UIColor darkGrayColor], nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
原文地址:https://www.cnblogs.com/iprogrammer/p/3247892.html