您的当前位置:首页正文

delegate assign weak应该用哪个,避免EXC_BAD_ACCESS

2023-11-09 来源:化拓教育网

- (id)init {self.tableView.delegate = self;}- (void)dealloc {self.tableView.delegate = nil;}

 

 

delegate assign weak应该用哪个,避免EXC_BAD_ACCESS

标签:

小编还为您整理了以下内容,可能对您也有帮助:

iOS代理了协议之后没法正常回调?

关于代理不回调问题有以下几问题:
1. 没有设置代理 XXX.delegate = self 
2. 方法名写错 
3. 新方法得判断iOS版本 
4. 引用对象被释放了 
5. 具体功能info.plist配置错误 
6. 在block中设置有时会出现代理不回调问题(用通知代替)
错误1,2 :
对于错误1,2一般是很新的新手或者老手脑袋短路极不正常时犯的错误。
错误3 :
这个错误一般也可以忽略,因为使用高级API程序直接会崩溃。 错误4
引用对象呗释放了。
这个错误较之于上面三个有点难度,解决这个错误需要对内存管理有所了解.如果内存管理不好,这里也很容易出现EXC_BAD_ACCESS崩溃.所以检查对象的生命周期,代理要设置成全局变量,不要设置成局部变量。
代理设置使用weak,避免造成循环引用.而且要在dealloc方法中将代理设置为nil。
* 注意: 不要使用assign,因为assgin类型在使用结束后不会设置成nil,此时发消息的话会EXC_BAD_ACCESS。
错误5
这个问题是在处理特定功能时才能遇到的,不具有普遍性.比如使用CLLocationManager获取地理位置时不进代理的回调.你代理明明设置的很清楚,也遵循代理了,对象也没有被释放,但是代理就是不进,是不是很迷惑很纠结. 此时代理不进的另一个原因就是info.plist没有配置正确。
因为获取地理位置需要配置info.plist的属性: NSLocationAlwaysUsageDescription 或者NSLocationWhenInUseUsageDescription。
错误6
关于这个问题具体什么需求可以以后讨论一下,如果大家也遇到这个问题,这里可以作为一个*。

iOS代理了协议之后没法正常回调?

关于代理不回调问题有以下几问题:
1. 没有设置代理 XXX.delegate = self 
2. 方法名写错 
3. 新方法得判断iOS版本 
4. 引用对象被释放了 
5. 具体功能info.plist配置错误 
6. 在block中设置有时会出现代理不回调问题(用通知代替)
错误1,2 :
对于错误1,2一般是很新的新手或者老手脑袋短路极不正常时犯的错误。
错误3 :
这个错误一般也可以忽略,因为使用高级API程序直接会崩溃。 错误4
引用对象呗释放了。
这个错误较之于上面三个有点难度,解决这个错误需要对内存管理有所了解.如果内存管理不好,这里也很容易出现EXC_BAD_ACCESS崩溃.所以检查对象的生命周期,代理要设置成全局变量,不要设置成局部变量。
代理设置使用weak,避免造成循环引用.而且要在dealloc方法中将代理设置为nil。
* 注意: 不要使用assign,因为assgin类型在使用结束后不会设置成nil,此时发消息的话会EXC_BAD_ACCESS。
错误5
这个问题是在处理特定功能时才能遇到的,不具有普遍性.比如使用CLLocationManager获取地理位置时不进代理的回调.你代理明明设置的很清楚,也遵循代理了,对象也没有被释放,但是代理就是不进,是不是很迷惑很纠结. 此时代理不进的另一个原因就是info.plist没有配置正确。
因为获取地理位置需要配置info.plist的属性: NSLocationAlwaysUsageDescription 或者NSLocationWhenInUseUsageDescription。
错误6
关于这个问题具体什么需求可以以后讨论一下,如果大家也遇到这个问题,这里可以作为一个*。

iOS 属性 @property

声明 @property 时,注意关键词及字符间的空格。

@property 的本质其实是: ivar (实例变量) + getter + setter ;

接下来逐个介绍一下,每个关键词的作用:

指定获取属性对象的名字为 getterName ,如果你没有使用 getter 指定 getterName ,系统默认直接使用 propertyName 访问即可。通常来说,只有所指属性需要我们指定 isPropertyName 对应的 Bool 值时,才使用指定 getterName ,一般直接用 PropertyName 即可。 setter=setterName: 则是用来指定设置属性所使用的的 setter 方法,即设置属性值时使用 setterName: 方法,此处 setterName 是一个方法名,因此要以":"结尾,具体示例如下:

表示强引用关系,即修饰对象的引用计数会+1,通常用来修饰对象类型,可变集合及可变字符串类型。当对象引用计数为0,即不被任何对象持有,且此对象不再显示在列表中时,对象就会从内存中释放。

对象不进行 retain 操作,即不改变对象引用计数。通常用来修饰基本数据类型( NSInteger, CGFloat, Bool, NSTimeInterval 等),内存在栈上由系统自动回收。

assign 也可以用来修饰 NSObject 类型对象,因为 assign 不会改变修饰对象的引用计数,所以当修饰对象的引用计数为0,对象销毁的时候,对象指针不会被自动清空。而此时对象指针指向的地址已被销毁,这时再访问该属性会产生野指针错误: EXC_BAD_ACCESS ,因此 assign 通常用来修饰基本数据类型。

当调用修饰对象的 setter 方法时,会建立一个引用计数为 1 的新对象,即对象会在内存里拷贝一份副本,两个指针指向不同的内存地址。一般用于修饰字符串( NSString )和集合类( NSArray , NSDictionary )的不可变变量, Block 也是用 copy 修饰。

针对 copy ,这里又牵涉到了深 copy 和浅 copy 的问题,这里做一下简单介绍,后续会有文章专门探讨这个问题:

注意:当使用 copy 修饰的属性赋值时, copy 出来的是一份不可变对象。因此当对象是一个可变对象时,切记不要使用 copy 进行修饰。如果这时使用 copy 修饰,当使用 copy 出来的对象调用可变对象所特有的方法时,会因为找不到对应的方法而 Crash 。

表示弱引用关系,修饰对象的引用计数不会增加,当修饰对象被销毁的时候,对象指针会自动置为 nil ,防止出现野指针。 weak 也用来修饰 delegate ,避免循环引用。另外 weak 只能用来修饰对象类型,且是在 ARC 下新引入的修饰词, MRC 下相当于使用 assign 。

weak 的底层实现是基于 Runtime 底层维护的 SideTables 的 hash 数组,里面存储的是一个 SideTable 的数据结构:

这里重点说一下 weak_entry_t 定长数组 到 动态数组 的切换,首先会将原来定长数组中的内容转移到动态数组中,然后再在动态数组中插入新的元素。

而对于动态数组中元素个数大于或等于总空间的 3/4 时,会对动态数组进行总空间 * 2 的扩容

每次动态数组扩容,都会将原先数组中的内容重新插入到新的数组中。

备注: 此处省略了 weak 底层实现的很多细节,具体详细实现,后续会单独发文介绍。

设置属性函数 reallySetProperty(...) 的原子性非原子性实现如下:

获取属性函数 objc_getProperty(...) 的内部实现如下:

由此可见,对属性对象的加锁操作仅限于对象的 getter/setter 操作,如果是 getter/setter 以外的操作,该加锁并没有意义。因此 atomic 的原子性,仅能保障对象的 getter/setter 的线程安全,并不能保障多线程下对对象的其他操作安全。如一个线程在 getter/setter 操作,另一个线程进行 release 操作,可能会导致 crash 。此种场景的线程安全,还需要由开发者自己进行处理。

那如何给 Category 实现类似实例变量功能呢?简单列举两种方式,此处暂时不做具体详解,后续会有文章单独介绍:

根据苹果官方文档的建议,如果捕获的引用永远不会变为 nil ,我们应该使用 unowned ,否则应该使用 weak 。

@property 延展相关的技术点有很多,如: copy 相关的 NSCopying 协议, weak 底层详细的实现原理,如何保障对象的多线程安全。还有很多技术点跟 Runtime 、Runloop 有关,后续文章会陆续介绍。

知识点完整说下来就是一整套系统的协同运转,各个环节紧密相扣,最终才成为我们现在看到的样子。本文及以后的文章都会尽可能的收缩一下单篇文章探讨的范围,以期能够让话题更加紧密。

objective-c问题exc_bad_access报错

1.

  • 访问一个僵尸对象,访问僵尸对象的成员变量或者向其发消息
  • 死循环
  • 2.

  • 设置全局断点快速定位问题代码所在行
  • 开启僵尸对象调试功能 技术分享 技术分享
  • BAD_ACCESS在什么情况下出现?如何调试BAD_ACCESS错误

    标签:技术分享   错误   分享   设置   wan   死循环   代码   content   bsp   

    objective-c问题exc_bad_access报错

    1.

  • 访问一个僵尸对象,访问僵尸对象的成员变量或者向其发消息
  • 死循环
  • 2.

  • 设置全局断点快速定位问题代码所在行
  • 开启僵尸对象调试功能 技术分享 技术分享
  • BAD_ACCESS在什么情况下出现?如何调试BAD_ACCESS错误

    标签:技术分享   错误   分享   设置   wan   死循环   代码   content   bsp   

    OC中weak的原理

      weak是OC中用于打破对象间的循环引用的一种技术。

    1. weak 修饰一个变量时,表示该指针变量可以使用但不拥有该对象;及 weak 引用指向对象时,对象的引用计数并不增加。

      当 weak 引用一个对象时,Runtime会将引用的信息( key 为指向对象的指针, value 是 weak 指针的变量的地址数组)封装到 weak_entry_t 结构体中,具体存储到 DisguisedPtr 类中。

      OC的Runtime会维护一个 weak 表( weak_table_t 结构体),用于维护指向对象的所有 weak 指针,是 weak_entry_t 的结构体上的一层封装。

       weak_table_t 类型的结构体,是一个哈希表,但是在这个表中的操作并不是线程安全的。

      于是Runtime对于 weak_table_t 上又进行了一层封装,也就是 SideTable 。 SideTable 这层封装对于 weak 引用机制的主要目的是解决线程安全的问题。

       weak_entry_t 是 weak_table_t 具体存储的数据类型

    DisguisedPtr<T> 是Runtime对于普通对象指针(引用)的一个封装,目的在于隐藏 weak_table_t 的内部指针。

       RefcountMap 用于OC的引用计数机制; slock 实际上是 os_unfair_lock_s 类型,用于处理线程安全的问题; weak_table 弱引用表,用于存储对象的弱引用的数组。 SideTable 提供的方法都与锁有关。

    strong :该对象强引用delegate,引用计数+1,外界不能销毁 delegate 对象,会导致循环引用( Retain Cycles )

    weak :指明该对象并不持有delegate这个对象,delegate的销毁由外部控制。当 delegate 指向的对象销毁后,自动 delegate = nil 。

    assign :具有 weak 的效果,但需要手动设置 nil 。

    为什么用 weak 不用 assign ?

    assign 是指针赋值,不操作引用计数, delegate 用完后如果没有设置为 nil ,有可能产生野指针;而 weak 指向的 delegate 一旦用完,自动就 nil 了,不会产生野指针。

    assign :一般修饰值类型的属性;

    strong :修饰引用类型的属性,内存计数+1;

    weak :修饰引用类型的属性,原理见上述讲解;但是 weak 修饰的属性虽然不持有,在释放对象时会查询和释放其对应的弱引用表,这样是会增加内存和性能上的开销。

    OC Runtime之Weak(3)

    OC Runtime之Weak(2)

    OC Runtime之Weak(1)

    retain和strong,assign和weak的区别

    strong与weak是由ARC新引入的对象变量属性
      xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的。xcode 4.3(ios5和以上版本)之后就有了ARC,并且开始使用
      strong与weak

      assign: 用于非指针变量。用于
      基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id
      如:
      @property (nonatomic, assign) int number;
      @property (nonatomic, assign) id className;//id必须用assign
      反正记住:前面不需要加 “*” 的就用assign吧

      retain:用于指针变量。就是说你定义了一个变量,然后这个变量在程序的运行过程中会被更改,并且影响到其他方法。一般是用于字符串( NSString,NSMutableString),数组(NSMutableArray,NSArray),字典对象,视图对象(UIView ),控制器对象(UIViewController)等
      比如:
      @property (nonatomic,retain) NSString * myString;
      @property (nonatomic, retain) UIView * myView;
      @property (nonatomic, retain) UIViewController * myViewController;
      xcode 4.2不支持ARC,所以会频繁使用retain来修饰,用完释放掉,而xcode4.3支持ARC,可以使用retian,不需要手动释放内存,系统会自动为你完成,如果你在xcode4.3上面开发,retian和strong都是一样的,没区别

      strong和weak:
      事实上
      @property(nonatomic,strong) MyClass *myObject;就是相当于@property(nonatomic,retain) MyClass *myObject;@property(nonatomic, weak )id<RNNewsFeedCellDelegate>delegate;就是相当于@property(nonatomic,assign )id<RNNewsFeedCellDelegate>delegate;
      现在系统自动生成的属性都是用weak来修饰的,我想应该是xcode 4.2不支持ARC,所以大家都是用retain。现在xcode4.3支持ARC了,于是苹果建议程序员放弃retain,以后都用weak。
      weak 就是相当于assign,同样可以在xcode4.3开发环境下放弃使用assign 使用weak 来代替

      unsafe_unretained
      unsafe_unretained 就是ios5版本以下的 assign ,也就是 unsafe_unretained , weak, assign 三个都是一个样的。 因为 ios5用的是 weak ,那在ios4.3就用不了,如果你将 weak 修改为 unsafe_unretained ,那就可以用了。说到底就是iOS 5之前的系统用该属性代替 weak 来使用。

      copy:这个东西估计是大部分人最不容易搞明白的东西,我也搞不明白。听别人说这个东西基本不用了,效果其实和retain没什么两样,唯一的区别就是copy只用于NSString而不能用于NSMutableString。
      不过好像当一个类继承NSObject,那么这个类里面的属性需要使用copy,比如:
      #import <Foundation/Foundation.h>
      #import <MapKit/MKAnnotation.h>
      @interface Annotation : NSObject <MKAnnotation> {
      
      CLLocationCoordinate2D coordinate;
      NSString *title;
      NSString *subtitle;
      }
      @property (nonatomic) CLLocationCoordinate2D coordinate;
      @property (nonatomic, copy) NSString *title;
      @property (nonatomic, copy) NSString *subtitle;
      @end
      反正以后就这么用就是了

      反正就记住一点:xcode4.2用retain和assign ;xcode4.3或以上版本用strong与weak 。

    retain和strong,assign和weak的区别

    strong与weak是由ARC新引入的对象变量属性
      xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的。xcode 4.3(ios5和以上版本)之后就有了ARC,并且开始使用
      strong与weak

      assign: 用于非指针变量。用于
      基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id
      如:
      @property (nonatomic, assign) int number;
      @property (nonatomic, assign) id className;//id必须用assign
      反正记住:前面不需要加 “*” 的就用assign吧

      retain:用于指针变量。就是说你定义了一个变量,然后这个变量在程序的运行过程中会被更改,并且影响到其他方法。一般是用于字符串( NSString,NSMutableString),数组(NSMutableArray,NSArray),字典对象,视图对象(UIView ),控制器对象(UIViewController)等
      比如:
      @property (nonatomic,retain) NSString * myString;
      @property (nonatomic, retain) UIView * myView;
      @property (nonatomic, retain) UIViewController * myViewController;
      xcode 4.2不支持ARC,所以会频繁使用retain来修饰,用完释放掉,而xcode4.3支持ARC,可以使用retian,不需要手动释放内存,系统会自动为你完成,如果你在xcode4.3上面开发,retian和strong都是一样的,没区别

      strong和weak:
      事实上
      @property(nonatomic,strong) MyClass *myObject;就是相当于@property(nonatomic,retain) MyClass *myObject;@property(nonatomic, weak )id<RNNewsFeedCellDelegate>delegate;就是相当于@property(nonatomic,assign )id<RNNewsFeedCellDelegate>delegate;
      现在系统自动生成的属性都是用weak来修饰的,我想应该是xcode 4.2不支持ARC,所以大家都是用retain。现在xcode4.3支持ARC了,于是苹果建议程序员放弃retain,以后都用weak。
      weak 就是相当于assign,同样可以在xcode4.3开发环境下放弃使用assign 使用weak 来代替

      unsafe_unretained
      unsafe_unretained 就是ios5版本以下的 assign ,也就是 unsafe_unretained , weak, assign 三个都是一个样的。 因为 ios5用的是 weak ,那在ios4.3就用不了,如果你将 weak 修改为 unsafe_unretained ,那就可以用了。说到底就是iOS 5之前的系统用该属性代替 weak 来使用。

      copy:这个东西估计是大部分人最不容易搞明白的东西,我也搞不明白。听别人说这个东西基本不用了,效果其实和retain没什么两样,唯一的区别就是copy只用于NSString而不能用于NSMutableString。
      不过好像当一个类继承NSObject,那么这个类里面的属性需要使用copy,比如:
      #import <Foundation/Foundation.h>
      #import <MapKit/MKAnnotation.h>
      @interface Annotation : NSObject <MKAnnotation> {
      
      CLLocationCoordinate2D coordinate;
      NSString *title;
      NSString *subtitle;
      }
      @property (nonatomic) CLLocationCoordinate2D coordinate;
      @property (nonatomic, copy) NSString *title;
      @property (nonatomic, copy) NSString *subtitle;
      @end
      反正以后就这么用就是了

      反正就记住一点:xcode4.2用retain和assign ;xcode4.3或以上版本用strong与weak 。

    如何mrc工程中兼容编译arc文件

    例子很简单,这是一个查找歌手的应用,包含一个简单的UITableView和一个搜索框,当用户在搜索框搜索时,调用MusicBrainz的API完成名字搜索和匹配。MusicBrainz是一个开放的音乐信息平台,它提供了一个免费的XML网页服务,如果对MusicBrainz比较有兴趣的话,可以到它的官网逛一逛。
    Demo的起始例子可以从这里下载,为了照顾新人,在这边进行简单说明。在Xcode中打开下载的例子,应该可以看到如下内容(Xcode和iOS开发熟练者请跳过此段)
    AppDelegate.h/m 这是整个app的delegate,没什么特殊的,每个iOS/Mac程序在main函数以后的入口,由此进入app的生命周期。在这里加载了最初的viewController并将其放到Window中展示出来。另外appDelegate还负责处理程序开始退出等系统委托的事件
    MainViewController.h/m/xib 这个demo最主要的ViewController,含有一个TableView和一个搜索条。 SoundEffect.h/m 简单的播放声音的类,在MusicBrainz搜索完毕时播放一个音效。 main.m 程序入口,所有c程序都从main函数开始执行
    AFHTTPRequestOperation.h/m 这是有名的网络框架AFNetworking的一部分,用来帮助等简单地处理web服务请求。这里只包含了这一个类而没有将全部的AFNetworking包括进来,因为我们只用了这一个类。完整的框架代码可以在github的相关页面上找
    SVProgresHUD.h/m/bundle 是一个常用的进度条指示,当搜索的时候出现以提示用户正在搜索请稍后。bundle是资源包,里面包含了几张该类用到的图片,打进bundle包的目的一方面是为了资源容易管理,另一方面也是主要方面时为了不和其他资源发生冲突(Xcode中资源名字是资源的唯一标识,同名字的资源只能出现一次,而放到bundle包里可以避免这个潜在的问题)。SVProgresHUD可以在这里找到
    快速过一遍这个应用吧:MainViewController是UIViewController的子类,对应的xib文件定义了对应的UITableView和UISearchBar。TableView中显示searchResult数组中的内容。当用户搜索时,用AFHTTPRequestOperation发一个HTTP请求,当从MusicBrainz得到回应后将结果放入searchResult数组中并用tableView显示,当返回结果是空时在tableView中显示没找到。主要的逻辑都在MainViewController.m中的-searchBarSearchButtonClicked:方法中,生成了用于查询的URL,根据MusicBrainz的需求替换了请求的header,并且完成了返回逻辑,然后在主线程中刷新UI。整个程序还是比较简单的~
    MRC到ARC的自动转换
    回到正题,我们讨论的是ARC,关于REST API和XML解析的技术细节就暂时先忽略吧..整个程序都是用MRC来进行内存管理的,首先来让我们把这个demo转成ARC吧。基本上转换为ARC意味着把所有的retain,release和autorelease关键字去掉,在之前我们明确几件事情:
    * Xcode提供了一个ARC自动转换工具,可以帮助你将源码转为ARC
    * 当然你也可以自己动手完成ARC转换
    * 同时你也可以指定对于某些你不想转换的代码禁用ARC,这对于很多庞大复杂的还没有转至ARC的第三方库帮助很大,因为不是你写的代码你想动手修改的话代码超级容易mess…
    对于我们的demo,为了说明问题,这三种策略我们都将采用,注意这仅仅只是为了展示如何转换。实际操作中不需要这么麻烦,而且今后的绝大部分情况应该是从工程建立开始就是ARC的。

    首先,ARC是LLVM3.0编译器的特性,而老的工程特别是Xcode3时代的工程的默认编译器很可能是GCC或者LLVM-GCC,因此第一步就是确认编译器是否正确。在Project设置面板,选择target,在Build Settings中将Compiler for C/C++/Objective-C选为Apple LLVM compiler 3.0或以上。为了确保之后转换的顺利,在这里我个人建议最好把Treat Warnings as Errors和 Run Static Analyzer都打开,确保在改变编译器后代码依旧没有警告或者内存问题(虽然静态分析可能不太能保证这一点,但是聊胜于无)。好了~clean(Shift+Cmd+K)以后Bulid一下试试看,经过修改后的demo工程没有任何警告和错误,这是很好的开始。(对于存在警告的代码,这里是很好的修复的时机..请在转换前确保原来的代码没有内存问题)。

    接下来就是完成从MRC到ARC的伟大转换了。还是在Build Settings页面,把Objective-C Automatic Reference Counting改成YES(如果找不到的话请看一看搜索栏前面的小标签是不是调成All了..这个选项在Basic里是不出现的),这样我们的工程就将在所有源代码中启用ARC了。然后…试着编译一下看看,嗯..无数的错误。

    这是很正常的,因为ARC里不允许出现retain,release之类的,而MRC的代码这些是肯定会有的东西。我们可以手动一个一个对应地去修复这些错误,但是这很麻烦。Xcode为我们提供了一个自动转换工具,可以帮助重写源代码,简单来说就是去掉多余的语句并且重写一些property关键字。

    这个小工具是Edit->Refactor下的Convert to Objective-C ARC,点击后会让我们选择要转换哪几个文件,在这里为了说明除了自动转换外的方法,我们不全部转换,而只是选取其中几个转换(MainViewController.m和AFHTTPRequestOperation.m不做转换)。注意到这个对话框上有个警告标志告诉我们target已经是ARC了,这是由于之前我们在Build Settings里已经设置了启用ARC,其实直接在这里做转换后Xcode会自动帮我们开启ARC。点击检查后,Xcode告诉我们一个不幸的消息,不能转换,需要修复ARC readiness issues..后面还告诉我们要看到所有的所谓的ARC readiness issues,可以到设置的General里把Continue building after errors勾上…What the f**k…好吧~先乖乖听从Xcode的建议”Cmd+,“然后Continue building after errors打勾然后再build。

    问题依旧,不过在issue面板里应该可以看到所有出问题的代码了。在我们的例子里,问题出在SoundEffect.m里:
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
    if (fileURL != nil)
    {
    SystemSoundID theSoundID;
    OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)fileURL, &theSoundID);
    if (error == kAudioServicesNoError)
    soundID = theSoundID;
    }

    这里代码尝试把一个NSURL指针强制转换为一个CFURLRef指针。这里涉及到一些Core Services特别是Core Foundation(CF)的东西,AudioServicesCreateSystemSoundID()函数接受CFURLRef为参数,这是一个CF的概念,但是我们在较高的抽象层级上所建立的是NSURL对象。在Cocoa框架中,有很多顶层对象对底层的抽象,而在使用中我们往往可以不加区别地对这两种对象进行同样的对待,这类对象即为可以”自由桥接”的对象(toll-free bridged)。NSURL和CFURLRef就是一对好基友好例子,在这里其实CFURLRef和NSURL是可以进行替换的。
    通常来说为了代码在底层级上的正确,在iOS开发中对基于C的API的调用所传入的参数一般都是CF对象,而Objective-C的API调用都是传入NSObject对象。因此在采用自由桥接来调用C API的时候就需要进行转换。但是在使用ARC编译的时候,因为内存管理的原因,编译器需要知道对这些桥接对象要实行什么样的操作。如果一个NSURL对象替代了CFURLRef,那么在作用区域外,应该由谁来决定内存释放和对象销毁呢?为了解决这个问题,引入了bridge,bridge_transfer和__bridge_retained三个关键字。关于选取哪个关键字做转换,需要由实际的代码行为来决定。如果对于自由桥接机制感兴趣,大家可以自己找找的相关内容,比如适用类型、内部机制和一个简介~之后我也会对这个问题做进一步说明
    回到demo,我们现在在上面的代码中加上__bridge进行转换。然后再运行ARC转换工具,这时候检查应该没有其他问题了,那么让我们进行转换吧~当然在真正转换之前会有一个预览界面,在这里我们最好检查一下转换是不是都按照预想进行了..要是出现大面积错误又没有备份或者出现各种意外的话就可以哭了…
    前后变化的话比较简单,基本就是去掉不需要的代码和改变property的类型而已,其实有信心的话不太需要每次都看,但是如果是第一次执行ARC转换的操作的话,我还是建议稍微看一下变化,这样能对ARC有个直观上的了解。检查一遍,应该没什么问题了..需要注意的是main.m里关于autoreleasepool的变化以及所有dealloc调用里的[super dealloc]的删除,它们同样是MRC到ARC的主要变化..
    好了~转换完成以后我们再build看看..应该会有一些警告。对于原来retain的property,比较保险的做法是转为strong,在LLVM3.0中自动转换是这样做的,但是在3.1中property默认并不是strong,这样在使用property赋值时存在警告,我们在property声明里加上strong就好了~然后就是SVProgressHUD.m里可能存在问题,这是由于原作者把release的代码和其他代码写在一行了.导致自动转换时只删掉了部分,而留下了部分不应该存在的代码,删掉对变量的空调用就好了..
    自动转换之后的故事
    然后再编译,没有任何错误和警告了,好棒~等等…我们刚才没有对MainViewController和AFHTTPRequestOperation进行处理吧,那么这两个文件里应该还存在release之类的东西吧..?看一看这两个文件,果然有各种release,但是为什么能编译通过呢?!明明刚才在自动转换前他们还有N多错的嘛…答案很简单,在自动转换的时候因为我们没有勾选这两个文件,因此编译器在自动转换过后为这两个文件标记了”不使用ARC编译”。可以看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m两个文件后面被加上了-fno-objc-arc的编译标记,被加上该标记的文件将不使用ARC规则进行编译。

    提供这样的编译标记的原因是显而易见的,因为总是有一部分的第三方代码并没有转换为ARC(可能是由于维护者犯懒或者已经终止维护),所以对于这部分代码,为了迅速完成转换,最好是使用-fno-objc-arc标记来禁止在这些源码上使用ARC。
    为了方便查找,再此列出一些在转换时可能出现的问题,当然在我们使用ARC时也需要注意避免代码中出现这些问题:
    “Cast … requires a bridged cast”
    这是我们在demo中遇到的问题,不再赘述
    Receiver type ‘X’ for instance message is a forward declaration
    这往往是引用的问题。ARC要求完整的前向引用,也就是说在MRC时代可能只需要在.h中申明@class就可以,但是在ARC中如果调用某个子类中未覆盖的父类中的方法的话,必须对父类.h引用,否则无法编译。
    Switch case is in protected scope
    现在switch语句必须加上{}了,ARC需要知道局部变量的作用域,加上{}后switch语法更加严格,否则遇到没有break的分支的话内存管理会出现问题。
    A name is referenced outside the NSAutoreleasePool scope that it was declared in
    这是由于写了自己的autoreleasepool,而在转换时在原来的pool中申明的变量在新的@autoreleasepool中作用域将被局限。解决方法是把变量申明拿到pool的申请之前。
    ARC forbids Objective-C objects in structs or unions
    可以说ARC所引入的最严格的*是不能在C结构体中放OC对象了..因此类似下面这样的代码是不可用的
    typedef struct {
    UIImage *selectedImage;
    UIImage *disabledImage;
    } ButtonImages;

    这个问题只有乖乖想办法了..改变原来的结构什么的..

    手动转换
    刚才做了对demo的大部分转换,还剩下了MainViewController和AFHTTPRequestOperation是MRC。但是由于使用了-fno-objc-arc,因此现在编译和运行都没有问题了。下面我们看看如何手动把MainViewController转为ARC,这也有助于进一步理解ARC的规则。
    首先,我们需要转变一下观念…对于MainViewController.h,在.h中申明了两个实例变量:
    @interface MainViewController : UIViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    }

    我们不妨仔细考虑一下,为什么在interface里出现了实例变量的申明?通常来说,实例变量只是在类的实例中被使用,而你所写的类的使用者并没有太多必要了解你的类中有哪些实例变量。而对于绝大部分的实例变量,应该都是protected或者private的,对它们的操作只应该用setter和getter,而这正是property所要做的工作。可以说,将实例变量写在头文件中是一种遗留的陋习。更好的写实例变量名字的地方应当与类实现关系更为密切,为了隐藏细节,我们应该考虑将它们写在@implementation里。好消息是,在LLVM3.0中,不论是否开启ARC,编译器是支持将实例变量写到实现文件中的。甚至如果没有特殊需要又用了property,我们都不应该写无意义的实例变量申明,因为在@synthesize中进行绑定时,我们就可以设置变量名字了,这样写的话可以让代码更加简洁。
    在这里我们对着两个实例变量不需要property(外部成员不应当能访问到它们),因此我们把申明移到.m里中。修改后的.h是这样的,十分简洁一看就懂~
    #import
    @interface MainViewController : UIViewController
    @property (nonatomic, retain) IBOutlet UITableView *tableView;
    @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
    @end

    然后.m的开头变成这样:
    @implementation MainViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    }

    这样的写法让代码相当灵活,而且不得不承认.m确实是这些实例变量的应该在的地方…build一下,没问题..当然对于SoundEffect类也可以做相似的操作,这会让使用你的类的人很开心,因为.h越简单越好..P.S.另外一个好处可以减少.h里的引用,减少编译时间(虽然不明显=。=)
    然后就可以在MainViewController里启用ARC了,方法很简单,删掉Build Phases里相关文件的-fno-objc-arc标记就可以了~然后..然后当然是一大堆错误啦。我们来手动一个个改吧,虽然谈不上乐趣,但是成功以后也会很有成就~(如果你不幸在启用ARC后build还是成功了,恭喜你遇到了Xcode的bug,请Cmd+Q然后重新打开Xcode把=_=)
    dealloc
    红色最密集的地方是dealloc,因为每一行都是release。由于在这里dealloc并没有做除了release和super dealloc之外的任何事情,因此简单地把整个方法删掉就好了。当然,在对象被销毁时,dealloc还是会被调用的,因此我们在需要对非ARC管理的内存进行管理和必要的逻辑操作的时候,还是应该保留dealloc的,当然这涉及到CF以及以下层的东西:比如对于retain的CF对象要CFRelease(),对于malloc()到堆上的东西要free()掉,对于添加的observer可以在这里remove,schele的timer在这里invalidate等等~[super dealloc]这个消息也不再需要发了,ARC会自动帮你搞定。
    另外,在MRC时代一个常做的事情是在dealloc里把指向自己的delegate设成nil(否则就等着EXC_BAD_ACCESS吧 ),而现在一般delegate都是weak的,因此在self被销毁后这个指针自动被置成nil了,你不用再为之担心,好棒啊..
    去掉各种release和autorelease
    这个很直接,没有任何问题。去掉就行了~不再多说
    讨论一下Property
    在MainViewController.m里的类扩展中定义了两个property:
    @interface MainViewController ()
    @property (nonatomic, retain) NSMutableArray *searchResults;
    @property (nonatomic, retain) SoundEffect *soundEffect;
    @end

    申明的类型是retain,关于retain,assign和copy的讨论已经烂大街了,在此不再讨论。在MRC的年代使用property可以帮助我们使用dot notation的时候简化对象的retain和copy,而在ARC时代,这就显得比较多余了。在我看来,使用property和点方法来调用setter和getter是不必要的。property只在将需要的数据在.h中暴露给其他类时才需要,而在本类中,只需要用实例变量就可以。因此我们可以移去searchResults和soundEffect的@property和@synthesize,并将起移到实例变量申明中:
    @implementation MainViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    NSMutableArray *searchResults;
    SoundEffect *soundEffect;
    }

    相应地,我们需要将对应的self.searchResult和self.soundEffect的self.都去去掉。在这里需要注意的是,虽然我们去掉了soundEffect的property和synthesize,但是我们依然有一个lazy loading的方法- (SoundEffect *)soundEffect,神奇之处在于(可能你以前也不知道),点方法并不需要@property关键字的支持,虽然大部分时间是这么用的..(property只是对setter或者getter的申明,而点方法是对其的调用,在这个例子的实现中我们事实上实现了-soundEffect这个getter方法,所以点方法在等号右边的getter调用是没有问题的)。为了避免误解,建议把self.soundEffect的getter调用改写成[self soundEffect]。

    如何mrc工程中兼容编译arc文件

    例子很简单,这是一个查找歌手的应用,包含一个简单的UITableView和一个搜索框,当用户在搜索框搜索时,调用MusicBrainz的API完成名字搜索和匹配。MusicBrainz是一个开放的音乐信息平台,它提供了一个免费的XML网页服务,如果对MusicBrainz比较有兴趣的话,可以到它的官网逛一逛。
    Demo的起始例子可以从这里下载,为了照顾新人,在这边进行简单说明。在Xcode中打开下载的例子,应该可以看到如下内容(Xcode和iOS开发熟练者请跳过此段)
    AppDelegate.h/m 这是整个app的delegate,没什么特殊的,每个iOS/Mac程序在main函数以后的入口,由此进入app的生命周期。在这里加载了最初的viewController并将其放到Window中展示出来。另外appDelegate还负责处理程序开始退出等系统委托的事件
    MainViewController.h/m/xib 这个demo最主要的ViewController,含有一个TableView和一个搜索条。 SoundEffect.h/m 简单的播放声音的类,在MusicBrainz搜索完毕时播放一个音效。 main.m 程序入口,所有c程序都从main函数开始执行
    AFHTTPRequestOperation.h/m 这是有名的网络框架AFNetworking的一部分,用来帮助等简单地处理web服务请求。这里只包含了这一个类而没有将全部的AFNetworking包括进来,因为我们只用了这一个类。完整的框架代码可以在github的相关页面上找
    SVProgresHUD.h/m/bundle 是一个常用的进度条指示,当搜索的时候出现以提示用户正在搜索请稍后。bundle是资源包,里面包含了几张该类用到的图片,打进bundle包的目的一方面是为了资源容易管理,另一方面也是主要方面时为了不和其他资源发生冲突(Xcode中资源名字是资源的唯一标识,同名字的资源只能出现一次,而放到bundle包里可以避免这个潜在的问题)。SVProgresHUD可以在这里找到
    快速过一遍这个应用吧:MainViewController是UIViewController的子类,对应的xib文件定义了对应的UITableView和UISearchBar。TableView中显示searchResult数组中的内容。当用户搜索时,用AFHTTPRequestOperation发一个HTTP请求,当从MusicBrainz得到回应后将结果放入searchResult数组中并用tableView显示,当返回结果是空时在tableView中显示没找到。主要的逻辑都在MainViewController.m中的-searchBarSearchButtonClicked:方法中,生成了用于查询的URL,根据MusicBrainz的需求替换了请求的header,并且完成了返回逻辑,然后在主线程中刷新UI。整个程序还是比较简单的~
    MRC到ARC的自动转换
    回到正题,我们讨论的是ARC,关于REST API和XML解析的技术细节就暂时先忽略吧..整个程序都是用MRC来进行内存管理的,首先来让我们把这个demo转成ARC吧。基本上转换为ARC意味着把所有的retain,release和autorelease关键字去掉,在之前我们明确几件事情:
    * Xcode提供了一个ARC自动转换工具,可以帮助你将源码转为ARC
    * 当然你也可以自己动手完成ARC转换
    * 同时你也可以指定对于某些你不想转换的代码禁用ARC,这对于很多庞大复杂的还没有转至ARC的第三方库帮助很大,因为不是你写的代码你想动手修改的话代码超级容易mess…
    对于我们的demo,为了说明问题,这三种策略我们都将采用,注意这仅仅只是为了展示如何转换。实际操作中不需要这么麻烦,而且今后的绝大部分情况应该是从工程建立开始就是ARC的。

    首先,ARC是LLVM3.0编译器的特性,而老的工程特别是Xcode3时代的工程的默认编译器很可能是GCC或者LLVM-GCC,因此第一步就是确认编译器是否正确。在Project设置面板,选择target,在Build Settings中将Compiler for C/C++/Objective-C选为Apple LLVM compiler 3.0或以上。为了确保之后转换的顺利,在这里我个人建议最好把Treat Warnings as Errors和 Run Static Analyzer都打开,确保在改变编译器后代码依旧没有警告或者内存问题(虽然静态分析可能不太能保证这一点,但是聊胜于无)。好了~clean(Shift+Cmd+K)以后Bulid一下试试看,经过修改后的demo工程没有任何警告和错误,这是很好的开始。(对于存在警告的代码,这里是很好的修复的时机..请在转换前确保原来的代码没有内存问题)。

    接下来就是完成从MRC到ARC的伟大转换了。还是在Build Settings页面,把Objective-C Automatic Reference Counting改成YES(如果找不到的话请看一看搜索栏前面的小标签是不是调成All了..这个选项在Basic里是不出现的),这样我们的工程就将在所有源代码中启用ARC了。然后…试着编译一下看看,嗯..无数的错误。

    这是很正常的,因为ARC里不允许出现retain,release之类的,而MRC的代码这些是肯定会有的东西。我们可以手动一个一个对应地去修复这些错误,但是这很麻烦。Xcode为我们提供了一个自动转换工具,可以帮助重写源代码,简单来说就是去掉多余的语句并且重写一些property关键字。

    这个小工具是Edit->Refactor下的Convert to Objective-C ARC,点击后会让我们选择要转换哪几个文件,在这里为了说明除了自动转换外的方法,我们不全部转换,而只是选取其中几个转换(MainViewController.m和AFHTTPRequestOperation.m不做转换)。注意到这个对话框上有个警告标志告诉我们target已经是ARC了,这是由于之前我们在Build Settings里已经设置了启用ARC,其实直接在这里做转换后Xcode会自动帮我们开启ARC。点击检查后,Xcode告诉我们一个不幸的消息,不能转换,需要修复ARC readiness issues..后面还告诉我们要看到所有的所谓的ARC readiness issues,可以到设置的General里把Continue building after errors勾上…What the f**k…好吧~先乖乖听从Xcode的建议”Cmd+,“然后Continue building after errors打勾然后再build。

    问题依旧,不过在issue面板里应该可以看到所有出问题的代码了。在我们的例子里,问题出在SoundEffect.m里:
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
    if (fileURL != nil)
    {
    SystemSoundID theSoundID;
    OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)fileURL, &theSoundID);
    if (error == kAudioServicesNoError)
    soundID = theSoundID;
    }

    这里代码尝试把一个NSURL指针强制转换为一个CFURLRef指针。这里涉及到一些Core Services特别是Core Foundation(CF)的东西,AudioServicesCreateSystemSoundID()函数接受CFURLRef为参数,这是一个CF的概念,但是我们在较高的抽象层级上所建立的是NSURL对象。在Cocoa框架中,有很多顶层对象对底层的抽象,而在使用中我们往往可以不加区别地对这两种对象进行同样的对待,这类对象即为可以”自由桥接”的对象(toll-free bridged)。NSURL和CFURLRef就是一对好基友好例子,在这里其实CFURLRef和NSURL是可以进行替换的。
    通常来说为了代码在底层级上的正确,在iOS开发中对基于C的API的调用所传入的参数一般都是CF对象,而Objective-C的API调用都是传入NSObject对象。因此在采用自由桥接来调用C API的时候就需要进行转换。但是在使用ARC编译的时候,因为内存管理的原因,编译器需要知道对这些桥接对象要实行什么样的操作。如果一个NSURL对象替代了CFURLRef,那么在作用区域外,应该由谁来决定内存释放和对象销毁呢?为了解决这个问题,引入了bridge,bridge_transfer和__bridge_retained三个关键字。关于选取哪个关键字做转换,需要由实际的代码行为来决定。如果对于自由桥接机制感兴趣,大家可以自己找找的相关内容,比如适用类型、内部机制和一个简介~之后我也会对这个问题做进一步说明
    回到demo,我们现在在上面的代码中加上__bridge进行转换。然后再运行ARC转换工具,这时候检查应该没有其他问题了,那么让我们进行转换吧~当然在真正转换之前会有一个预览界面,在这里我们最好检查一下转换是不是都按照预想进行了..要是出现大面积错误又没有备份或者出现各种意外的话就可以哭了…
    前后变化的话比较简单,基本就是去掉不需要的代码和改变property的类型而已,其实有信心的话不太需要每次都看,但是如果是第一次执行ARC转换的操作的话,我还是建议稍微看一下变化,这样能对ARC有个直观上的了解。检查一遍,应该没什么问题了..需要注意的是main.m里关于autoreleasepool的变化以及所有dealloc调用里的[super dealloc]的删除,它们同样是MRC到ARC的主要变化..
    好了~转换完成以后我们再build看看..应该会有一些警告。对于原来retain的property,比较保险的做法是转为strong,在LLVM3.0中自动转换是这样做的,但是在3.1中property默认并不是strong,这样在使用property赋值时存在警告,我们在property声明里加上strong就好了~然后就是SVProgressHUD.m里可能存在问题,这是由于原作者把release的代码和其他代码写在一行了.导致自动转换时只删掉了部分,而留下了部分不应该存在的代码,删掉对变量的空调用就好了..
    自动转换之后的故事
    然后再编译,没有任何错误和警告了,好棒~等等…我们刚才没有对MainViewController和AFHTTPRequestOperation进行处理吧,那么这两个文件里应该还存在release之类的东西吧..?看一看这两个文件,果然有各种release,但是为什么能编译通过呢?!明明刚才在自动转换前他们还有N多错的嘛…答案很简单,在自动转换的时候因为我们没有勾选这两个文件,因此编译器在自动转换过后为这两个文件标记了”不使用ARC编译”。可以看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m两个文件后面被加上了-fno-objc-arc的编译标记,被加上该标记的文件将不使用ARC规则进行编译。

    提供这样的编译标记的原因是显而易见的,因为总是有一部分的第三方代码并没有转换为ARC(可能是由于维护者犯懒或者已经终止维护),所以对于这部分代码,为了迅速完成转换,最好是使用-fno-objc-arc标记来禁止在这些源码上使用ARC。
    为了方便查找,再此列出一些在转换时可能出现的问题,当然在我们使用ARC时也需要注意避免代码中出现这些问题:
    “Cast … requires a bridged cast”
    这是我们在demo中遇到的问题,不再赘述
    Receiver type ‘X’ for instance message is a forward declaration
    这往往是引用的问题。ARC要求完整的前向引用,也就是说在MRC时代可能只需要在.h中申明@class就可以,但是在ARC中如果调用某个子类中未覆盖的父类中的方法的话,必须对父类.h引用,否则无法编译。
    Switch case is in protected scope
    现在switch语句必须加上{}了,ARC需要知道局部变量的作用域,加上{}后switch语法更加严格,否则遇到没有break的分支的话内存管理会出现问题。
    A name is referenced outside the NSAutoreleasePool scope that it was declared in
    这是由于写了自己的autoreleasepool,而在转换时在原来的pool中申明的变量在新的@autoreleasepool中作用域将被局限。解决方法是把变量申明拿到pool的申请之前。
    ARC forbids Objective-C objects in structs or unions
    可以说ARC所引入的最严格的*是不能在C结构体中放OC对象了..因此类似下面这样的代码是不可用的
    typedef struct {
    UIImage *selectedImage;
    UIImage *disabledImage;
    } ButtonImages;

    这个问题只有乖乖想办法了..改变原来的结构什么的..

    手动转换
    刚才做了对demo的大部分转换,还剩下了MainViewController和AFHTTPRequestOperation是MRC。但是由于使用了-fno-objc-arc,因此现在编译和运行都没有问题了。下面我们看看如何手动把MainViewController转为ARC,这也有助于进一步理解ARC的规则。
    首先,我们需要转变一下观念…对于MainViewController.h,在.h中申明了两个实例变量:
    @interface MainViewController : UIViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    }

    我们不妨仔细考虑一下,为什么在interface里出现了实例变量的申明?通常来说,实例变量只是在类的实例中被使用,而你所写的类的使用者并没有太多必要了解你的类中有哪些实例变量。而对于绝大部分的实例变量,应该都是protected或者private的,对它们的操作只应该用setter和getter,而这正是property所要做的工作。可以说,将实例变量写在头文件中是一种遗留的陋习。更好的写实例变量名字的地方应当与类实现关系更为密切,为了隐藏细节,我们应该考虑将它们写在@implementation里。好消息是,在LLVM3.0中,不论是否开启ARC,编译器是支持将实例变量写到实现文件中的。甚至如果没有特殊需要又用了property,我们都不应该写无意义的实例变量申明,因为在@synthesize中进行绑定时,我们就可以设置变量名字了,这样写的话可以让代码更加简洁。
    在这里我们对着两个实例变量不需要property(外部成员不应当能访问到它们),因此我们把申明移到.m里中。修改后的.h是这样的,十分简洁一看就懂~
    #import
    @interface MainViewController : UIViewController
    @property (nonatomic, retain) IBOutlet UITableView *tableView;
    @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
    @end

    然后.m的开头变成这样:
    @implementation MainViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    }

    这样的写法让代码相当灵活,而且不得不承认.m确实是这些实例变量的应该在的地方…build一下,没问题..当然对于SoundEffect类也可以做相似的操作,这会让使用你的类的人很开心,因为.h越简单越好..P.S.另外一个好处可以减少.h里的引用,减少编译时间(虽然不明显=。=)
    然后就可以在MainViewController里启用ARC了,方法很简单,删掉Build Phases里相关文件的-fno-objc-arc标记就可以了~然后..然后当然是一大堆错误啦。我们来手动一个个改吧,虽然谈不上乐趣,但是成功以后也会很有成就~(如果你不幸在启用ARC后build还是成功了,恭喜你遇到了Xcode的bug,请Cmd+Q然后重新打开Xcode把=_=)
    dealloc
    红色最密集的地方是dealloc,因为每一行都是release。由于在这里dealloc并没有做除了release和super dealloc之外的任何事情,因此简单地把整个方法删掉就好了。当然,在对象被销毁时,dealloc还是会被调用的,因此我们在需要对非ARC管理的内存进行管理和必要的逻辑操作的时候,还是应该保留dealloc的,当然这涉及到CF以及以下层的东西:比如对于retain的CF对象要CFRelease(),对于malloc()到堆上的东西要free()掉,对于添加的observer可以在这里remove,schele的timer在这里invalidate等等~[super dealloc]这个消息也不再需要发了,ARC会自动帮你搞定。
    另外,在MRC时代一个常做的事情是在dealloc里把指向自己的delegate设成nil(否则就等着EXC_BAD_ACCESS吧 ),而现在一般delegate都是weak的,因此在self被销毁后这个指针自动被置成nil了,你不用再为之担心,好棒啊..
    去掉各种release和autorelease
    这个很直接,没有任何问题。去掉就行了~不再多说
    讨论一下Property
    在MainViewController.m里的类扩展中定义了两个property:
    @interface MainViewController ()
    @property (nonatomic, retain) NSMutableArray *searchResults;
    @property (nonatomic, retain) SoundEffect *soundEffect;
    @end

    申明的类型是retain,关于retain,assign和copy的讨论已经烂大街了,在此不再讨论。在MRC的年代使用property可以帮助我们使用dot notation的时候简化对象的retain和copy,而在ARC时代,这就显得比较多余了。在我看来,使用property和点方法来调用setter和getter是不必要的。property只在将需要的数据在.h中暴露给其他类时才需要,而在本类中,只需要用实例变量就可以。因此我们可以移去searchResults和soundEffect的@property和@synthesize,并将起移到实例变量申明中:
    @implementation MainViewController
    {
    NSOperationQueue *queue;
    NSMutableString *currentStringValue;
    NSMutableArray *searchResults;
    SoundEffect *soundEffect;
    }

    相应地,我们需要将对应的self.searchResult和self.soundEffect的self.都去去掉。在这里需要注意的是,虽然我们去掉了soundEffect的property和synthesize,但是我们依然有一个lazy loading的方法- (SoundEffect *)soundEffect,神奇之处在于(可能你以前也不知道),点方法并不需要@property关键字的支持,虽然大部分时间是这么用的..(property只是对setter或者getter的申明,而点方法是对其的调用,在这个例子的实现中我们事实上实现了-soundEffect这个getter方法,所以点方法在等号右边的getter调用是没有问题的)。为了避免误解,建议把self.soundEffect的getter调用改写成[self soundEffect]。

    iOS面试题有哪些?

    iOS面试题主要有:

    1、多线程、特别是NSOperation 和 GCD 的内部原理。

    2、运行时机制的原理和运用场景。

    3、SDWebImage的原理。实现机制。如何解决TableView卡的问题。

    4、block和代理的通知的区别。block的用法需要注意些什么。

    5、strong,weak,retain,assign,copy nomatic 等的区别。

    6、设计模式,mvc,单利,工厂,代理等的应用场景。

    7、单利的写法。在单利中创建数组应该注意些什么。

    8、NSString 的时候用copy和strong的区别。

    9、响应值链。

    10、NSTimer 在子线程中应该手动创建NSRunLoop ,否则不能循环执行。

    11、UIScrollView和NSTimer组合做循环广告图轮播的时候有一个属性可以控制当上下滚动tableview的时候广告轮播图依然正常滚动。

    12、Xcode最新的自动布局。

    13、git ,和svn的用法,git的几个命令。

    14、友盟报错可以查到具体某一行的错误,原理是什么。

    15、Instrument 可以检测 电池的耗电量、和内存的消耗。的用法。

    16、动画CABaseAnimation CAKeyAni…. CATrans….. CAGoup…. 。

    17、ARC的原理。

    ios开发 registerusernotificationsettings为什么会堵塞线程

    先来看看官方的文档,是这样写的:   In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.   翻译过来是:   在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,而不一定是在注册观察者的那个线程中。   也就是说,Notification的发送与接收处理都是在同一个线程中。为了说明这一点,我们先来看一个示例:   代码清单1:Notification的发送与处理   @implementation ViewController   - (void)viewDidLoad {   [super viewDidLoad];   NSLog(@"current thread = %@", [NSThread currentThread]);   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];   });   }   - (void)handleNotification:(NSNotification *)notification   {   NSLog(@"current thread = %@", [NSThread currentThread]);   NSLog(@"test notification");   }   @end   其输出结果如下:   2015-03-11 22:05:12.856 test[865:45102] current thread = {number = 1, name = main}   2015-03-11 22:05:12.857 test[865:45174] current thread = {number = 2, name = (null)}   2015-03-11 22:05:12.857 test[865:45174] test notification   可以看到,虽然我们在主线程中注册了通知的观察者,但在全局队列中post的Notification,并不是在主线程处理的。所以,这时候就需要注意,如果我们想在回调中处理与UI相关的操作,需要确保是在主线程中执行回调。   这时,就有一个问题了,如果我们的Notification是在二级线程中post的,如何能在主线程中对这个Notification进行处理呢?或者换个提法,如果我们希望一个Notification的post线程与转发线程不是同一个线程,应该怎么办呢?我们看看官方文档是怎么说的:   For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.   这里讲到了“重定向”,就是我们在Notification所在的默认线程中捕获这些分发的通知,然后将其重定向到指定的线程中。   一种重定向的实现思路是自定义一个通知队列(注意,不是NSNotificationQueue对象,而是一个数组),让这个队列去维护那些我们需要重定向的Notification。我们仍然是像平常一样去注册一个通知的观察者,当Notification来了时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,则将这个Notification存储到我们的队列中,并发送一个信号(signal)到期望的线程中,来告诉这个线程需要处理一个Notification。指定的线程在收到信号后,将Notification从队列中移除,并进行处理。   官方文档已经给出了示例代码,在此借用一下,以测试实际结果:   代码清单2:在不同线程中post和转发一个Notification   @interface ViewController ()   @property (nonatomic) NSMutableArray *notifications; // 通知队列   @property (nonatomic) NSThread *notificationThread; // 期望线程   @property (nonatomic) NSLock *notificationLock; // 用于对通知队列加锁的锁对象,避免线程冲突   @property (nonatomic) NSMachPort *notificationPort; // 用于向期望线程发送信号的通信端口   @end   @implementation ViewController   - (void)viewDidLoad {   [super viewDidLoad];   NSLog(@"current thread = %@", [NSThread currentThread]);   // 初始化   self.notifications = [[NSMutableArray alloc] init];   self.notificationLock = [[NSLock alloc] init];   self.notificationThread = [NSThread currentThread];   self.notificationPort = [[NSMachPort alloc] init];   self.notificationPort.delegate = self;   // 往当前线程的run loop添加端口源   // 当Mach消息到达而接收线程的run loop没有运行时,则内核会保存这条消息,直到下一次进入run loop   [[NSRunLoop currentRunLoop] addPort:self.notificationPort   forMode:(__bridge NSString *)kCFRunLoopCommonModes];   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil];   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];   });   }   - (void)handleMachMessage:(void *)msg {   [self.notificationLock lock];   while ([self.notifications count]) {   NSNotification *notification = [self.notifications objectAtIndex:0];   [self.notifications removeObjectAtIndex:0];   [self.notificationLock unlock];   [self processNotification:notification];   [self.notificationLock lock];   };   [self.notificationLock unlock];   }   - (void)processNotification:(NSNotification *)notification {   if ([NSThread currentThread] != _notificationThread) {   // Forward the notification to the correct thread.   [self.notificationLock lock];   [self.notifications addObject:notification];   [self.notificationLock unlock];   [self.notificationPort sendBeforeDate:[NSDate date]   components:nil   from:nil   reserved:0];   }   else {   // Process the notification here;   NSLog(@"current thread = %@", [NSThread currentThread]);   NSLog(@"process notification");   }   }   @end   运行后,其输出如下:   2015-03-11 23:38:31.637 test[1474:92483] current thread = {number = 1, name = main}   2015-03-11 23:38:31.663 test[1474:92483] current thread = {number = 1, name = main}   2015-03-11 23:38:31.663 test[1474:92483] process notification   可以看到,我们在全局dispatch队列中抛出的Notification,如愿地在主线程中接收到了。   这种实现方式的具体解析及其局限性大家可以参考官方文档Delivering Notifications To Particular Threads,在此不多做解释。当然,更好的方法可能是我们自己去子类化一个NSNotificationCenter,或者单独写一个类来处理这种转发。   NSNotificationCenter的线程安全性   苹果之所以采取通知中心在同一个线程中post和转发同一消息这一策略,应该是出于线程安全的角度来考量的。官方文档告诉我们,NSNotificationCenter是一个线程安全类,我们可以在多线程环境下使用同一个NSNotificationCenter对象而不需要加锁。原文在Threading Programming Guide中,具体如下:   The following classes and functions are generally considered to be thread-safe. You can use the same instance from multiple threads without first acquiring a lock.   NSArray   ...   NSNotification   NSNotificationCenter   我们可以在任何线程中添加/删除通知的观察者,也可以在任何线程中post一个通知。   NSNotificationCenter在线程安全性方面已经做了不少工作了,那是否意味着我们可以高枕无忧了呢?再回过头来看看第一个例子,我们稍微改造一下,一点一点来:   代码清单3:NSNotificationCenter的通用模式   @interface Observer : NSObject   @end   @implementation Observer   - (instancetype)init   {   self = [super init];   if (self)   {   _poster = [[Poster alloc] init];   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]   }   return self;   }   - (void)handleNotification:(NSNotification *)notification   {   NSLog(@"handle notification ");   }   - (void)dealloc   {   [[NSNotificationCenter defaultCenter] removeObserver:self];   }   @end   // 其它地方   [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];   上面的代码就是我们通常所做的事情:添加一个通知监听者,定义一个回调,并在所属对象释放时移除监听者;然后在程序的某个地方post一个通知。简单明了,如果这一切都是发生在一个线程里面,或者至少dealloc方法是在-postNotificationName:的线程中运行的(注意:NSNotification的post和转发是同步的),那么都OK,没有线程安全问题。但如果dealloc方法和-postNotificationName:方法不在同一个线程中运行时,会出现什么问题呢?   我们再改造一下上面的代码:   代码清单4:NSNotificationCenter引发的线程安全问题   #pragma mark - Poster   @interface Poster : NSObject   @end   @implementation Poster   - (instancetype)init   {   self = [super init];   if (self)   {   [self performSelectorInBackground:@selector(postNotification) withObject:nil];   }   return self;   }   - (void)postNotification   {   [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];   }   @end   #pragma mark - Observer   @interface Observer : NSObject   {   Poster *_poster;   }   @property (nonatomic, assign) NSInteger i;   @end   @implementation Observer   - (instancetype)init   {   self = [super init];   if (self)   {   _poster = [[Poster alloc] init];   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];   }   return self;   }   - (void)handleNotification:(NSNotification *)notification   {   NSLog(@"handle notification begin");   sleep(1);   NSLog(@"handle notification end");   self.i = 10;   }   - (void)dealloc   {   [[NSNotificationCenter defaultCenter] removeObserver:self];   NSLog(@"Observer dealloc");   }   @end   #pragma mark - ViewController   @implementation ViewController   - (void)viewDidLoad {   [super viewDidLoad];   __autoreleasing Observer *observer = [[Observer alloc] init];   }   @end   这段代码是在主线程添加了一个TEST_NOTIFICATION通知的监听者,并在主线程中将其移除,而我们的NSNotification是在后台线程中post的。在通知处理函数中,我们让回调所在的线程睡眠1秒钟,然后再去设置属性i值。这时会发生什么呢?我们先来看看输出结果:   2015-03-14 00:31:41.286 SKTest[932:88791] handle notification begin   2015-03-14 00:31:41.291 SKTest[932:88713] Observer dealloc   2015-03-14 00:31:42.361 SKTest[932:88791] handle notification end   (lldb)   // 程序在self.i = 10处抛出了"Thread 6: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)"   经典的内存错误,程序崩溃了。其实从输出结果中,我们就可以看到到底是发生了什么事。我们简要描述一下:   当我们注册一个观察者是,通知中心会持有观察者的一个弱引用,来确保观察者是可用的。   主线程调用dealloc操作会让Observer对象的引用计数减为0,这时对象会被释放掉。   后台线程发送一个通知,如果此时Observer还未被释放,则会向其转发消息,并执行回调方法。而如果在回调执行的过程中对象被释放了,就会出现上面的问题。   当然,上面这个例子是故意而为之,但不排除在实际编码中会遇到类似的问题。虽然NSNotificationCenter是线程安全的,但并不意味着我们在使用时就可以保证线程安全的,如果稍不注意,还是会出现线程问题。   那我们该怎么做呢?这里有一些好的建议:   尽量在一个线程中处理通知相关的操作,大部分情况下,这样做都能确保通知的正常工作。不过,我们无法确定到底会在哪个线程中调用dealloc方法,所以这一点还是比较困难。   注册监听都时,使用基于block的API。这样我们在block还要继续调用self的属性或方法,就可以通过weak-strong的方式来处理。具体大家可以改造下上面的代码试试是什么效果。   使用带有安全生命周期的对象,这一点对象单例对象来说再合适不过了,在应用的整个生命周期都不会被释放。   使用代理。