ARC详细解析

本文的主要内容:html

  • ARC的本质ios

  • ARC的开启与关闭git

  • ARC的修饰符github

  • ARC与Block安全

  • ARC与Toll-Free Bridging多线程

ARC的本质


ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。app

Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.ide

ARC只是相对于MRC(Manual Reference Counting或称为非ARC,下文中咱们会一直使用MRC来指代非ARC的管理方式)的一次改进,但它和以前的技术本质上没有区别。具体信息能够参考ARC编译器官方文档函数

ARC的开启与关闭


不一样于XCode4能够在建立工程时选择关闭ARC,XCode5在建立的工程是默认开启ARC,没有能够关闭ARC的选项。oop

若是须要对特定文件开启或关闭ARC,能够在工程选项中选择Targets -> Compile Phases -> Compile Sources,在里面找到对应文件,添加flag:

  • 打开ARC:-fobjc-arc

  • 关闭ARC:-fno-objc-arc

如图:

ARC的修饰符


ARC主要提供了4种修饰符,他们分别是:__strong,__weak,__autoreleasing,__unsafe_unretained。

__strong

表示引用为强引用。对应在定义property时的"strong"。全部对象只有当没有任何一个强引用指向时,才会被释放。

注意:若是在声明引用时不加修饰符,那么引用将默认是强引用。当须要释放强引用指向的对象时,须要将强引用置nil。

__weak

表 示引用为弱引用。对应在定义property时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即便有100个弱引用对象指 向也没用,该对象依然会被释放。不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。__weak通常用在delegate关系中防止循环引用或者用来修饰指向由Interface Builder编辑与生成的UI控件。

__autoreleasing

表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不该该是autorelease型的。

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

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

1
2
NSString *str = [[[NSString alloc] initWithFormat:@ "hehe" ] autorelease];  // MRC
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@ "hehe" ];  // ARC

 这里关于autoreleasepool就不作展开了,详细地信息能够参考官方文档或者其余文章。

__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的状况下。

__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.

好比经常使用的NSError的使用:

1
2
3
4
5
NSError *__autoreleasing error; 
? (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) 
?{ 
  NSLog(, error); 
}

(在上面的writeToFile方法中error参数的类型为(NSError *__autoreleasing *))

注意,若是你的error定义为了strong型,那么,编译器会帮你隐式地作以下事情,保证最终传入函数的参数依然是个__autoreleasing类型的引用。

1
2
3
4
5
6
7
NSError *error; 
NSError *__autoreleasing tempError = error;  // 编译器添加 
if  (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) 
?{ 
  error = tempError;  // 编译器添加 
  NSLog(@ "Error: %@" , error); 
}

因此为了提升效率,避免这种状况,咱们通常在定义error的时候将其(老老实实地=。=)声明为__autoreleasing类型的:

1
NSError *__autoreleasing error;

在这里,加上__autoreleasing以后,至关于在MRC中对返回值error作了以下事情:

1
*error = [[[NSError alloc] init] autorelease];

*error指向的对象在建立出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不须要关心*error指向对象的释放。

另一点,在ARC中,全部这种指针的指针 (NSError **)的函数参数若是不加修饰符,编译器会默认将他们认定为__autoreleasing类型。

好比下面的两段代码是等同的:

1
2
3
4
- (NSString *)doSomething:(NSNumber **)value
{
         // do something  
}
1
2
3
4
- (NSString *)doSomething:(NSNumber * __autoreleasing *)value
{
         // do something  
}

除非你显式得给value声明了__strong,不然value默认就是__autoreleasing的。

最后一点,某些类的方法会隐式地使用本身的autorelease pool,在这种时候使用__autoreleasing类型要特别当心。

好比NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
     [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
 
           // do stuff  
           if  (there is some error && error != nil)
           {
                 *error = [NSError errorWithDomain:@ "MyError"  ?code:1 userInfo:nil];
           }
?
     }];
?}

会隐式地建立一个autorelease pool,上面代码实际相似于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
     [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
 
           @autoreleasepool   // 被隐式建立
      {
               if  (there is some error && error != nil)
               {
                     *error = [NSError errorWithDomain:@ "MyError"  ?code:1 userInfo:nil];
               }
?          }
     }];
 
     // *error 在这里已经被dict的作枚举遍历时建立的autorelease pool释放掉了 :(  
?}

为了可以正常的使用*error,咱们须要一个strong型的临时引用,在dict的枚举Block中是用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
  __block NSError* tempError;  // 加__block保证能够在Block内被修改  
  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
  { 
     if  (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@ "MyError"  ?code:1 userInfo:nil]; 
    } ? 
 
  }] 
 
   if  (error != nil) 
  { 
    *error = tempError; 
  } ?
}

__unsafe_unretained

ARC 是在iOS 5引入的,而这个修饰符主要是为了在ARC刚发布时兼容iOS 4以及版本更低的设备,由于这些版本的设备没有weak pointer system,简单的理解这个系统就是咱们上面讲weak时提到的,可以在weak引用指向对象被释放后,把引用值自动设为nil的系统。这个修饰符在定 义property时对应的是"unsafe_unretained",实际能够将它理解为MRC时代的assign:纯粹只是将引用指向对象,没有任何 额外的操做,在指向对象被释放时依然原本来本地指向原来被释放的对象(所在的内存区域)。因此很是不安全。

如今能够彻底忽略掉这个修饰符了,由于iOS 4早已退出历史舞台不少年。

*使用修饰符的正确姿式(方式=。=)

这多是不少人都不知道的一个问题,包括以前的我,但倒是一个特别要注意的问题。

苹果的文档中明确地写道:

You should decorate variables correctly. When using qualifiers in an object variable declaration,

the correct format is:

1
ClassName * qualifier variableName;

按照这个说明,要定义一个weak型的NSString引用,它的写法应该是:

1
NSString * __weak str = @ "hehe" // 正确!

而不该该是:

1
__weak NSString *str = @ "hehe" ;   // 错误!

我相信不少人都和我同样,从开始用ARC就一直用上面那种错误的写法。

那这里就有疑问了,既然文档说是错误的,为啥编译器不报错呢?文档又解释道:

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

好 吧,看来是苹果爸爸(=。=)考虑到不少人会用错,因此在编译器这边贴心地帮咱们忽略并处理掉了这个错误:)虽然不报错,可是咱们仍是应该按照正确的方式 去使用这些修饰符,若是你之前也经常用错误的写法,那看到这里记得之后不要这么写了,哪天编译器怒了,再不支持错误的写法,就要郁闷了。

栈中指针默认值为nil

不管是被strong,weak仍是autoreleasing修饰,声明在栈中的指针默认值都会是nil。全部这类型的指针不用再初始化的时候置nil了。虽然好习惯是最重要的,可是这个特性更加下降了“野指针”出现的可能性。

在ARC中,如下代码会输出null而不是crash:)

1
2
3
4
5
- (void)myMethod 
{
     NSString *name;
     NSLog(@ "name: %@" , name);
}

ARC与Block


在MRC时代,Block会隐式地对进入其做用域内的对象(或者说被Block捕获的指针指向的对象)加retain,来确保Block使用到该对象时,可以正确的访问。

这件事情在下面代码展现的状况中要更加额外当心。

1
2
3
4
5
6
7
8
9
10
MyViewController *myController = [[MyViewController alloc] init…];
 
// 隐式地调用[myController retain];形成循环引用
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};
 
[self presentViewController:myController animated:YES completion:^{
    [myController release];  // 注意,这里调用[myController release];是在MRC中的一个常规写法,并不能解决上面循环引用的问题
}];

在 这段代码中,myController的completionHandler调用了myController的方法 [dismissViewController...],这时completionHandler会对myController作retain操做。而我 们知道,myController对completionHandler也至少有一个retain(通常准确讲是copy),这时就出现了在内存管理中最 糟糕的状况:循环引用!简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用致使了 myController和completionHandler最终都不能被释放。咱们在delegate关系中,对delegate指针用weak就是 为了不这种问题。

不过好在,编译器会及时地给咱们一个警告,提醒咱们可能会发生这类型的问题:

对这种状况,咱们通常用以下方法解决:给要进入Block的指针加一个__block修饰符。

这个__block在MRC时代有两个做用:

  • 说明变量可改

  • 说明指针指向的对象不作这个隐式的retain操做

一个变量若是不加__block,是不能在Block里面修改的,不过这里有一个例外:static的变量和全局变量不须要加__block就能够在Block中修改。

使用这种方法,咱们对代码作出修改,解决了循环引用的问题:

1
2
3
4
5
6
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
     [myController dismissViewControllerAnimated:YES completion:nil];
};
//以后正常的release或者retain

在 ARC引入后,没有了retain和release等操做,状况也发生了改变:在任何状况下,__block修饰符的做用只有上面的第一条:说明变量可 改。即便加上了__block修饰符,一个被block捕获的强引用也依然是一个强引用。这样在ARC下,若是咱们还按照MRC下的写 法,completionHandler对myController有一个强引用,而myController对completionHandler有一 个强引用,这依然是循环引用,没有解决问题:(

因而咱们还须要对原代码作修改。简单的状况咱们能够这样写:

1
2
3
4
5
6
__block MyViewController * myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
     [myController dismissViewControllerAnimated:YES completion:nil];
     myController = nil;   // 注意这里,保证了block结束myController强引用的解除
};

在 completionHandler以后将myController指针置nil,保证了completionHandler对myController 强引用的解除,不过也同时解除了myController对myController对象的强引用。这种方法过于简单粗暴了,在大多数状况下,咱们有更好 的方法。

这个更好的方法就是使用weak。(或者为了考虑iOS4的兼容性用unsafe_unretained,具体用法和weak相同,考虑到如今iOS4设备可能已经绝迹了,这里就不讲这个方法了)(关于这个方法的本质咱们后面会谈到)

为 了保证completionHandler这个Block对myController没有强引用,咱们能够定义一个临时的弱引用 weakMyViewController来指向原myController的对象,并把这个弱引用传入到Block内,这样就保证了Block对 myController持有的是一个弱引用,而不是一个强引用。如此,咱们继续修改代码:

1
2
3
4
5
6
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
     [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

这 样循环引用的问题就解决了,可是却不幸地引入了一个新的问题:因为传入completionHandler的是一个弱引用,那么当 myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运做了。在 通常的单线程环境中,这种问题出现的可能性不大,可是到了多线程环境,就很很差说了,因此咱们须要继续完善这个方法。

为 了保证在Block内可以访问到正确的myController,咱们在block内新定义一个强引用strongMyController来指向 weakMyController指向的对象,这样多了一个强引用,就能保证这个myController对象不会在completionHandler 被调用前释放掉了。因而,咱们对代码再次作出修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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...
     }
};

到此,一个完善的解决方案就完成了:)

官 方文档对这个问题的说明到这里就结束了,可是可能不少朋友会有疑问,不是说不但愿Block对原myController对象增长强引用么,这里为啥堂而 皇之地在Block内新定义了一个强引用,这个强引用不会形成循环引用么?理解这个问题的关键在于理解被Block捕获的引用和在Block内定义的引用 的区别。为了搞得明白这个问题,这里须要了解一些Block的实现原理,但因为篇幅的缘故,本文在这里就不展开了,详细的内容能够参考其余的文章,这里特 别推荐唐巧的文章和另外2位做者的博文:这个这个,讲的都比较清楚。

这里假设你们已经对Block的实现原理有所了解了。咱们就直入主题了!注意前方高能(=。=)

为了更清楚地说明问题,这里用一个简单的程序举例。好比咱们有以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include < stdio.h>
 
int main()
{
     int b = 10;
     
     int *a = &b;
     
     void (^blockFunc)() = ^(){
     
         int *c = a;
 
     };
     
     blockFunc();
     
     return  1;
}

程序中,同为int型的指针,a是被Block捕获的变量,而c是在Block内定义的变量。咱们用clang -rewrite-objc处理后,能够看到以下代码:

原main函数:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
     int b = 10;
 
     int *a = &b;
 
     void (*blockFunc)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
 
     ((void (*)(__block_impl *))((__block_impl *)blockFunc)->FuncPtr)((__block_impl *)blockFunc);
 
     return  1;
}

Block的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct __main_block_impl_0 {
   struct __block_impl impl;
   struct __main_block_desc_0* Desc;
   
   int *a;  // 被捕获的引用 a 出如今了block的结构体里面
   
   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
};

实际执行的函数:

1
2
3
4
5
6
7
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   int *a = __cself->a;  // bound by copy
 
 
         int *c = a;  // 在block中声明的引用 c 在函数中声明,存在于函数栈上
 
     }

我 们能够清楚得看到,a和c存在的位置彻底不一样,若是Block存在于堆上(在ARC下Block默认在堆上),那么a做为Block结构体的一个成员,也 天然会存在于堆上,而c不管如何,永远位于Block内实际执行代码的函数栈内。这也致使了两个变量生命周期的彻底不一样:c在Block的函数运行完毕, 即会被释放,而a呢,只有在Block被从堆上释放的时候才会释放。

回 到咱们的MyViewController的例子中,同上理,若是咱们直接让Block捕获咱们的myController引用,那么这个引用会被复制后 (引用类型也会被复制)做为Block的成员变量存在于其所在的堆空间中,也就是为Block增长了一个指向myController对象的强引用,这就 是形成循环引用的本质缘由。对于MyViewController的例子,Block的结构体能够理解是这个样子:(准确的结构体确定和如下这个有区别, 但也确定是以下这种形式:)

1
2
3
4
5
6
7
8
9
10
11
12
13
struct __main_block_impl_0 {
   struct __block_impl impl;
   struct __main_block_desc_0* Desc;
   
   MyViewController * __strong myController;   // 被捕获的强引用myController
   
   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
};

而反观咱们给Block传入一个弱引用weakMyController,这时咱们Block的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct __main_block_impl_0 {
   struct __block_impl impl;
   struct __main_block_desc_0* Desc;
   
   MyViewController * __weak weakMyController;   // 被捕获的弱引用weakMyController
   
   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
};

再 看在Block内声明的强引用strongMyController,它虽然是强引用,但存在于函数栈中,在函数执行期间,它一直存在,因此 myController对象也一直存在,可是当函数执行完毕,strongMyController即被销毁,因而它对myController对象的 强引用也被解除,这时Block对myController对象就不存在强引用关系了!加入了strongMyController的函数大致会是这个样 子:

1
2
3
4
5
6
7
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
   MyViewController * __strong strongMyController = __cself->weakMyController; 
 
     // ....
 
     }

综上所述,在ARC下(在MRC下会略有不一样),Block捕获的引用和Block内声明的引用不管是存在空间与生命周期都是大相径庭的,也正是这种不一样,形成了咱们对他们使用方式的区别。

以上就解释了以前提到的全部问题,但愿你们能看明白:)

好的,最后再提一点,在ARC中,对Block捕获对象的内存管理已经简化了不少,因为没有了retain和release等操做,实际只须要考虑循环引用的问题就好了。好比下面这种,是没有内存泄露的问题的:

1
2
3
4
5
6
7
8
9
TestObject *aObject = [[TestObject alloc] init];
     
aObject.name = @ "hehe" ;
 
self.aBlock = ^(){
     
     NSLog(@ "aObject's name = %@" ,aObject.name);
         
};

咱们上面提到的解决方案,只是针对Block产生循环引用的问题,而不是说全部的Block捕获引用都要这么处理,必定要注意!

ARC与Toll-Free Bridging


There are a number of data types in the Core Foundation framework and the Foundation framework that can be used interchangeably. This capability, called toll-free bridging, means that you can use the same data type as the parameter to a Core Foundation function call or as the receiver of an Objective-C message. 

Toll-Free Briding保证了在程序中,能够方便和谐的使用Core Foundation类型的对象和Objective-C类型的对象。详细的内容可参考官方文档。如下是官方文档中给出的一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@ "en_GB" ];
CFLocaleRef gbCFLocale = (CFLocaleRef) gbNSLocale;
CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSLog(@ "cfIdentifier: %@" , (NSString *)cfIdentifier);
// logs: "cfIdentifier: en_GB"
CFRelease((CFLocaleRef) gbNSLocale);
  
CFLocaleRef myCFLocale = CFLocaleCopyCurrent();
NSLocale * myNSLocale = (NSLocale *) myCFLocale;
[myNSLocale autorelease];
NSString *nsIdentifier = [myNSLocale localeIdentifier];
CFShow((CFStringRef) [@ "nsIdentifier: "  stringByAppendingString:nsIdentifier]);
// logs identifier for current locale

在 MRC时代,因为Objective-C类型的对象和Core Foundation类型的对象都是相同的release和retain操做规则,因此Toll-Free Bridging的使用比较简单,可是自从ARC加入后,Objective-C类型的对象内存管理规则改变了,而Core Foundation依然是以前的机制,换句话说,Core Foundation不支持ARC。

这 个时候就必需要要考虑一个问题了,在作Core Foundation与Objective-C类型转换的时候,用哪种规则来管理对象的内存。显然,对于同一个对象,咱们不可以同时用两种规则来管理, 因此这里就必需要肯定一件事情:哪些对象用Objective-C(也就是ARC)的规则,哪些对象用Core Foundation的规则(也就是MRC)的规则。或者说要肯定对象类型转换了以后,内存管理的ownership的改变。

If you cast between Objective-C and Core Foundation-style objects, you need to tell the compiler about the ownership semantics of the object using either a cast (defined in objc/runtime.h) or a Core Foundation-style macro (defined inNSObject.h)

因而苹果在引入ARC以后对Toll-Free Bridging的操做也加入了对应的方法与修饰符,用来指明用哪一种规则管理内存,或者说是内存管理权的归属。

这些方法和修饰符分别是:

__bridge(修饰符)

只是声明类型转变,可是不作内存管理规则的转变。

好比:

1
CFStringRef s1 = (__bridge CFStringRef) [[NSString alloc] initWithFormat:@ "Hello, %@!" , name];

只是作了NSString到CFStringRef的转化,但管理规则未变,依然要用Objective-C类型的ARC来管理s1,你不能用CFRelease()去释放s1。

__bridge_retained(修饰符) or CFBridgingRetain(函数)

表示将指针类型转变的同时,将内存管理的责任由原来的Objective-C交给Core Foundation来处理,也就是,将ARC转变为MRC。

好比,仍是上面那个例子

1
2
3
4
5
NSString *s1 = [[NSString alloc] initWithFormat:@ "Hello, %@!" , name];
?CFStringRef s2 = (__bridge_retained CFStringRef)s1;
? // do something with s2
//...
?CFRelease(s2);  // 注意要在使用结束后加这个

咱们在第二行作了转化,这时内存管理规则由ARC变为了MRC,咱们须要手动的来管理s2的内存,而对于s1,咱们即便将其置为nil,也不能释放内存。

等同的,咱们的程序也能够写成:

1
2
3
4
5
NSString *s1 = [[NSString alloc] initWithFormat:@ "Hello, %@!" , name];
?CFStringRef s2 = (CFStringRef)CFBridgingRetain(s1);
? // do something with s2
//...
?CFRelease(s2);  // 注意要在使用结束后加这个

__bridge_transfer(修饰符) or CFBridgingRelease(函数)

这个修饰符和函数的功能和上面那个__bridge_retained相反,它表示将管理的责任由Core Foundation转交给Objective-C,即将管理方式由MRC转变为ARC。

好比:

1
2
3
4
CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);
?NSString *s = (__bridge_transfer NSString *)result;
//or NSString *s = (NSString *)CFBridgingRelease(result);
? return  s;

这里咱们将result的管理责任交给了ARC来处理,咱们就不须要再显式地将CFRelease()了。

对了,这里你可能会注意到一个细节,和ARC中那个4个主要的修饰符(__strong,__weak,...)不一样,这里修饰符的位置是放在类型前面的,虽然官方文档中没有说明,但看官方的头文件能够知道。小伙伴们,记得别把位置写错哦:)

相关文章
相关标签/搜索