【iOS】ARC(浅学版)

前言

虽然之前看了小白书Effective Objective-C 2.0关于内存管理,里面讲了关于ARC的相关内容,但最近发现好像寒假只是简单的过了一遍书,没有具体的学到什么,很多知识点还是没有理解到位导致忘的的快。所以这里重新学习一遍ARC的相关知识,会持续更新。

基础知识

强引用和弱引用

在iOS中,不可避免的会用的强引用和弱引用,特别是在block块中使用,关键词也很直白就是weak和strong。因为OC中采用的引用计数的方式,当引用计数为0时该对象会被释放,不为0时会一直存在不被释放。使用strong就会将引用计数加1,当持有的对象被释放的时候,就会对应的减掉该引用数,而当使用weak时,不会修改引用计数。

强引用

在ARC中修饰符是__strong,比如


__strong NSObject *obj;

  • 不加修饰符的话默认都是__strong

弱引用

在ARC中修饰符是__weak,比如


__weak NSObject *obj;

  • 弱引用表示并不持有对象,当所引用的对象销毁了,这个变量就自动设为nil。

两者区别

简单点讲的话,强引用持有对象,而弱引用不持有对象

下面看代码详解:

		__strong NSObject *obj1=[[NSObject alloc] init];
        
        __strong NSObject *obj2=obj1;
        
        NSLog(@"%@,%@",obj1,obj2);
        
        obj1 = nil;
        
        NSLog(@"%@,%@",obj1,obj2);

请添加图片描述

obj2声明改为__weak

    	__strong NSObject *obj1 = [[NSObject alloc] init];
    
   	 	__weak NSObject *obj2 = obj1;
    	
    	NSLog(@"%@,%@",obj1, obj2);
    
    	obj1 = nil;
    
    	NSLog(@"%@,%@",obj1, obj2);

请添加图片描述

从上面可以看出使用__strong__weak的区别,因为strong的对象会使retainCount+1,而weak的并不会。

所以第一个例子的retainCount为2,obj1 = nil之后retainCount为1,并不会对obj2造成影响,而第二个例子obj1 = nil之后retainCount 为0了,内存也跟着释放了,所以obj2也为nil

注意:这里提下weak和MRC时代的assign的区别,两者都可以用于弱引用,但是内存释放后使用weak会将对象置nil,而assign不会,会造成野指针,现在assign一般只用在基础类型。

应用场景

至于什么时候用strong 和weak?

  • 平时一般都是用strong,也就是默认不添加。
  • __weak只有在避免循环引用的时候才会使用,至于循环引用就是两个对象互相持有,或者或互相强引用对方,所以两者永远不能被销毁。必须将其中一方的引用改成__weak的才能打破这种死循环,最常见的就是delegate模式和block情况下使用。

1.当两个不同的对象各有一个强引用指向对方时就造成循环引用,会导致对象无法释放,例如我们常用的delegate, 见图:

请添加图片描述

这就是我们在申明delegate时都是用weak的原因(MRC的话是用assign)

@property (nonatomic, weak) id<Delegate> delegate

2.block的使用也会照成循环引用,比如当一个对象持有block,而该block又持有该对象时,类似下面的伪代码会照成循环引用:

[self block:^{
        
    self.number = 1;
}];

应该改为

__weak typeof(self) weakself = self;
[self block:^{
        
    weakself.number = 1;
}];

注意:只有该对象持有block,而block里的代码块又持有该对象时才需要用到weak

3.NSTimer 也会照成循环引用,所以使用了NSTimer后,释放资源前要先调用invalidate方法

[Timer invalidate];
Timer = nil;

strong和copy的区别

看完了上述关于强引用和弱引用的相关描述,大家可能会感觉这跟前面学的 对象的深浅拷贝(Objective-C——浅复制和深复制)有些类似。

这里讲述一下强引用关键字strongcopy的区别

  • strong是将对象的引用计数加一,当对象空间的值发生改变时,指向这个空间的对象都会改变。此特质标明该属性定义了一种拥有关系。为这种属性设定新值时,设置方法会先保留新值再释放旧值,然后再讲新值设置上去。
  • copy是将对象值拷贝一份,然后自己创建一个空间放进去。此特质所表达的所属关系与strong类似。然而设置方法并不保留新值而是将其拷贝。

内存管理四大原则

  • 自己生成的对象自己持有
  • 非自己生成的对象自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

ARC

ARC(Automatic Reference Counting)是在 Xcode 4.2的时候推出的编译器的一个功能,编译器简单的说就是把你写的代码翻译成机器能识别的代码,通常还会在这个过程中给你的代码添砖加瓦,ARC就是这样的一个功能,编译器在编译过程中自动地在合适的位置加入内存管理的代码,

  • 基于ARC管理内存,就一条口诀:只要还有强引用(strong reference)引用着一个对象,这个对象就不会被销毁,反之则对象会被销毁,内存会被释放。

ARC特性

  • ARC 是编译器特性,而不是 iOS 运行时特性,它也不是类似于其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化
  • “在LLVM编译器中设置ARC为有效状态,就无需再次键入retian或者release代码。”

所有权修饰符

ARC有效时,id类型和对象类型必须附加所有权修饰符,所有权修饰符一共有4种

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong

表示引用为强引用。在ARC模式下,只要一个变量有__strong标识了,就标识拥有了赋值的对象,不管赋值的对象是怎么来的。也可理解为只要见到__strong标识,编译器就会给那些不是你持有的对象自动加上retain,并且在变量超出作用域后自动调用了一次release

  • 例如,控制器中有个文本输入框属性
@property (nonatomic,strong) UITextField *textField;//
  1. 如果用户在文本框中输入first这个字符串

请添加图片描述

那么可以这样说,textFieldtext属性是NSString对象的指针,也就是拥有者,该对象保存了文本输入框的内容。

请添加图片描述
2. 如果执行了如下的代码

NSString *name = self.textField.text; 

一个对象可以有多个拥有者,在上面的代码中,name变量同样也是这个NSString对象的拥有者,也就是有两个指针指向同一个对象

请添加图片描述

  1. 随后用户改变了输入框的内容

请添加图片描述

此时textFeildtext属性就指向了新的NSString对象。但原来的NSString对象仍然还有一个所有者(name变量),因此会继续保留在内存中

请添加图片描述

  1. name变量获得新值,或者不再存在时(如局部变量方法返回时、实例变量对象释放时),原先的NSString对象就不再拥有任何所有者,retain计数降为0,这时对象会被释放
name = @"third"; 

请添加图片描述
我们称nametextField.text指针为 “Strong指针” ,因为它们能够保持对象的生命。默认所有实例变量和局部变量都是Strong指针

__weak

表示引用为弱引用。对应在定义property时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。 不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。

如下所示:

1. 执行下面的代码 (接着刚才strong的例子)

__weak NSString *name = self.textField.text; 

请添加图片描述

name变量和nameField.text属性都指向同一个NSString对象,但name不是拥有者

2. 如果文本框的内容发生变化

请添加图片描述
则原先的NSString对象就没有拥有者, 会被释放, 此时name变量会自动变成nil, 称为空指针

请添加图片描述

  • 注意:即使是使用alloc/new/copy/mutableCopy创建的对象,也不持有,结果就是这个对象没人要,所以一出来就销毁了,这里需要注意的是通过非这4个方法创建的对象,并不会因为__weak标识已创建就销毁,而是要等到超出autoreleasepool的时候才会销毁。

__unsafe_unretained

__unsafe_unretained__weak很像,唯一区别就是,__unsafe_unretained变量引用的对象再被销毁以后,不会被自动设置为nil,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe,此时你再尝试通过变量访问这个对象的属性或方法就会crash。

不安全的修饰符
  • 人如其名,__unsafe__unretained 是不安全的所有权修饰符,附有__unsafe__unretained 修饰符的变量不属于编译器的内存管理类对象。

请添加图片描述

__autoreleasing

  • 表示在autoreleasepool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。
  • __autoreleasing可以说跟__strong有点类似,你可以放心地在autoreleasing块结束此前都可以正常地使用这个对象,而不用担心它被销毁。
  • 这个对象也会被注册到autoreleasepool里,将autoreleasepool结束时候才会被销毁,而在变量的作用域结束后不会像__strong那样马上被release
__autoreleasing的应用场景

__autoreleasing在我们实际真实的iOS应用开发的时候,基本上是用不到的,我说的用不到只是针对业务程序员来说。实际上编译器是会用到它的,另外在一些方法声明的时候,你也会看到自动地给你加上的__autoreleasing,即时你没有显式地手敲出来。

隐式使用的情况

隐式使用__autoreleasing的情况就是你的代码上看不到它,但是它编译后确实会存在,比如返回值:

//这个是我们的TestObj的创建对象的一个自定义类方法
+ (TestObj *)obj
{
  /*
    本来这个对象创建出来默认是__strong的,
    但是因为编译器发现这个对象是要被当做返回值返回出去的,
    所以不能在这个方法结束时候就release这个对象,
    而应该放长远的眼光,使用autorelease,这个其实也是在MRC里
  */
  TestObj * obj = [[TestObj alloc] init];
  return obj;
}

再结合__weak的例子来说说隐式的__autoreleasing

TestObj *__weak obj = [[TestObj alloc] init];

这句TestObj对象已创建就被销毁了,因为它没有被强引用,那么假如用__weak修饰非new/alloc/copy/mutableCopy方法获得的对象会怎样?

@autoreleasepool {
  //通过类方法obj获取一个对象,注意不是使用alloc/new/copy/mutablCopy获取的对象
  //所以这个+obj内部返回对象之前其实已经给对象调用了autorelease。
  TestObj * __weak obj1 = [TestObj obj];
  //TestObj不会马上销毁,XCode编译器也不会提醒你警告。
  .
  .
  .
  //在即将超出autoreleasepool作用域的时候,obj1指向那个对象才会因为注册到了autoreleasepool而调用release方法进行释放
}

可以看到区别,虽然obj1是弱引用,但是通过类方法obj创建获得的对象不会马上被销毁,而是要等到超过autoreleasepool的作用域才会真实执行release。这就是因为obj方法里的编译器帮你返回了一个autoreleasing的对象的效果。

显式使用的情况

显式使用是你在代码上能看到它,虽然可能不是你手动敲出来的,但是你如果手动去修改它是会影响到对象的销毁的。

  • 最常见的显示使用的情况就是发生在指向对象的指针的指针时候。

什么叫做指向对象的指针的指针,有点拗口,我们知道对象内存是在heap(堆)里面,我们声明一个引用对象变量,实际上这个变量就是一个指针,它里面存放的就是这个对象再内存中的地址。

在这里插入图片描述

NSError**与autoreleasing的关系
- (BOOL)doSomethingWithError:(NSError **)error;

看到这个假方法是不是很眼熟?如果执行成功返回YES,失败返回NO,但是失败的时候还能通过读取error来知道具体错误原因,也就是说将是否创建error对象的权利交给这个方法。

接下来来说明它跟__autoreleasing之间的关系之前,先给我们的测试对象加上一些方法,变成以下的样子

//TestObj.m
@interface TestObj : NSObject
+ (TestObj *)obj;
//增加一个doSomethingWithError方法
- (BOOL)doSomethingWithError:(NSError **)error;
@end

//TestObj.h
@implementation TestObj
+ (TestObj *)obj
{
  return [[TestObj alloc] init];
}

//重写dealloc方法,未了稍后测试时打印对象的销毁
- (void)dealloc
{
  NSLog(@"dealloc:%@", [self className]);
}

/*
  注意注意,当我们先在.h头文件里写了方法的定义后,再来.m里来敲这个方法的时候,XCode会自动地补全这个方法
  可以看到error的类型多了修饰符变成NSError *__autoreleasing  _Nullable *
  _Nullable不重要,只是说明此方法这个参数不能传空
  __autoreleasing才是我们要关注的重点
*/
- (BOOL)doSomethingWithError:(NSError *__autoreleasing  _Nullable *)error {
  //做某事
  NSLog(@"do something - start");

  //做某事出错,设置错误
  *error = [[NSError alloc] initWithDomain:@"test error" code:1234 userInfo:@{}];
  /*
    由于*error类型是NSError *__autoreleasing,所以编译器自动在这里加上了
    [*error autoreleasing];
  */
  
  //返回失败
  return NO;
}
@end

为什么我们在.h头文件里写的NSError **到.m文件里会自动变成NSError* __autoreleasing* 类型?

  • 那是因为编译器看到NSError这种指向指针的指针作为参数的时候就默认给它加上__autoreleasing的标识,为了延迟销毁在指针方法里创建的对象。

而在调用方,并没有显示地声明一个__autoreleasing修饰的对象,这样传递的话就会类型不匹配(修饰符也属于类型定义),那为什么没有报错呢?

  • 因为编译器会改写我们的改码:
NSError * __strong error;
NSError *__autoreleasing tmp = error;
[obj doSomethingWithError:&tmp];
error = tmp;

它会创建一个_autoreleasing的临时变量,传给方法的实际上是这个临时变量,所以不会出现类型不匹配的警告。

鉴于以上编译器的行为,有的文章问告诉你,这个可以进行一定的优化:当你想要传递指针的指针的时候,就自己显式地申明一个__autoreleasing,比如NSError * __autoreleasing error。这样编译器就不用再隐式地帮你创建一个临时变量来做类型转换,可以提高一定的效率。

可以选择这么做,但是不是必要的,因为针对传递error这个情况不会持续频繁的动作,对app的实际影响很小。而ARC的出现的目的就是为了让程序员在写业务代码的时候更加专注入业务本身,所以就将手动内存管理简化为强引用和弱引用两个概念,并且编译器做了很多工作为了让程序员不要手写__autoreleasing

以MRC的思想分析ARC模式下对象的内存释放

注释代表在MRC中的模拟运行过程

@autoreleasepool {
  TestObj *__autoreleasing atObj;
  TestObj *__weak wObj;
  {
    //创建一个强引用的obj1变量,这个TestObj对象的retainCount = 1
    TestObj * obj1 = [[TestObj alloc] init];
    //obj1赋值给atObj
    atObj = obj1;
    /*
      由于atObj是__autoreleasing修饰的,相当于编译器会在此加上
      [atObj retain];
      [atObj autorelease];
      由于只是autorelease,不是马上release,此时obj1引用的TestObj对象的retainCount = 2,
    */

     //创建一个强引用的obj2变量,这个TestObj对象的retainCount = 1
    TestObj * obj2 = [[TestObj alloc] init];
    //obj2赋值给wObj
    wObj = obj2;
    /*
      由于wObj是__weak修饰的,编译器不会在此加上retain和autorelease
      此时obj2引用的TestObj对象的retainCount = 1,
    */
    
    /*
      编译器会对超出作用的变量所持有的对象调用release  
      obj1和obj2即将超出其作用域,所以再这里会自动调用release
      [obj1 release];
      [obj2 release];
      执行完后,obj1引用的对象retainCount = 1,所以还未销毁
      obj2引用的对象retainCount = 0,所以被销毁了
    */
  }
  //此时,超出了内部大括号的作用域,obj2引用的对象已经被销毁了,wObj也重新指向了nil
  //obj1引用的对象还存活着,虽然obj1这个变量已经没有了
}
//执行到最后这个位置,超出了autoreleasepool,atObj1引用的对象的autorelease才被触发,才真正地调用了release,才被销毁。
ARC中不存在autorelease吗?

一个常见的误解是,在ARC中没有autorelease,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease“自动”的混淆。其实你只要看一下每个iOS App的main.m文件就能知道,autorelease不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。

#import <Foundation/Foundation.h>

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

   
    }
    return 0;
}

以下两行代码的意义是相同的。

NSString *str = [[[NSString alloc] initWithFormat:@"boom"] autorelease]; // MRC

NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"boom"]; // ARC

ARC有效时属性与修饰符的对照关系

所有权修饰符的使用注意

  1. 下面代码是有问题的:
__weak NSString *str = [[NSString alloc] initWithFormat:@""];

NSLog(@"%@", str);  

虽然能编译,运行结果为null,但是会发出警告
请添加图片描述str是个weak指针,所以NSString对象没有拥有者,在创建之后就会被立即释放。

  1. 使用修饰符的正确方式
    这可能是很多人都不知道的一个问题,但却是一个特别要注意的问题。
    苹果的文档中明确地写道:

You should decorate variables correctly. When using qualifiers in an object variable declaration,the correct format is:
ClassName * qualifier variableName;

也就是像下面这两种写法

NSString * __weak str = @"haha"; // 正确!
__weak NSString *str = @"haha";  // 错误!

但是错误写法在编译时并不会报错而且是正常运行的,这是因为苹果考虑到很多人会用错,所以在编译器这边贴心地帮我们忽略并处理掉了这个错误。虽然不报错,但是我们还是应该按照正确的方式去使用这些修饰符。

Other variants are technically incorrect but are “forgiven” by the compiler. To understand the issue, seehttp://cdecl.org/.

  1. 以下代码在ARC之前是可能会行不通的,因为在手动内存管理中,从NSArray中移除一个对象时,这个对象会发送一条release消息,可能会被立即释放。随后NSLog()打印该对象就会导致应用崩溃
id obj = [array objectAtIndex:];

[array removeObjectAtIndex:];

NSLog(@"%@", obj);  

在ARC中这段代码是完全合法的,因为obj变量是一个strong指针,它成为了对象的拥有者,从NSArray中移除该对象也不会导致对象被释放

  1. @property : 想长期拥有某个对象,应该用strong,其他对象用weak,其他基本数据类型依然用assign
  2. 两端互相引用时,一端用strong、一端用weak

栈中指针默认值为nil

无论是被strongweak还是autoreleasing修饰,声明在栈中的指针默认值都会是nil。所有这类型的指针不用再初始化的时候置nil了。虽然好习惯是最重要的,但是这个特性更加降低了 “野指针” 出现的可能性。

在ARC中,以下代码会输出null而不是crash

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

ARC规则

  1. 不能使用retain/release/retainCount/autorelease
  2. 不能使用NSAllocateObject/NSDeallocateObject
  3. 必须遵守内存管理的方法命名规则
  4. 不能显示的调用dealloc,可以重写dealloc,但是不能调用[super dealloc]
  5. 使用@autoreleasepool块来替代NSAutoreleasePool
  6. 不能使用区域(NSZone)
  7. 对象型变量不能作为C语言结构体(struct/union)的成员
  8. 显示的转化“id”和“void*

ARC与__bridge

在Cocoa应用程序中,我们常常会使用到Core Foundation对象,例如CFArrayRef 或者
CFMutableDictionaryRef等等,ARC不会自动管理Core Foundation对象的生命周期,你必须根据Core
Foundation的内存管理规则来手动管理。
同时,我们经常又面临着CoreFoundation对象与Objective-C对象的转换,在转换中用到的关键字就是bridge,如果使用不好会造成内存泄露,或者crash,设置直接编译不通过。

Objective-C 与 C语言之间的转换

显式转换 id 和 void *

在MRC下,像以下代码这样将 id 变量强制转换成 void * 变量并不会出问题。

// MRC下

id obj = [[NSObject alloc] init];

void *p = obj;

更进一步,将改 void * 变量赋值给 id 变量中,调用其实例方法,运行时也不会有问题。

// MRC下

id 0 = p;

[o release];

但是以上代码在ARC下便会引起编译错误

请添加图片描述
id 型或对象型变量赋值给void * 或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,则可以使用 “__bridge转换”

OC指针与void *互相转换

使用 __bridge
- (void)OCAndVoidUse__bridgeInARC {
    
    id obj = [[NSObject alloc] init];
    
    void *p = (__bridge void *)obj;
    
    id o = (__bridge id)(p);
}

像这样,通过“__bridge转换”,idvoid * 就能够相互转换。但是转换为 void *__bridge转换,其 安全性与赋值给 __unsafe_unretained修饰符相近,甚至会更低 。如果管理时不注意赋值对象的所有者,就会 因悬垂指针而导致程序崩溃

  • 注意:此时指针变量 p 并没有持有对象,因为__bridge并不会改变持有情况。

OC指针转换为void *指针

使用 __bridge_retained
- (void)OCToVoid__bridge_retainedInARC {

    void *p = 0;
    {
        id obj = [[NSObject alloc] init];
        p = (__bridge_retained void *)obj;
//        p = (__bridge void *)obj; 报错,obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针
    }
    
    NSLog(@"class===%@",[(__bridge id)p class]);
}
  • 注意:若将上述代码中p = (__bridge_retained void *)obj;改为p = (__bridge void *)obj; 会报错,因为obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针
ARC下使用 __bridge_retained对应在MRC的过程
- (void)OCToVoid__bridge_retainedInMRC {
    /* MRC下 */
    void *p = 0;
    
    {
        id obj = [[NSObject alloc] init];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 1 */
        
        p = [obj retain];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 2 */
        
        [obj release];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 1 */
    }
    
    NSLog(@"(id)p retainCount===%lu",[(id)p retainCount]);
    /**
     * [(id)p retainCount] -> 1
     * 即
     * [obj retainCount] -> 1
     * 对象仍然存在
     */
    NSLog(@"class===%@",[(__bridge id)p class]);
}
  • __bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。
  • __bridge_retained 转换变为了 retain
  • 变量obj和变量p同时持有对象。
  • 变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于 __bridge_retained 转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃。

void *指针转换为OC指针

使用 __bridge_transfer
- (void)VoidToOC__bridge_transferInARC {
    
    void *p = 0;
    {
        id tempObj = [[NSObject alloc] init];
        p = (__bridge_retained void *)tempObj;
    }
    
    id obj = (__bridge_transfer id)p;
}

ARC下使用 __bridge_transfer对应在MRC的过程
- (void)VoidToOC__bridge_transferInMRC {
    void *p = 0;
    {
        id tempObj = [[NSObject alloc] init];
        p = [tempObj retain];
        [tempObj release];
    }
    
    id obj = (id)p;
    /**
     * 同__bridge_retained转换与retain类似,__bridge_transfer转换与release相似。
     * 在给id obj 赋值时retain即相当于__strong修饰符的变量。
     */
    [obj retain];
    [(id)p release];
}
  • __bridge_transfer 转换与 __bridge_retained 相反,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放
  • __bridge_retained 转换与retain类似,__bridge_transfer 转换与release相似。在给 id obj 赋值时 retain 即相当于 __strong 修饰符的变量。

Foundation与Core Foundation对象之间的转换

  • “Toll-free bridging 是ARC下Foundation和Core Foundation对象之间的桥梁”
  • “CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化”

两个框架的基本知识

Foundation

框架名是Foundation.framework,在Xcode新建工程时可以选择导入(其实会默认自动依赖好)。Foundation框架允许使用一些基本对象,如数字和字符串,以及一些对象集合,如数组,字典和集合,其他功能包括处理日期和时间、内存管理、处理文件系统、存储(或归档)对象、处理几何数据结构(如点和长方形)。这个框架中的类都是一些最基础的类。来自于这个框架的类名以NS开头。

Foundation框架提供了非常多好用的类, 比如:

NSString : 字符串
NSArray : 数组
NSDictionary : 字典
NSDate : 日期
NSData : 数据
NSNumber : 数字
Core Foundation

Core Foundation 对象主要使用在用C语言编写的Core Foundation 框架中,并使用引用计数的对象。在ARC无效时,Core Foundation 框架中的retain/release 分别是 CFRetain /CFRelease。
框架CoreFoundation.framework是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能。

两者关系

Core Foundation 框架和 Foundation 框架紧密相关,它们为相同功能提供接口,但 Foundation
框架提供Objective-C接口。Foundation对象 和 Core Foundation对象间的转换,俗称为桥接。如果您将Foundation 对象和 Core Foundation 类型掺杂使用,则可利用两个框架之间的 “ Toll Free Bridging”。所谓的Toll-free bridging是说您可以在某个框架的方法或函数同时使用 Core Foundation 和 Foundation 框架中的某些类型。
很多数据类型支持这一特性,其中包括群体和字符串数据类型。每个框架的类和类型描述都会对某个对象是否为 Toll-free bridged,应和什么对象桥接进行说明。

Objective-C指针与CoreFoundation指针之间的转换

ARC仅管理Objective-C指针(retainreleaseautorelease),不管理CoreFoundation指针。CF指针由人工管理,手动的CFRetainCFRelease来管理。

在ARC中,CF和OC之间的转化桥梁是 __bridge,有3种方式:

  • __bridge: 只做类型转换,不改变对象所有权,是我们最常用的转换符。
  • __bridge_transfer:ARC接管 管理内存
  • __bridge_retained:ARC释放 内存管理
简单互相转换:__bridge
从OC转CF,ARC管理内存:
  • (__bridge CFStringRef)
  • 需要人工CFRetain,否则,Cocoa指针释放后, 传出去的指针则无效。

例如:

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

上面只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当aNSString对象被ARC释放的时候,aCFString也不能被使用了。

从CF转OC,需要开发者手动释放,不归ARC管:
  • (__bridge NSString*)
  • 需要人工CFRelease,否则,OC对象的指针释放后,对象引用计数仍为1,不会被销毁。

例如:

- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);  
    NSString *aNSString = (__bridge NSString *)aCFString;  
      
    (void)aNSString;  
      
    CFRelease(aCFString);  
}  

ARC下内存管理发生改变的转换

CF–>OC:__bridge_transfer
  • 非Objective-C 指针到 Objective-C 指针并移交持有权给ARC。ARC负责释放对象。

例如:

- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
    aNSString = (__bridge_transfer NSString *)aCFString;  
}  
OC–>CF:__bridge_retained
  • Objective-C 指针到Core Foundation 指针并移交持有权。你要负责调用 CFRelease 或一个相关的函数来释放对象。

例如:

- (void)viewDidLoad  {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
      
    (void)aCFString;  
      
    //这时候,即使开启ARC,也需要手动执行CFRelease  
    CFRelease(aCFString);   
}  
怎么区分记忆 OC–>CF/CF–>OC进行转换时的细节
  • 因为ARC无法管理CF对象的指针,所以,无论是CF转OC还是OC转CF,我们只需关心CF对象的引用需要加1还是减1即可。
  1. CF转OC:CFRef必须减1
    这样原来的CF对象就被释放,所以,以后也不用手动释放。
NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef   
  1. OC转CF:CFRef 必须加1
    这样新的CF对象就不会被释放,所以,以后用完必须手动释放。
CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1  
//这时候,即使开启ARC,CF对象用完后也需要手动执行CFRelease  
CFRelease(aCFString); 

总结

  • __bridge可以实现Objective-C与C语言变量 和 Objective-C与Core Foundation对象之间的互相转换
  • __bridge不会改变对象的持有状况,既不会retain,也不会release
  • __bridge转换需要慎重分析对象的持有情况,稍不注意就会内存泄漏
  • __bridge_retained用于将OC变量转换为C语言变量 或 将OC对象转换为Core Foundation对象
  • __bridge_retained类似于retain,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后持有该对象
  • __bridge_transfer用于将C语言变量转换为OC变量 或 将Core Foundation对象转换为OC对象
  • __bridge_transfer类似于release,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放

有一个问题没解决

在CF对象转换为OC对象使用__bridge_transfer修饰时,上面已经明确说了ARC来负责释放对象,已经把CF的控制权交给OC了。就说明编译器内部自动进行release操作,那么我再手动对aCFString进行release操作时,表明我对对象进行了两次释放,这时候超出界限,应该会报crush。但是程序没有进行警告,也没有crush。为什么?????????

如下代码:

	CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);
    NSString *aNSString = (__bridge_transfer NSString *)aCFString;
    CFRelease(aCFString);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值