1、以下代码有什么问题吗?如果没有问题的话,obj、obj2的引用计数分别是多少?如果有问题的话存在什么问题?
Class *obj = [[Class alloc]init];
Class *obj2 = obj;
[obj hello];
[obj release];
[obj2 hello];
[obj2 release];
答:上面的代码是存在问题的。当执行完第一行代码时,因为执行了alloc,obj的引用计数为1。第二行代码执行完之后,obj2只是和obj指向了同一块内存。第三行代码是执行了hello方法。第四行代码执行release消息之后,obj的引用计数减一,这时retainCount变为0.系统自动调用dealloc方法,对象被销毁。第五行代码执行时,obj2执行的内存已经被系统回收了,但还是调用了hello方法,出现了问题(野指针)。第六行执行时,obj2所指向的内存已经不存在,再次调用release消息,出现过度释放的问题,而且obj2已经变成野指针了。
Class *obj = [[Class alloc]init];//obj引用计数加1
Class *obj2 = obj;//obj,obj2指向同一块内存(对象)
[obj hello];
[obj release];//obj指向的内存(对象)被销毁
[obj2 hello];//错误,obj2指向的内存(对象)已经被销毁了
[obj2 release];//[obj release]之后,obj2是个野指针,不应该再去调用方法.
2、在实际开发的过程中,什么情况下需要创建自动释放池?下面代码中有没有什么问题?
Person *p1=[[Person alloc]init];
@autoreleasepool {
[p1 autorelease];
@autoreleasepool {
[p1 autorelease];
}
}
答:其实自动释放池存在的意义是为了延迟释放一些对象,延迟向对象发送release消息。在实际的开发中呢,有两种情况是需要手动创建自动释放池的。第一个就是在多线程中,因为子线程中可能会使用便利构造器等方法来创建对象,那么这些对象的释放只能放在自动释放池中,主线程其实已经添加过自动释放池,在main函数里面。第二个就是如果一段代码里面(比如for循环)大量使用便利构造器创建对象,也需要手动添加自动释放池。
上述代码其实是存在问题的。当执行完第一行代码时,p1的引用计数是1.第二行是创建了一个autoreleasepool。第三行代码向p1发送了autorelease消息,延迟release,即在出池的时候,把p1释放掉。第四行代码又创建了一个autoreleasepool。第五行代码再次向p1发送了autorelease消息。当代码执行到第六行的“}”时,第二个自动释放池结束,这时p1引用技术减1,p1所指向的内存(对象)的retainCount由1变为0,内存被系统回收。代码执行到第七行时,最外层的自动释放池结束,再次向p1发送release消息,出现过度释放。
@autoreleasepool {
[p1 autorelease];//此时p1被加入自动释放池1
@autoreleasepool {
[p1 autorelease];//此时p1被加入自动释放池2
}//此处,自动释放池2结束,p1引用计数-1
}//此处,自动释放池1结束,但是向已经被释放的对象p1发送了消息
3、ARC下dealloc方法存在的意义在于什么地方?举例说明一下具体的使用场景。
答:dealloc 在ARC下的作用就是释放系统无法释放的该对象占用的资源,或者其他对象对该对象的引用(释放代理)
其实在MRC中dealloc方法存在的主要意义是为了释放自身的实例变量,移除观察者,停止timer,移除通知,代理置空等。ARC下,系统会帮助我们释放该对象所包含的实例变量,但是有些对象还是需要们自己去释放的(比如Core Foundation框架下的一些对象),另外通知中观察者的移除,代理置空,停止timer等
示例如下所示:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];//移除通知观察者
[[XMPPManager sharedManager] removeFromDelegateQueue:self];//移除委托引用
[[MyClass shareInstance] doSomething ]//其他操作
scrollView.delegate = nil;
[timer invalidate];
}
4、分别写出MRC中在assign、retain、copy下属性name对应的setter方法的内部实现。
答:
- assign下
- (void) setName:(NSString*)name
{
_name = name;
}
- retain下
if(_name != name){
[_name release];
_name = [name retain];
}
}
- copy下
if(_name != name){
[_name release];
_name = [name copy];
}
}
5、在Category中本身不允许为已有的类添加新的属性或者成员变量,你有没有其他的方法可以在category中添加属性或者是成员变量?
答:一种方法就是使用runtime.h中的objc_getAssociatedObject和objc_setAssociatedObject来访问和生成关联对象。例如为NSObject添加一个类目,分类中添加一个属性。代码如下所示:
- NSObject+Test.h文件
\#import <Foundation/Foundation.h>
@interface NSObject (Test)
@property (nonatomic, strong) NSString *test;
@end
- NSObject+Test.m文件
#import “NSObject+Test.h"
#import <objc/runtime.h>
static const void *instanceNameKey = &instanceNameKey;
@implementation NSObject (Test)
@dynamic test;
-(NSString *)test
{
return objc_getAssociatedObject(self, instanceNameKey);
}
-(void)setTest:(NSString *)test
{
objc_setAssociatedObject(self, instanceNameKey, test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
6、@synthesize和@dynamic有什么区别?
- (1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
- (2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
- (3)@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = instance.var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。@dynamic可用于在分类中添加属性(需要用到objc_getAssociatedObject和objc_setAssociatedObject函数)。
7、你如何看待iOS中的拷贝?
答:在我看来,日常生活中,当我们用到”拷贝”这个词语的时候,不管怎样都会产生两份。一份是原来的,一份是新拷贝出来的。但是到目前为止,在iOS中我看到了三种拷贝方式:
- (1)伪拷贝:伪拷贝,顾名思义,就是假拷贝,没有拷贝出新的对象。这一点对于NSString这种类簇来说比较常见,NSString本身是不可变字符串,内容不可能被修改,因此我们也没有拷贝的必要,因为拷贝的目的是防止原件被修改,所以才拷贝出来一份,伪拷贝实际上是对象引用计数加了1(相当于retain或者strong的功效)。
- (2)浅拷贝:浅拷贝就是确实拷贝出来一份和原来内容一样的新对象。但是对于对象自带的属性是伪拷贝,两个对象的属性指向同一个内存。
- (3)深拷贝:深拷贝就是不仅仅拷贝出一份新对象,而且对象的属性也拷贝了一份。
总的来说,如果在开发的过程中需要实现拷贝,那么需要接受NSCopying协议。实现copyWithZone:方法。浅拷贝、深拷贝的区别在于copyWithZone:方法的实现不同。
8、父类实现深拷贝时,子类如何实现深拷贝 ?父类没有实现深拷贝时,子类如何实现深度拷贝?
答:父类实现深拷贝之后,子类在重写的copyWithZone方法,先调用父类的copyWithZone方法,之后实现自己属性的拷贝。
如果父类没有实现深拷贝,子类除了需要对自己的属性进行拷贝,还要对父类的属性进行拷贝。
9、系统中有哪些单例类?在真实的开发中,单例的应用场景在于什么地方?
答:系统为我们提供了很多单例类。例如:UIScreen、UIDevice、NSFileManager、NSNotificationCenter,NSUserDefaults,UIApplication等等。而且在实际的开发中除了使用系统给我们提供的单例类之外,还会根据需求自己创建单例类。例如:1.数据库处理。对于数据的增删改查,可能在很多界面都会用到,那这个时候,可以定义一个针对数据处理的单例类。2.音乐播放。在做音乐播放类项目时,一般会在多个页面都可以播放音乐,这时也可以讲播放器定义为单例类,在多个界面使用。3.下载管理,当下载音乐或者视频时,应该定义一个单例类管理下载任务。4.登录类程序里面,还可以定义单例类保存用户资料,状态等。
10、写出在多线程情况下的一个单例。
答:一般情况下,在开发中我们所写的单例都是伪单例。即只是保证了在调用某一方法时,所产生的对象只有一个,但是没有考虑其他的影响其引用计数的方法。例如retain、copy等。为了保证线程安全,单例的写法如下所示:
方法一:
static AccountManager *sharedAccountManagerInstance = nil;
+ (AccountManager *)sharedManager{
@synchronized (self){
if (sharedAccountManagerInstance == nil) {
sharedAccountManagerInstance = [[AccountManager alloc] init];
}
}
return sharedAccountManagerInstance;
}```
方法二:
static AccountManager *sharedAccountManagerInstance = nil;
- (AccountManager *)sharedManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAccountManagerInstance = [[AccountManager alloc] init];
});
return sharedAccountManagerInstance;
}
#####11、写一个标准的宏,这个宏输入两个参数并且返回较小的一个。
>答:#define Min(X,Y) ((X) < (Y) ?(X):(Y))
#####12、应用程序在启动的时候主要做了什么操作?
>答:应用程序在启动的时候,会执行main函数,而main函数里面主要执行了UIApplicationMain函数。UIApplicationMain函数执行完主要做了以下三个操作:
> * (1)创建应用程序UIApplication对象。
> * (2)创建了应用程序代理对象。默认的应用程序代理对象是AppDelegate。
> * (3)建立一个事件循环RunLoop。用来实时监测应用程序中的各种事件(触摸,晃动,远程控制事件,通知,观察者,timer等等)。
#####13、简述应用程序按Home键进入后台时的生命周期,以及从后台回到前台时的生命周期。
>答:正在运行的程序,点击了Home键之后,应用程序会由活跃状态变为非活跃状态,之后应用程序进入后台。在这个过程中执行的方法分别是applicationWillResignActive:和applicationDidEnterBackground:。
当应用程序从后台回到前台时,应用程序会先进入前端,然后由非活跃状态变为活跃状态。在这个过程中执行的方法分别是:
applicationWillEnterForeground:和applicationDidBecomeActive:
#####14、ViewController 的 alloc,loadView,viewDidLoad,dealloc在自定义ViewController中这几个函数里面应该做什么工作?
>答: alloc方法一般情况下会直接调用,主要是为了开辟内存空间,创建对象。
重写loadView方法的主要目的是使用自己创建的view(一般会定义一个UIView子类,用子类创建对象,子类中已经添加好对应的控件了)作为ViewController的View,而不是直接使用系统自带的View。loadView方法和我们在storyboard或XIB中拖控件是等价的。
viewDidLoad方法一般情况下会给添加好的控件赋值(比如页面传值时,列表页面会把model传递给detail页面,在detail页面的viewDidLoad中使用model中的数据给控件赋值),开启timer,网络请求等。
dealloc方法在执行的时候,主要是释放控制器的实例变量或者是将delegate置空、移除观察者,停止timer等。
#####15、请描述一下viewController几个重要方法的执行时机
>答:视图控制器的生命周期分为创建、 显示 和 销毁。
######(1)创建
xxx *x = [[xxx alloc] init] // 作用:分配内存空间,创建控制器时使用
######(2)加载和显示:
* -(void)loadView: // 作用:加载根视图(即为self.view赋值) ,在控制器view属性的getter方法首次调用的时候执行此方法。
根视图已加载:
* -(void)viewDidLoad// loadView执行完毕之后,立即执行此方法。作用:给创建好的控件设置值,做网络请求,开启timer等
loadView 和 viewDidLoad 当首次访问controller的根视图时才会执行,即view属性的getter方法首次使用时执行, loadView 执行在前, viewDidLoad执行在后。如果重写了 loadView 一定要给控制器的根视图赋值(self.view = xxView;),在给控制器的根视图赋值的之前,不能使用控制器view属性的getter方法,否则会重复执行 loadView 和 viewDidLoad 方法。视图将要显示:
* -(void)viewWillAppear:
视图将要被添加到 UI 层级上,还没有添加时执行。(此时视图还没有显示)视图已经显示:
* -(void)viewDidAppear
视图已经被添加到 UI 层级上,视图已经显示出来时执行。(视图已经显示)
视图将要消失:
- (void)viewWillDisappear:
视图将要被从 UI 层级上面移除,但视图还没有移除时执行。 (视图还可见)
视图已经消失:
- (void)viewDidDisappear:
视图已经从 UI 层级上面移除,视图已经不可见时执行。 (视图不可见)
######(3)销毁:
- (void)viewWillUnload
iOS6.0之后已经被弃用。iOS 5.0 之前,当低内存且控制器的view不需要使用的时候会调用这个方法,即当控制器的根视图 将要 被释放时执行([vc.view release],vc.view还未置空),我们可以在这个方法中移除一些跟视图相关的观察者和通知,并记录视图的状态,以便之后重新创建视图。 iOS6.0 之后不再需要做释放了,该方法也被遗弃了。
- (void)viewDidUnload
iOS6.0 之后已经被弃用。iOS 5.0 之前,当低内存且控制器的view不需要使用的时候会调用这个方法,即当控制器的根视图被释放时执行([vc.view release],vc.view已经为nil),这个方法给我们一个机会做内存的相关清理工作,如果控制器对某些视图有引用,可以在这里释放这些引用,同样可以释放一些懒加载的对象,但是不要释放那些不容易重新加载的数据。 iOS6.0 之后不再需要做释放了,该方法也被遗弃了。
#####16.在使用系统的一些类例如UITableView时,会发现其delegate属性的语义设置为assign而不是retain,为什么呢?
>答:``delegate`` 的语义修饰符 为``assign`` 不是 ``retain`` 主要是为了避免两个对象因相互引用造成的内存泄露问题。
因为 `` retain``修饰``delegate``,当通过set方法赋值时,对象的引用计数会+1.
如果此时两个对象具有如下关系:A对象有一个属性是B,B的``delegate``是A。即互相为彼此的属性。例如: A 是``viewController`` ,B是``tableView``. B 是A的属性,B的引用计数会+1,B的``delegate``是A,A 的引用计数也会 +1. 那么如果A没有释放,B就一直不会释放,因为A对B有拥有权。 同理,B没有释放,A也一直释放不掉,因为B对A也有拥有权。 因此A 和 B 的``dealloc`` 都会不执行,造成内存泄露.
代码演示如下(只写出了核心代码):
A *a = [[A alloc] init] // a 引用计数:1
B *b = [[B alloc] init] // b 引用计数:1
a.b = b; // b 引用计数: 2
b.delegate = a; // a 引用计数: 2
[a release]; // a引用计数: 1
[b release]; // b引用计数: 1
// 最终 A 和 B 都释放不掉。
assign则是直接赋值,并不会引起引用计数增加,因此使用assign 不会出现内存泄露。
代码演示如下(只写出了核心代码):
A *a = [[A alloc] init] // a 引用计数:1
B *b = [[B alloc] init] // b 引用计数:1
a.b = b; // b 引用计数: 2
b.delegate = a; // a 引用计数: 1
[a release]; // a引用计数: 0, b引用计数:1(在a的dealloc里面_b被释放了)
[b release]; // b引用计数: 0
// 最终 A 和 B 都可以释放掉。
######17.UIImage初始化一张图片的方法以及优缺点
>答:
> * ``
UIImage *image = [UIImage imageNamed:@”1.png”];
``
这个方法创建的图片是从缓存⾥面获取的, 先在缓存⾥面查,看是不是有这个图⽚, 没有的话将图⽚添加进缓存再使用. 有的话直接使用缓存⾥面的. 在程序中,如果这张图⽚要在多个地方使用的话, 建议使用这种⽅式. 缺点是一旦加入缓存,就一直占用内存,不能被释放掉。
> * 读取本地图⽚路径
NSString *imagePath=[NSString stringWithFormat:@"%@/Documents/
%@.jpg",NSHomeDirectory(),@"test"];
[UIImage imageWithContentsOfFile:imagePath];
从⼿机本地读取, 比较第一种方式, 这个是直接加载图片的,图片不需要的时候,可以release掉. 所以建议在使用重复率低的图片时使用这个方法.
> * 以下这种方式会卡线程,建议代码放到子线程里面。
imageWithData: data可以通过data创建图片(data可以来自网络也可
以来自本地)
NSURL *url = [NSURL URLWithString:@“http://e.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=f51d2708cb11728b342d8c27f8c7b2f3/bba1cd11728b47109d0f5555c5cec3fdfc032302.jpg?referer=f55e30e2d71373f0ac285aaf8d00&x=.jpg"];
UIImage *image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
需要注意的是,如果imageWithData是同步网络请求,如果在主线程中直接使用,会卡主线程,因此一般不会在主线程中直接使用,而是采用异步网络请求获取data赋值。
######18.当手指点击屏幕上的登录按钮时,响应者链的检测过程是什么样子的?
>答:当手指触摸到屏幕上的登录按钮时,首先``runloop``会检测到事件,将事件传递给``UIApplication``对象,``UIApplication``对象将事件传递给``AppDelegate``对象,之后``AppDelegate``对象将事件传递给``window``对象,然后传递给``rootViewController``,之后是``view``,之后检测``View``上的子视图,通过比对触摸的位置定位到对应的``Button``。
#####19.在一个imageView上添加Button,给Button添加一个事件,点击Button时能否响应事件?如果不能,用响应者链解释一下为什么不可以?
>答:不能。因为imageView的用户交互默认是关闭的。imageView的用户交互关闭,阻断了响应者链的检测过程,所以在检测的过程中,就直接不检测imageView以及它上面的其他控件,也就检测不到button的存在。因为响应的过程正好是和检测的过程相反,因为检测不到button被触摸,所以button的点击事件也不会响应。解决办法就是把imageView的用户交互打开。
#####20.列举一下常用的第三方框架
>答:我在做开发的过程中曾经用过以下第三方框架:
(1)AFNetworking 网络请求库
(2)SDWebImage 网络图片加载
(3)FMDB 数据库
(4)Masonry 自动布局
(5)UmengSDK/Share SDK 分享
(6)融云/环信 即时通信
(7)ZBar/ZXing 二维码扫描和生成
(8)MBProgressHUD 风火轮
(9)GTMBase64 base64编码解码
(10)MJRefresh 下拉刷新,上拉加载
(11)百度/高德地图SDK
#####21.MJRefresh 原理
>答:给``UISCrollView``添加了分类,在分类中,根据``scrollView``的``contentOffset``进行判断方向和偏移量,当偏移量到某一个临界值的时候,开始切换``header``或者``footer``的状态,以呈现不同的``UI``,当偏移量大于临界值并松开手的时候, 执行``block``回调或者``target..action``的方法, 主要在``block``或者``target..action``中进行网络请求,请求结束后,调用``endRefresh``方法,再次更改``header``或者``footer``的状态,使他们回到初始状态。
#####22.block在定义成属性时应该用什么关键字,为什么?
>答:block在定义成属性时关键字应该使用copy。我们平时使用的block,主要是存放在栈区的(有的是在全局区),栈区的block出了作用域就会被释放,当我们进行block回调的时候,block已经被系统给销毁了,会出现crash。为了管理和使用block,需要将其拷贝到堆区。全局区的block理论上是不用拷贝的,但拷贝也没什么坏处,不用的时候,我们也会对copy的对象进行释放,不会造成内存泄露。因为block在栈区的情况比较多,为了书写方便,保持格统一,我们把block定义成属性时,使用copy关键字。
#####23.定义一个返回值为字符串类型,参数是int类型的Block。并且调用该Block。
>答:Block定义如下所示:
NSString * (^block)(int a) = ^ NSString * (int a){
return [NSString stringWithFormat:“%d”,a];
};
Block调用:
NSString *str = block(10);
#####24. 请谈谈你对block和delegate模式认识?
>答:无论是block还是delegate模式本质上都是回调,使用block,其优点是回调的block代码块直接就放在了block赋值的地方,使代码更为紧凑,缺点是block内使用到当前类的实例变量的时候,需要注意循环引用的问题,即需要使用__block(MRC下)或者__weak(ARC下)定义一个弱引用的self出来,block里面使用弱引用的self去操作属性或调用方法。delegate模式不用像block一样做特殊处理,但是如果多个对象设置的代理是同一个对象,就需要在delegate方法中判断当前执行代理的是哪个对象。
#####25.什么是沙盒?
>答:所谓的沙盒其实是操作系统为应用程序分配的一个密闭文件夹。应用程序拥有这个文件夹内文件的访问权限,且只能对这个文件夹内的文件进行操作(当然也可以访问系统提供的文件,比如:相册),不可以去访问其他应用程序的文件夹。
#####26.在沙盒中有几个文件夹?
>答:沙盒含有3个文件夹:Documents, Library 和 tmp
> * Documents:苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录。
> * Library:存储程序的默认设置或其它状态信息。
iTunes在与iPhone同步时,备份所有的Documents和Library文件。
Library/Caches:存放缓存文件,一般是下载的图片和视频,iTunes不会备份此目录,此目录下文件不会在应用退出删除。
> * tmp:临时文件夹,应用程序在重启时,会丢弃所有的tmp文件。
#####27.你熟悉的设计模式有哪些,请说说对他们的理解。
>答:单例,通知,KVO,代理,target-action等
> * 单例,主要特点是一个类只有一个对象。对外提供了一个获取唯一对象的方法,一般都是类方法,完整的单例会重写很多引用计数相关的方法(比如:allocWithZone,copyWithZone,retain,release,autorelease,retainCount等)以保证对象在任何情况下都唯一。单例最大的价值就是建立程序级别的全局变量,就是把不同模块都要用的变量以属性的形式放到单例里面,以便随时使用。音频播放类程序一般会写单例来管理需要播放的音乐信息,需要下载的地方也会以单例的形式来管理下载对象。
> * 通知,是M与C通信的方式之一。一般是Model发送变化的时候,会发送通知告诉Controller,Controller再做相应的处理。需要先往通知中心里面注册观察者,然后在合适的时机,通知中心post通知,观察者做对应的处理,当观察者将要释放的时候,从通知中心移除观察者。
> * KVO,也是M与C通讯的方式。一般是C去观察M的某个属性,某个属性发生变化之后,C做出相应的处理,当C将要释放的时候,M移除观察者。
> * 代理,是V与C通信的方式之一。一般C是V的代理,V发生变化的时候,C做对应的调整。例如:UITableView被点击了,由Controller做处理。我们在自己写代理的时候,一定要清楚谁来处理事件,谁来调用代理方法。通常情况下都是C处理时间,V在适当的时候调用代理方法。
> * target-action,也是V与C通信的方式之一。一般C是V的target,V被点击或者被滑动之后,C做出对应的处理。无论是target-action还是代理,核心都是回调。
#####28.NSNotification和KVO的区别和用法是什么?什么时候应该使用通知,什么时候应该使用KVO,它们的实现上有什么区别?
>答:
> * 通知和KVO都是观察者模式的体现,二者侧重点有些不同。
通知往往用于1对多的场景,多个观察者观察一个通知,一旦这个通知被通知中心post,所有观察者都可以做出响应。具体的实现流程是:(1)通知中心通过addObserver方法添加观察者,其实是把观察者和通知进行绑定,多次使用addObserver方法可以为同一个通知添加多个观察者。
(2)通知中心发送通知。
(3)各个观察者做各自的处理。
(4)在观察者销毁之前(dealloc中),从通知中心移除观察者。
> * KVO多用于1对1的场景,一个对象去观察另外一个对象的属性值有没有发生变化,一旦发生变化,观察者做出响应。具体的流程是:
(1)被观察者添加观察者,指定观察者观察的属性。
(2)被观察者的属性在某一时间发生了变化。
(3)观察者做出响应。
(4)在观察者销毁之前,先移除观察者。
KVO其实也可以1对多,就是多个观察者观察同一个对象同一个属性的变化。KVO和通知给人的感觉一个主动通知(通知会由通知中心主动post),一个是被动通知(KVO,观察者一直盯着属性变没变,一旦变化,自己就做出响应。被观察的对象不是主动告知你我变了)。
#####29.定义属性时,什么情况使用copy,assign,和retain?
>答:
> * assign用于简单数据类型,如NSInteger,double,bool。retain 和copy用于对象类型,二者又有区别,
> * copy是用于一个对象有可能被修改,但不想修改原件,所以拷贝一份出来(新拷贝的对象引用计数为1),这样新拷贝的修改了,原件不会变,原件修改了,新拷贝的不会变。
> * 而retain呢,对象引用计数+1,对象只有一个,但引用计数为2,通过任何一个引用修改对象内容,另外一个引用使用的时候,用的就是修改之后的内容。
#####30.在项目中什么时候选择使用GCD,什么时候选择使用NSOperation?
>答:无论是GCD还是NSOperation其实都是多线程的一种实现形式。严格的说NSOperation和线程并没有必然联系,更不是多线程,NSOperation只是操作,封装了target和action 或者Block,在主线程中执行Operation,Operation就会运行在主线程中,在子线程中执行Operation,Operation就运行在子线程中。它只有和NSOperationQueue联合使用的时候,才能发挥出价值。NSOperationQueue是Operation的管理者,它首先是一个队列,先来的任务先开始执行,后来的任务后开始执行,这些任务的执行是并发的,NSOperationQueue负责为任务开辟线程以及关闭线程,有点类似于cell重用,用有限的线程执行任意数量的任务,同时可以设置最大并发数,控制程序的性能,不至于全部任务一起执行。GCD出现比NSOperationQueue要晚,是一套基于C函数的API,由于是C函数,所以性能比较高,使用灵活,安全,当然功能也强大。但是相对于GCD来讲,NSOperationQueue对线程做了封装,使用比较简单,尤其是不用管理线程的开启和关闭。个人认为,如果仅仅是想要实现异步执行任务,首选GCD,如果要管理多个异步任务,NSoperationQueue比较方便。如果要做更复杂的处理,比如前5个任务并发,5个任务执行完之后,需要串行执行3个任务,之后再并发执行若干任务,就需要使用GCD了。总的来说,特别简单的和非常复杂的多线程任务GCD比较适合,介于二者之间的用NSOperationQueue比较合适。
#####31.tableView必须实现的两个方法是什么(手写)?在哪个协议中声明的?
>答:在UITableViewDataSource协议中声明的两个必须实现的方法
1.`-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section`
2.`-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath`
#####32.UITableViewController 中,创建UITableViewCell时,initWithSytle:resuseIdentifier: 中,reuseIdentifier有什么用?简述UITableViewCell的复用原理.
>答:只有在cell被滑动出界面的时候,此cell才会被加入到复用队列中。每次在创建cell的时候,程序会首先通过调用`dequeueReusableCellWithIdentifier:cellType`方法,到复用队列中去寻找标示符为“cellType”的cell,如果找不到,返回nil,然后程序去通过调用`[[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:cellType] autorelease]`来创建标示符为“cellType”的cell。
#####33.#include、#import、@class这三个关键字是什么指令?
>答:是预编译指令。所谓的预编译指令指的就是在编译之前,先去执行的指令。
#####34.在oc中引入头文件使用的关键字是哪一个?能在c语言文件中使用吗?
>答: #import关键字可以在OC中使用,不能在C文件中使用, #include可以在C和OC中使用。
#####35.#import与#include相比,好处是什么?
>答: #import确定一个文件只能被导入一次,避免的重复导入的问题,使用#include一定要注意重复导入的问题。所以在OC中都使用#import来引用头文件。
#####36.#import<>和#import””的区别是什么?
>答:#import<>用于对系统文件的引用,编译器会在系统文件目录中去查找文件
\#import””用于对自定义的文件的引用,编译器首先回去用户目录下查找,然后去安装目录,最后去系统目录中查找文件。
#####37.@class的作用是什么?
>答:@class的作用是告诉编译器有@class后面的内容是一个类名。只是告诉编译器存在这么一个类,类具体包含哪些方法,属性和变量的并没有告诉编译器。一般在类的头文件中使用@class来引入其他类。
#####38.多线程的优点和缺点分别是什么?
>答:
> * 优点:
1、将耗时较长的操作(网络请求、图片下载、音频下载、数据库访问等)放在子线程中执行,可以防止主线程的卡死;
2、可以发挥多核处理的优势,提升cpu的使用率。
> * 缺点:
1、每开辟一个子线程就消耗一定的资源;
2、会造成代码的可读性变差;
3、如果出现多个线程同时访问一个资源,会出现资源争夺的情况
#####39.NSOperationQueue中有一个属性叫maxConcurrentCount即最大并发数。这里所谓的最大并发数指的是什么含义?
>答:这里的最大并发数指得是在队列中同时执行的任务的个数。有很多人会认为是所分配的线程的个数。其实不是的。因为线程的个数的多少取决于系统,系统会分配合适的线程数量来保证这些任务并发执行的,作为程序员我们是没办法控制的。
#####40.请写出下面代码的打印结果:
NSLog(@"1”);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3”);
>答:只能打印1.第一行代码按照顺序会打印1.当执行到第二段代码的时候出现了问题。因为在主线程中使用了同步,这时为了执行block内部的代码需要阻塞当前线程去执行Block,但是由于block也是在主线程里面执行,主线程当前还有任务没执行完,产生主线程死锁现象。代码没办法继续执行。
#####41.项目中,在什么情况下会用到多线程?
>答:多线程处理包括Core Data的多线程访问,耗时的数据计算,数据库访问,异步网络请求以及一些在运行态内存吃紧的情况下处理大文件的方案等。
#####42.iOS中哪些数据持久化的方式,各有什么特点,iOS平台怎么做数据的持久化,CoreData和sqlite有无必然联系?CoreData是一个关系型数据库吗?
>答:iOS中可以有四种持久化数据的方式: 属性列表、对象归档、SQLite3和Core Data;Core Data可以使你以图形界面的方式快速的定义app的数据模型,同时在你的代码中容易获取到它。core data提供了基础结构去处理常用的功能,例如保存,恢复,撤销和重做,允许你在app中继续创建新的任务。在使用Core Data的时候,你不用安装额外的数据库系统,因为core data使用内置的sqlite数据库。Core Data将你app的模型层放入到一组定义在内存中的数据对象中。Core Data会追踪这些对象的改变,同时可以根据需要做相反的改变,例如用户执行撤销命令。当Core Data在对你app数据的改变进行保存的时候,Core Data会把这些数据归档,并永久性保存。
mac os X中sqlite库,它是一个轻量级功能强大的关系数据引擎,也很容易嵌入到应用程序。可以在多个平台使用,sqlite是一个轻量级的嵌入式sql数据库编程。与core data框架不同的是,sqlite是使用程序式的,sql的主要的API来直接操作数据表。
Core Data不是一个关系型数据库,也不是关系型数据库管理系统(RDBMS)。虽然Core Dta支持SQLite作为一种存储类型,但它不能使用任意的SQLite数据库。Core Data在使用的过程中自己创建这个数据库。Core Data支持对一、对多的关系。
#####43.id声明的对象有什么特性?
>答:id是任意对象类型的,不能表示基本类型。id类型是通用指针类型,因为通过指针,也就是内存地址来引用对象,所以可以将任意对象赋值给id类型的对象。返回id类型值的方法是返回指向内存中某对象的指针。然后可以将该值赋给任何对象变量(强制类型转换即可)。因为无论在哪里,对象总是携带它的isa成员。所以即使将它存储在id类型的通用对象变量中,也总是可以确定它的真实类型,id是多态的一种体现。
#####44.对于语句NSString* testObject = [[NSData alloc]init];testObject在编译时和运行时分别是什么类型的对象?
>答:编译时是NSString,运行时是NSData。
#####45.什么是懒加载?在使用懒加载时的注意事项是什么?
>答:
(1).所谓的懒加载指的是延迟创建对象,只有当需要的时候才创建对象。
(2).在真正开发的过程中其实懒加载就是重写getter方法。在getter方法的内部,实现对象的创建,如果对象为nil才创建,如果不为nil,直接返回对象。在真正使用懒加载时需要注意的是当第一次使用对象时,需要调用self.因为只有这样才能调用对应的getter方法,对象才会被创建。
#####46.谈谈你对RunLoop的理解。
>答:一个程序从main函数开始,函数执行完毕之后就会退出,iOS程序也是一样的,但是我们从没看到过iOS程序打开之后直接闪退,肯定是有一些东西阻止了程序的退出,最简单的就是添加一个死循环, ``RunLoop``就是类似于这样的一个死循环,保证你的应用程序不被退出,区别就是``RunLoop``会在你的程序有事件(点击事件、摇晃事件等)要处理的时候才会去让``cpu``处理,在程序没有事件处理的时候就让系统``cpu``休眠。在iOS中,每个线程都有一个``RunLoop``,但是默认状态下只有主线程的``RunLoop``是开启的(系统自动帮我们开启),其他线程开启需要以下代码
BOOL isRunning = NO;
while (!isRunning) {
isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
``currentRunLoop ``可以获取当前线程的``RunLoop`` , 循环是为了保证能开启``RunLoop``(系统繁忙时有可能开启失败)
如果用``timerWithTimeInterval``来创建``NSTimer``,我们需要把这个``Timer``假如到``RunLoop``才能执行,如果是在子线程,还需要开启这个``RunLoop``。
#####47.简单描述一下``RunLoop``在实际开发中的应用场景。
>答:在做项目时曾遇到过两个问题,需要手动开启runLoop:
1.在一个页面中有一个轮播图和``tableView``。在滑动tableView的过程中,轮播图不动了。这时我是用了``RunLoop``将轮播图的NSTimer加入到了``RunLoop``中。
2.在子线程中开启了另外一个子线程用于下载图片。这时发现下载图片的代码不执行。通过在下载图片的代码里手动开启``RunLoop``,代码才可以执行。
#####48.什么是Runtime
>答:``Runtime``就是运行时,一个程序开发的过程通常可以分为以下阶段,编辑-预编译-编译-连接-运行,运行时可以说就是我们的程序再运行的阶段发生的一些事情,在这个阶段程序通常会把一些``OC``的代码转化成C语言的代码,从而提高执行的效率,在这个阶段我们也可以动态的为某个对象的属性赋值,而对象的属性具体是什么类型也会在这个阶段进行确定(``NSString *str = [NSData data]``; 其中str在编译的时候是NSString类型,运行的时候是NSData类型)。系统也提供了``Runtime``的类库,让我们可以直接调用一些运行时把OC代码转化C之后的代码比如:``objc_msgSend()``;同样也可以通过运行时,为分类添加属性,需要用到``objc_getAssociatedObject``和``objc_setAssociatedObject``函数。
#####49.解释这句代码的意思objc_msgSend(array,@selector(insertObject:atIndex:), foo, 5);
>答:给array 发送一个insertObject:atIndex: 的消息参数为 foo 和5, 就是给array再下标为5的地方插入一个foo对象
#####50.OC的优缺点:
>答:
> * 优点:
1、Category。可以很方便的为一个已有的类添加属性或者方法,而不需要笨拙的去继承它。
2、posing。可以让一个类的对象动态的以其他类行为去执行,也许可以理解成动态replace所有的method(消息转发机制,比如是用Person类的一个对象去调用一个Student类的方法)
3、 动态识别。 比较常见的动态语言的特性,涉及的点就多了,举个简单的例子,判断一个对象是否是某个类的成员。isKindOfClass:
4、弹性讯息传递 。方法(method)的动态处理,譬如当你调用一个没有的方法的时候,系统将是再运行时跑出异常而不是编译时给出错误。5、 不是一个过度复杂的 C 衍生语言
6、 Objective-C 与 C++ 可混合编程
> * 缺点:
1、不支持命名空间 (写过c#等其他语言的应该比较清楚,可以通过命名空间将相同名字的类进行分类,而objc中不得不通过前缀进行区分,这也是为什么苹果的类库都有UIXXX NSXXX等统一前缀了)
2、不支持运算符重载
3、不支持多重继承
4、使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到。(如内联函数等),性能低劣。
#####51.写一个冒泡排序
>答:
int numbers[5] = {4, 14, 88, 22, 60};
int count = sizeof(numbers) / sizeof(numbers[0]);
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - 1 - i; j++) {
if (numbers[j] > numbers[j + 1]) {
int temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
}
}
}
#####52.用OC写一个冒泡排序
>答:
NSMutableArray *numbers = [@[@42, @75, @22, @14, @1, @55] mutableCopy];
for (int i = 0; i < numbers.count - 1; i++) {
for (int j = 0; j < numbers.count - 1 - i; j++) {
if ([numbers[j] integerValue] > [numbers[j + 1] integerValue]) {
[numbers exchangeObjectAtIndex:j withObjectAtIndex:j + 1];
}
}
}
#####53.如何优化冒泡排序
>答:添加一个BOOL来标识当前的数组是否有序,在外层循环条件增加判断,无序(NO)的情况下再排序,每次进入循环假设是有序的(YES),无序的时候(进入if条件)把这个标识设置为无序(NO),如果没有进入if条件就说明当前的数组已经是有序的,则下次循环的时候会根据添加的条件自动停止循环
int flag = 0;
int numbers[5] = {4, 14, 88, 22, 60};
int count = sizeof(numbers) / sizeof(numbers[0]);
for (int i = 0; i < count - 1 && flag == 0; i++) {
flag = 1;
for (int j = 0; j < count - 1 - i; j++) {
if (numbers[j] > numbers[j + 1]) {
int temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
flag = 0;
}
}
}
#####54.写一个冒泡排序的函数
>```
void sort(int array[], int count){
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
int main(int argc, const char * argv[]) {
int flag = 0;
int numbers[5] = {4, 14, 88, 22, 60};
//如果函数实现在main函数后面需要先声明sort函数
int count = sizeof(numbers) / sizeof(numbers[0]);
sort(numbers, count);
for (int i = 0; i < count; i++) {
printf("%d\n", numbers[i]);
}
return 0;
}
55.简述你对UIView、UIWindow、CALayer的理解。
答:CALayer是图层类,本身可以显示的,但是不能响应事件。
UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。事件的处理由它来执行,但是显示其实是由其对应的layer层来操作的,UIView内嵌了一个layer,layer显示内容,UIView本身增加了事件处理的功能。
UIWindow继承自UIView,主要的作用是作为窗口呈现其他的视图。而且一个应用程序一般情况下只有一个窗口。
56.在iOS中如何实现国际化?
答:iOS中国际化需要做相关的配置:
(1)选中应用程序对应的project,然后添加所需要国际化的语言。
(2)新建Localizable.strings文件,作为多语言对应的词典,存储多种语言,点击右侧Localization,勾选国际化对应的语言。
(3)添加一个字段,设置你想要国际化的字段
在English中,添加:SUBMIT_BTN_TITLE = Go;
在Chinese中,添加:SUBMIT_BTN_TITLE = 开始;
57.简单描述你一下在开发的过程中,如何实现程序的性能优化?
答:我在开发的过程中会注意一下几点来优化程序性能:
1.避免庞大的XIB
2.使用懒加载的方式延迟加载界面
3.避免反复处理数据
4.避免使用NSDateFormatter和NSCalendar。
5.图片缓存的取舍
UIImage加载图片方式一般有两种:
A:imagedNamed初始化
B:imageWithContentsOfFile初始化
二者不同之处在于,imageNamed默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.如果缓存中没有找到相应的图片对象,则从指定地方加载图片然后缓存对象,并返回这个图片对象.
而imageWithContentsOfFile则仅只加载图片,不缓存.
大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间来做这件事.当应用程序需要加载一张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfFile是最为经济的方式,这样不会因为UIImage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间.使用场景需要编程时,应该根据实际应用场景加以区分,UIImage虽小,但使用元素较多问题会有所凸显.
58.XMPP的优点和缺点在于什么地方?
答:XMPP是一种即时通讯协议,基于XML的点对点的即时通讯协议。它的优点有:
1.开放:XMPP本身是开放的,所以在客户端、数据库等方面都有很多具体的实现和应用。
2.安全:
3.可扩展:XML命名空间的威力可使任何人在核心协议的基础上建造定制化的功能
缺点:
只能传输文本,不能传输音频,视频和图片。如果要传输音频,视频和图片,需要通过http协议传到服务器,服务器返回一个url,这个url再通过XMPP传递给对方,对方拿到之后,再通过url去下载和显示。
59.如何让你的应用程序更加省电?
答:(1)如果程序用到定位,需要在定位完毕之后关闭定位,或者降低定位的频率,不停的定位会消耗电量。(2)如果用到了蓝牙,需要使用蓝牙时候开启蓝牙,蓝牙用完之后关闭蓝牙,蓝牙也很耗电。(3)优化算法,减少循环次数,大量循环会让CPU一直处于忙碌状态,特别费电。(4)不要使用网络轮询,使用推送。(5)timer的时间间隔不宜太短,满足需求即可。(5)不要频繁刷新页面,能刷新1行cell,不要reloadData。(6)切勿让屏幕长亮。(7)线程适量,不宜过多。
60.如何实现程序后台运行?
答:苹果是不赞成程序在后台运行的(因为很费电),除非后台运行能提升用户体验。有3种情况允许程序后台运行。(1)在前台开启了一个短任务,可以在程序进入后台的时候,向系统申请点时间把任务执行完。(2)前台有下载任务,可以在进入后台的时候继续下载。(3)一些特定类型的任务可以在后台运行,比如:定位,音乐播放,VoIP等等。
对于第一种情况,可以调用UIApplication类的beginBackgroundTaskWithName:expirationHandler:或者beginBackgroundTaskWithExpirationHandler:方法申请一点额外的时间去执行未完成的任务。调用这两个方法中的一个都会临时延迟应用程序进入休眠的时间,以便有时间把任务执行完。当任务执行完毕之后,应用程序需要调用endBackgroundTask:方法让系统知道任务执行完了,程序可以进入休眠状态了。不一定非得等应用程序要进入后台了才设置后台需要执行的任务。一个好的设计应该是在任务开始前就是用beginBackgroundTaskWithName:expirationHandler:或者beginBackgroundTaskWithName:expirationHandler:添加后台执行的任务,任务一旦执行完就调用endBackgroundTask:方法告诉后台,需要进入后台执行的任务执行完了(此时可能还没进入后台)。
对于第二种情况,如果要下载文件,应用程序应该使用NSURLSession对象去开启下载任务,这样系统就可以在程序休眠的时候控制下载,当我们配置NSURLSession需要在后台传输的时候,系统会单独开一个进程去处理下载任务,如果在下载的时候,进入了后台,会在后台继续下载。想要实现后台下载,我们需要配置NSURLSession。首先创建一个NSURLSessionConfiguration对象,设置一些属性,然后把NSURLSessionConfiguration对象设置给NSURLSession对象。具体步骤:(1)使用backgroundSessionConfigurationWithIdentifier:创建NSURLSessionConfiguration对象。(2)设置NSURLSessionConfiguration对象的sessionSendsLaunchEvents 属性为YES。(3)如果在前台已经开始下载了,还需要设置NSURLSessionConfiguration的discretionary属性为YES。(4)为NSURLSessionConfiguration对象设置其他你需要的属性。(5)把NSURLSessionConfiguration对象设置给NSURLSession对象。
第三种情况属于Long-Running任务。如果需要实现长时任务,必须去申请,而且只有特定的类型才能长时间在后台执行。比如:后台播放音频,后台录音,后台定位和导航,VoIP,定期下载和处理新内容,定期接收外设的数据。需要在Info.plist添加UIBackgroundModes字段选择需要后台运行的事件类型,或者在工程的Capabilities里面打开Background Modes,勾选对应的事件,告诉系统会在后台运行什么类型的Long-Running任务,然后再根据不同类型的事件做相应的处理即可。
61.什么是arc?(arc是为了解决什么问题诞生的?)
首先解释ARC: automatic reference counting自动引用计数。
ARC几个要点:
在对象被创建时 retain count +1,在对象被release时 retain count -1.当retain count 为0 时,销毁对象。
程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。
那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。
MRC下内存管理的缺点:
1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
4.多线程操作时,不确定哪个线程最后使用完毕
/MRC
//1.block声明为属性的时候要使用copy语义(要将从栈区拷贝到堆区,retain无效,即retain和assign会造成野指针问题,因为栈内存系统自动回收,指向的内容不可控)
//2.block释放的时候要使用专有的Block_release(<#...#>)(block区别于向内存静态申请空间(+alloc)的OC普通对象,不符合常规内存管理)
//3.在block实现部分,必须使用__block来修饰实例变量、self。(block会持有其内部变量,导致引用计数+1)
//ARC
//1.block声明为属性的时候要使用strong语义,strong会自动将block拷贝一份到堆区
//2.在block实现部分,必须使用__weak来修饰实例变量、self。
怎么理解MVC,在Cocoa中MVC是怎么实现的?
MVC设计模式考虑三种对象:模型对象、视图对象、和控制器对象。模型对象代表特别的知识和专业技能,它们负责保有应用程序的数据和定义操作数据的逻辑。视图对象知道如何显示应用程序的模型数据,而且可能允许用户对其进行编辑。控制器对象是应用程序的视图对象和模型对象之间的协调者。
3.手写单例?
+(instancetype)shareSingleTon{
static SingleTon * singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (singleton == nil) {
singleton = [[singleton alloc]init];
}
});
return singleton;
}
#####62 什么是 KVC 和 KVO?
>答:KVC(Key-Value-Coding)内部的实现:一个对象在调用 setValue 的时候,(1)首先根据方法名找到运行方法的时候所需要的环 境参数。(2)他 会从自己 isa 指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具 体的方法实现。KVO(Key-Value- Observing):当观察者为一个对象的属性进行了注册, 被观察对象的 isa 指针被修改的时候,isa 指针就会指向一个中间类,而不是真实的类。所 以 isa 指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于 isa 指针。 在调用类的方法的时候,最好要明确对象实例的类名
#####63 概念
>面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术[1] 发展到一定阶段后的产物。
#####64.关键字
> * atomic:
原子操作(原子性是指事务的一个完整操作,操作成功就提交,反之就回滚. 原子操作就是指具有原子性的操作)在objective-c 属性设置里面 默认的就是atomic ,意思就是 setter /getter函数是一个原子操作,如果多线程同时调用setter时,不会出现某一个线程执行完setter所有语句之前,另一个线程就开始执行setter,相当于 函数头尾加了锁 . 这样的话 并发访问性能会比较低 .
> * nonatomic:
非原子操作 一般不需要多线程支持的时候就用它,这样在 并发访问的时候效率会比较高 . 在objective-c里面通常对象类型都应该声明为非原子性的. iOS中程序启动的时候系统只会自动生成一个单一的主线程.程序在执行的时候一般情况下是在同一个线程里面对一个属性进行操作. 如果在程序中 我们确定某一个属性会在多线程中被使用,并且需要做数据同步,就必须设置成原子性的,但也可以设置成非原子性的,然后自己在程序中用加锁之类的来做数据同步.
在头文件中声明属性的时候使用atomic 和 nonatomic等价于在头文件里面添加2个函数一个是用于设置这个属性的,一个是用于读取这个属性,例如:- (nsstring *)name; - (void)setName:(NSString *)str;
atomic / nonatomic 需要和@synthesize/@dynamic配和使用才有意义.
> * @synthesize
如果没有实现setter和getter方法,编译器将会自动在生产setter和getter方法。
> * @dynamic
表示变量对应的属性访问器方法 , 是动态实 现的 , 你需要在 NSObject 中继承而来的 +(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 动态实现的方法或者函数。
属性修饰其他关键字:
> * getter=getterName
指定 get 方法,并需要实现这个方法 。必须返回与声明类型相同的变量,没有参数
> * setter=setterName
指定 set 方法,并需要实现这个方法 。带一个与声明类型相同的参数,没有返回值(返回空值)
当声明为 readonly 的时候,不能指定 set 方法
> * readwrite
如果没有声明成 readonly ,那就 默认是 readwrite 。可以用来赋值,也可以被赋值
> * readonly
不可以被赋值
> * assign
所有属性都 默认 assign ,通常用于标量(简单变量 int , float , CGRect 等)
一种典型情况是用在对对象没有所有权的时候,通常是 delegate ,避免造成死循环(如果用 retain 的话会死循环)
> * retain
属性必须是 objc 对象,拥有对象所有权,必须在 dealloc 中 release 一次。
> * copy
属性必须是 objc 对象,拥有对象所有权,必须在 dealloc 中 release 一次。且属性必须实现 NSCopying 协议
一般常用于 NSString 类型
网友评论