还有第二篇在这里->iOS面试题 - 总会有你需要的(二)
基础
<a href="#overview">1. 为什么说Objective-C是一门动态的语言?</a>
<a href="#2">2. 讲一下MVC和MVVM,MVP?</a>
<a href="#3">3. 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?</a>
<a href="#4">4. 属性的实质是什么?包括哪几个部分?</a>
<a href="#5">5. 属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?</a>
<a href="#6">6. NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)</a>
<a href="#7">7. 如何令自己所写的对象具有拷贝功能?</a>
<a href="#8">8. 可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?</a>
<a href="#9">9. 为什么IBOutlet修饰的UIView也适用weak关键字?</a>
<a href="#10">10. nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?</a>
<a href="#11">11. UICollectionView自定义layout如何实现?</a>
<a href="#12">12. 用StoryBoard开发界面有什么弊端?如何避免?</a>
<a href="#13">13. 进程和线程的区别?同步异步的区别?并行和并发的区别?</a>
<a href="#14">14. 线程间通信?</a>
<a href="#15">15. GCD的一些常用的函数?(group,barrier,信号量,线程同步)</a>
<a href="#16">16. 如何使用队列来避免资源抢夺?</a>
<a href="#17">17. 数据持久化的几个方案(fmdb用没用过)</a>
<a href="#18">18. 说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?</a>
<a href="#19">19. NSCache优于NSDictionary的几点?</a>
<a href="#20">20. 知不知道Designated Initializer?使用它的时候有什么需要注意的问题?</a>
<a href="#21">21. 实现description方法能取到什么效果?</a>
<a href="#22">22. objc使用什么机制管理对象内存?</a>
中级
Block
<a href="#23">1. block的实质是什么?一共有几种block?都是什么情况下生成的?</a>
<a href="#24">2. 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?</a>
<a href="#25">3. 模拟一下循环引用的一个情况?block实现界面反向传值如何实现?</a>
Runtime
1. objc在向一个对象发送消息时,发生了什么
2. 什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
3. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
4. runtime如何实现weak变量的自动置nil?
5. 给类添加一个属性后,在类结构体里哪些元素会发生变化?
RunLoop
1. runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
2. runloop的mode是用来做什么的?有几种mode?
3. 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
4. 苹果是如何实现Autorelease Pool的?
类结构
1. isa指针?(对象的isa,类对象的isa,元类的isa都要说)
2. 类方法和实例方法有什么区别?
3. 介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
4. 运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
5. objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
高级
1. UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)
2. 有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)
3. 看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)
4. SDWebImage的缓存策略?
5. AFN为什么添加一条常驻线程?
6. KVO的使用?实现原理?(为什么要创建子类来实现)
7. KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
项目
1. 有已经上线的项目么?
2. 项目里哪个部分是你完成的?(找一个亮点问一下如何实现的)
3. 开发过程中遇到过什么困难,是如何解决的?
学习
1. 遇到一个问题完全不能理解的时候,是如何帮助自己理解的?举个例子?
2. 有看书的习惯么?最近看的一本是什么书?有什么心得?
3. 有没有使用一些笔记软件?会在多平台同步以及多渠道采集么?(如果没有,问一下是如何复习知识的)
4. 有没有使用清单类,日历类的软件?(如果没有,问一下是如何安排,计划任务的)
5. 平常看博客么?有没有自己写过?(如果写,有哪些收获?如果没有写,问一下不写的原因)
答案区
<h2 id="overview">1.为什么说Objective-C是一门动态的语言? </h2>
Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
动态特性基础
1、动态类型
即运行时再决定对象的类型。这类动态特性在日常应用中非常常见,简单说就是id类型。id类型即通用的对象类,任何对象都可以被id指针所指,而在实际使用中,往往使用introspection来确定该对象的实际所属类:
id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
someClass *classSpecifiedInstance = (someClass *)obj;
// Do Something to classSpecifiedInstance which now is an instance of someClass
//...
}
-isMemberOfClass:
是NSObject
的方法,用以确定某个NSObject
对象是否是某个类的成员。与之相似的为-isKindOfClass:
,可以用以确定某个对象是否是某个类或其子类的成员。这两个方法为典型的introspection方法。在确定对象为某类成员后,可以安全地进行强制转换,继续之后的工作。
2、动态绑定
基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。在继续之前,需要明确objective-c中消息的概念。由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法。
动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层,我们一般向一个NSObject对象发送-respondsToSelector:或者-instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。一个例子:
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
//该方法在OC消息转发生效前被调用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
//向[self class]中新加入返回为void的实现,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
return YES;
}
return [super resolveInstanceMethod:aSel];
}
当然也可以在任意需要的地方调用class_addMethod
或者method_setImplementation
(前者添加实现,后者替换实现),来完成动态绑定的需求。
3、动态加载
根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。
深入运行时特性
基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换(在Java里这个设计模式被叫做Strategy?不是很懂Java,求纠正)。而Objective-C还有一些高级或者说更底层的运行时特性,在一般的Cocoa开发中较为少见,基本被运用与编写OC和其他语言的接口上。但是如果有所了解并使用得当的话,在Cocoa开发中往往可以轻易解决一些棘手问题。
这类运行时特性大多由/usr/lib/libobjc.A.dylib
这个动态库提供,里面包括了对于类、实例成员、成员方法和消息发送的很多API,包括获取类实例变量列表,替换类中的方法,为类成员添加变量,动态改变方法实现等,十分强大。完整的API列表和手册可以在这里找到。虽然文档开头表明是对于Mac OS X Objective-C 2.0适用,但是由于这些是OC的底层方法,因此对于ios开发来说也是完全相同的。
一个简单的例子,比如在开发Universal应用或者游戏时,如果使用IB构建了大量的自定义的UI,那么在由iPhone版转向iPad版的过程中所面临的一个重要问题就是如何从不同的nib中加载界面。在iOS5之前,所有的UIViewController
在使用默认的界面加载时(init
或者initWithNibName:bundle:
),都会走-loadNibNamed:owner:options:
。而因为我们无法拿到loadNibNamed:owner:options
的实现,因此对其重载是比较困难而且存在风险的。因此在做iPad版本的nib时,一个简单的办法是将所有的nib的命名方式统一,然后使用自己实现的新的类似-loadNibNamed:owner:options
的方法将原方法替换掉,同时保证非iPad的设备还走原来的loadNibNamed:owner:options
方法。使用OC运行时特性可以较简单地完成这一任务。
代码如下,在程序运行时调用+swizze
,交换自己实现的loadPadNibNamed:owner:options
和系统的loadNibNamed:owner:options
,之后所有的loadNibNamed:owner:options
消息都将会发为loadPadNibNamed:owner:options
,由自己的代码进行处理。
+(BOOL)swizze { Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:));
if (!oldMethod){
return NO;
}
Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:));
if (!newMethod) {
return NO;
}
method_exchangeImplementations(oldMethod, newMethod);
return YES;
}
loadPadNibNamed:owner:options
的实现如下,注意在其中的loadPadNibNamed:owner:options
由于之前已经进行了交换,因此实际会发送为系统的loadNibNamed:owner:options
。以此完成了对不同资源的加载。
-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options {
NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""];
newName = [newName stringByAppendingFormat:@"@pad"];
//判断是否存在
NSFileManager *fm = [NSFileManager defaultManager];
NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”];
//这里调用的loadPadNibNamed:owner:options:实际为为交换后的方法,即loadNibNamed:owner:options:
if ([fm fileExistsAtPath:filepath]) {
return [self loadPadNibNamed:newName owner:owner options:options];
} else {
return [self loadPadNibNamed:name owner:owner options:options];
}
}
当然这只是一个简单的例子,而且这个功能也可以通过别的方法来实现。比如添加UIViewController的类别来重载init,但是这样的重载会比较危险,因为你UIViewController的实现你无法完全知道,因此可能会由于重载导致某些本来应有的init代码没有覆盖,从而出现不可预测的错误。当然在上面这个例子中重载VC的init的话没有什么问题(因为对于VC,init的方法会被自动转发为loadNibNamed:owner:options,因此init方法并没有做其他更复杂的事情,因此只要初始化VC时使用的都是init的话就没有问题)。但是并不能保证这样的重载对于其他类也是可行的。因此对于实现未知的系统方法,使用上面的运行时交换方法会是一个不错的选择~
<a id="2">2.讲一下MVC和MVVM,MVP</a>
MVC:Model-View-Controller
MVP:Model-View-Presenter
MVVM:Model-View-ViewModel
先说一下三者的共同点,也就是Model和View Model就是领域模型,数据对象,同时,提供外部对应用程序数据的操作的接口,也可能在数据变化时发出变更通知。Model不依赖于View的实现,只要外部程序调用Model的接口就能够实现对数据的增删改查。
View就是UI层,提供对最终用户的交互操作功能,包括UI展现代码及一些相关的界面逻辑代码。
三者的差异在于如何粘合View和Model,实现用户的交互操作以及变更通知
Controller接收View的操作事件,根据事件不同,或者调用Model的接口进行数据操作,或者进行View的跳转,从而也意味着一个Controller可以对应多个View。Controller对View的实现不太关心,只会被动地接收,Model的数据变更不通过Controller直接通知View,通常View采用观察者模式监听Model的变化。
Presenter,与Controller一样,接收View的命令,对Model进行操作;与Controller不同的是Presenter会反作用于View,Model的变更通知首先被Presenter获得,然后Presenter再去更新View。一个Presenter只对应于一个View。根据Presenter和View对逻辑代码分担的程度不同,这种模式又有两种情况:Passive View和Supervisor Controller。
ViewModel,注意这里的“Model”指的是View的Model,跟上面那个Model不是一回事。所谓View的Model就是包含View的一些数据属性和操作的这么一个东东,这种模式的关键技术就是数据绑定(data binding),View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上。这种模式实际上是框架替应用开发者做了一些工作,开发者只需要较少的代码就能实现比较复杂的交互。
MVP和MVVM完全隔离了Model和View,但是在有些情况下,数据从Model到ViewModel或者Presenter的拷贝开销很大,可能也会结合MVC的方式,Model直接通知View进行变更。在实际的应用中很有可能你已经在不知不觉中将几种模式融合在一起,但是为了代码的可扩展、可测试性,必须做到模块的解耦,不相关的代码不要放在一起。
个人理解,在广义地谈论MVC架构时,并非指本文中严格定义的MVC,而是指的MV*,也就是视图和模型的分离,只要一个框架提供了视图和模型分离的功能,我们就可以认为它是一个MVC框架。在开发深入之后,可以再体会用到的框架到底是MVC、MVP还是MVVM。
<a id="3">3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?</a>
weak:指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制
@property (nonatomic, weak) id<HSDogDelegate>delegate;
strong:该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain Cycles)
@property (nonatomic, strong) id<HSDogDelegate>delegate;
详细理解查看此篇文章iOS 代理为啥要用weak修饰? (刨根问底一)
delegate是代理
datasource是数据源
datasource协议里面东西是跟内容有关的,主要是cell的构造函数,各种属性
delegate协议里面的方法主要是操作相关的,移动编辑之类的
首先Delegate是委托的意思,在oc中则是一个类委托另一个类实现某个方法。当一个对象接受到某个事件或者通知的时候, 会向它的Delegate对象查询它是否能够响应这个事件或者通知,如果可以这个对象就会给它的Delegate对象发送一个消息(执行一个方法调用)。
Datasource字面是数据源,一般和Delegate伴生,这时数据源处理的数据就是Delegate中发送委托的类中的数据,并通过Datasource发送给接受委托的类。
Instead of being delegated control of the user interface, a data source is delegated control of data.官网上这句话解释的比较好,我们可以发现,delegate控制的是UI,是上层的东西;而datasource控制的是数据。他们本质都是回调,只是回调的对象不同。(官网原文:https://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html)
Block 和 Delegate 两种在 iOS 开发中都有广泛的应用,如果加上 NSNotification 的话就把三大 iOS 回调方式给凑齐了。我这里根据这三者来进行说明。
Delegation (代理)是一种很常见的回调方式,和 Singleton (一样),在大部分编程语言里都有实现方法。Block 是一种苹果开发的基于 C 的调用方式 ,在 iOS 和 Mac 开发上都有广泛的应用。
Block 从某种程度上来说是一种很新的回调方式,苹果在2009年将其引入 Mac OS X 10.6,之后在2010年将其引入到 iOS 4.0。值得一提的是,同样是在 iOS 4.0 ,苹果引入了 ARC (Automatic Reference Counting),准确的说的话是 ARCLite , iOS 5 才正式加入了 ARC 。
NSNotification 这个作为苹果在 2001 年发布 Mac OS X 的时候就集成在 Foundation 库里面的类,一直作为一种全局回调通知的功能而存在。有趣的是,按照苹果官方文档对于 Delegation 的说明,在 Cocoa Framwroks 中,苹果的 Delegation 被表述为是一种单观察者通知的实现。
三种回调方式,能共存于 Objective-C 之中,现在又集体被继承到了 Swift 里面,说明三者肯定是有不同和各自的优劣之处的。下面我就我个人这些年的 iOS 开发了解,简单说一下三者的优劣。
Delegation 是一种很清晰回调形式,从 Protocol 的建立,到之后的引用,和对于 delegate 声明的变量处理,都非常具有条理。建立完 Delegation 之后,其他方法在调用的时候会很清晰整个回调的流程和处理。在处理一些延迟回调或者触发回掉的时候,声明调用的类里面的回调方法在编写时可以按照很独立的逻辑在制作。在使用 Delegation 的时候,一个回调方法可以对应多个不同的 Delegation ,这不仅简化了编程过程,也让能回调处理更加清晰。Delegation 不好的地方在于,在类中调用 delegate 方法的时候,需要对 delegate 本体进行一定的验证核对,防止出现方法对象为空的情况,再一个就是受制于 performSelector 这个 selector 方法,回掉返回的参数被限制在了 NS 类的范围内,数量也很有限(当然可以用直接调用方法的形式在绕过,并不推荐;也可以用 Array 套着传, 不过这样需要有文档支持,不然不够清晰,回调方法也需要独立的验证,故也不推荐):
if (self.delegate && [self.delegate respondsToSelector:@selector(ABC:)]){
[self.delegate performSelector:@selector(ABC:) withObject:@"ABC"];
}
//需要验证代理是否为空,同时验证代理方法是否存在
这个问题在 Swfit 中有了很不错的解决:
delegate?.ABC?(@"ABC")
至于说 Delegation 的另外一个问题,那就是使用了 Delegation 之后,代码阅读变成了一件很困难的事情,你需要在不同的 Class 文件中一次次的跳转来理解整个代码思路,要知道糟糕的 XCode 还经常会跳错方法(Command + 点击跳转,跳到了同名的其他方法),导致代码可读性的下降。要说的话,这一点在 Swift(2.2)版本中有一定的优化,不过 Swift 的 Selector 还在修正中,之后也许会在对于 Delegation 的可读性上做更多的优化。
最后一个比较核心的问题就在于,在一批变量声明了代理之后,在代理回掉被执行时,你是不太好知道这个变量究竟是这一批中的哪一个的。这也就是为什么苹果在制作 delgate 和 datasource 的时候都会在代理方法的返回值中带上声明代理类本身的原因。
Block 是一种很好用的回掉方式,其解决了 Delegation 在确认声明对象上的问题,由于 Block 回调方法一般都跟随在声明之后,所以可以很快确认回调来源,通过 __block 声明的变量也可以很方便的穿越到回调方法中进行使用,代码可读性上,一般来说使用 Block 的代码比使用 Delegation 的代码可读性更强(当然你也可以声明 Block 回调方法对应变量,然后按照和 Delegation 差不多的方法来调用他)。
Block 的缺陷主要就在于使用 ARC 情况下的循环引用。从某种程度上来说, Block 回调的方法内变量实际上是关联在声明 Block 的类里面的,但 Block 回调方法本身是在使用 Block 的类中的,同时使用类的 self 本身是可以在 Block 回调方法中被请求的,这里就会出现使用类 A 引用声明类 B ,B 在关联的 Block 中又引用了使用类 A ,这样一个循环之后引用可以无限循环下去,在这种无限循环的引用下, ARC 就无法知道 A 和 B 的确切弃用时间,所以 A 和 B 会在内存中永远存活下去,直到进程被消灭。所以,在很多时候,我们必须要使用 __weak 来声明一个 weakself ,来帮助 ARC 判断内存回收的时间。
这里要指出的是, Block 的回调方法也是可以复用,创建回调方法这一套东西在 Objective-C 中是继承于 C 样式的,一般来说,这个东西是没人用。
void (^simpleBlock)(id responseObject) = ^(id responseObject){
NSLog(@"This is a block");
};
//这里创建了 simpleBlock 之后就可以像 Delegation 回调方法那样在各种回调声明中使用了
NSNotification 本身是一个非常强大的回调,因为他的回调是全局而且一对多的,广播的特性使得很多时候使用 NSNotification 能一次解决大面积的问题。同时,由于 NSNotification 是一套很古老的回调机制,在一些时候,当我们需要对 iOS Framework 做一些特殊处理的时候,会用到一些获取隐藏 NSNotification 的方法,这个在之前权限不完善的时候,企业开发中使用很广泛。
至于说 NSNotification,就和上面说到的一样,也主要是在于他的全局广播属性和权限机制。在使用 NSNotification 的时候,注册完回调方法之后,开发者需要自行对注册进行取消操作。比如说你注册一个 A 方法到 B 通知,由于某些原因 A 方法所在的类被 ARC 回收掉了,那么在 B 通知被触发的时候,由于 A 的通知注册信息还在,就会出现调用了非法的内存地址的情况。我曾经遇到了一次,应该是在调用 Jianting Zhu 还是 Jim Liu 的 WeiboSDK 的时候,记得那一版 SDK 特别奇葩,所有回调都是通过 NSNotification 来进行的,一开始用的时候不太懂,各种注册回调方法,然后发现 App 跑起来会莫名的闪退,那时候也不太懂,只能硬着头皮把代码都改成代理的形式。后来在做键盘的时候碰到了类似的报错,花时间研究了之后才发现是通知回调了已经被回收的类里面的方法,这时候才知道要如果通知没有注销会带来调用上的问题。至于说 NSNotification 的权限问题,对于写类的人来说是比较头疼的,很多时候只能靠文档和调用者的自觉,出于权限考虑,如果不是特别需求全局广播这个特性,一般不太建议使用 NSNotification。
NSNotification 由于自身的限制,在回调可以传递的内容上也存在数量和内容的限制,虽然可以通过 Array 的方法绕过,但这样在代码可读性就会有折扣,对于文档也需要有要求,回调方法中还需要验证。
那么,在何种情况下使用上面三者呢?对于初级的开发人员来说,关键在于使用习惯。如果你从其他语言转到 Objective-C 或者 Swift ,相信 Delegation 肯定让你觉得更加亲切,那么在初级阶段请使用好这个语法糖,多用,多去理解;如果你用着 AFNetworking 看着其他老前辈的说法用 Block 觉得效率很高很开心,那就开心的用,直到你被循环引用烦到了为止(笑);如果你用 NSNotification ……你还是别用了。然后,在你代码写多了之后,你可以开始尝试接触其他回调方式,去感受这些回调方式的不同。
对于中级的开发人员,关键在于对于回调流程的理解。你要知道你的回调是一个什么性质的回调,如果这个回调是一个不定期触发,或者会多次触发的,那么 Delegation 应该更适合;如果这个回调是一个一次性的,并且和调用方法是单线性关系的,那么 Block 应该更适合;如果这个回调是广播性质的,需要很多个不同的类都接收到,那么 NSNotification 更适合。
在不同的执行线(不是线程),不同的执行次数、执行数量上的区别,是鉴别使用哪一种回调的最好判断方法。
对于 Block 来说,他的执行线应该是和调用方法、回调方法连续在一起的;对于 Delegation 和 NSNotification 来说,他的执行线可以是连续的,也可以是调用方法和回调方法之间有很长的间隔,或者说回调方法在执行线上会多次出现。
对于高级的开发人员……你们要是不懂上面那些就不要做高级开发了,你们应该去研究线程、 GCD 、 NSOperation 这些玩意。
<a id="4">4. 属性的实质是什么?包括哪几个部分?</a>
属性是一个类中用来描述对象的抽象概念。
属性包括的部分有setter和getter方法
<a id="5">5. 属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?</a>
atomic:
nonatomic:
@synthesize
@dynamic
getter=getterName
setter=setterName
readwrite
readonly
assign
retain
copy
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 类型
<a id="6">6.NSString为什么要用copy关键字,如果用strong会有什么问题?</a>
因为用copy,以免因可变字符串的修改导致的一些非预期问题。用strong也不会出现问题, 不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
这里先感谢南峰子并引用了他的文章
我们在声明一个NSString属性时,对于其内存相关特性,通常有两种选择(基于ARC环境):strong与copy。那这两者有什么区别呢?什么时候该用strong,什么时候该用copy呢?让我们先来看个例子。
示例
我们定义一个类,并为其声明两个字符串属性,如下所示:
@interface TestStringClass ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@end
上面的代码声明了两个字符串属性,其中一个内存特性是strong,一个是copy。下面我们来看看它们的区别。
首先,我们用一个不可变字符串来为这两个属性赋值,
- (void)test {
NSString *string = [NSString stringWithFormat:@"abc"];
self.strongString = string;
self.copyedString = string;
NSLog(@"origin string: %p, %p", string, &string);
NSLog(@"strong string: %p, %p", _strongString, &_strongString);
NSLog(@"copy string: %p, %p", _copyedString, &_copyedString);
}
其输出结果是:
origin string: 0x7fe441592e20, 0x7fff57519a48
strong string: 0x7fe441592e20, 0x7fe44159e1f8
copy string: 0x7fe441592e20, 0x7fe44159e200
我们要以看到,这种情况下,不管是strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。如果我们换作MRC环境,打印string的引用计数的话,会看到其引用计数值是3,即strong操作和copy操作都使原字符串对象的引用计数值加了1。
接下来,我们把string由不可变改为可变对象,看看会是什么结果。即将下面这一句
NSString *string = [NSString stringWithFormat:@"abc"];
改成:
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
其输出结果是:
origin string: 0x7ff5f2e33c90, 0x7fff59937a48
strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8
copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0
可以发现,此时copy属性字符串已不再指向string字符串对象,而是深拷贝了string字符串,并让_copyedString对象指向这个字符串。在MRC环境下,打印两者的引用计数,可以看到string对象的引用计数是2,而_copyedString对象的引用计数是1。
此时,我们如果去修改string字符串的话,可以看到:因为_strongString与string是指向同一对象,所以_strongString的值也会跟随着改变(需要注意的是,此时_strongString的类型实际上是NSMutableString,而不是NSString);而_copyedString是指向另一个对象的,所以并不会改变。
结论
由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象,让我们的strongString指针指向一个可变字符串是OK的。
而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
关于字符串的内存管理,还有些有意思的东西,可以参考NSString特性分析学习。
<a id="7">7. 如何令自己所写的对象具有拷贝功能?</a>
对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying与 NSMutableCopying协议。
需声明该类遵从 NSCopying 协议
实现 NSCopying 协议。该协议只有一个方法
- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
至于如何重写带 copy 关键字的 setter这个问题,如果抛开本例来回答的话,如下:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
<a id="8">8.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?</a>
- 对于系统的非容器类对象,对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。
- 对于系统的容器类对象,以上规则同样适用,但是容器内的元素全部都是浅拷贝,也就是说所有的元素拷贝的仅仅是指针,内存没被复制。
详情内容参考此篇文章 iOS Copy 和 MutableCopy的区别 深浅拷贝的区别
<a id="9">9. 为什么IBOutlet修饰的UIView也适用weak关键字?</a>
IBOutlet的属性一般可以设为weak是因为它已经被view引用了,除非view被释放,否则IBOutlet的属性也不会被释放,另外IBOutlet属性的生命周期和view应该是一致的,所以IBOutlet属性一般设为weak。
可参考如下:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).
简单的说,如果IBOutlet对象是nib/sb scene的拥有者(File’s owner)所持有的对象,那么很显然拥有者必须“拥有”对象的指针,因此属性应设置为strong。而其他的IBOutlet对象的属性需要设置为weak,因为拥有者并不需要“拥有”他们的指针。举例来说,UIViewController的view属性是strong,因为controller要直接拥有view。而添加到view上的subviews,作为IBOutlet只需要设置为weak就可以了,因为他们不是controller直接拥有的。直接拥有subviews的是controller的view,ARC会帮助管理内存。
紧接着,文档里又提到:
Outlets should be changed to strong when the outlet should be considered to own the referenced object:
As indicated previously, this is often the case with File’s Owner—top level objects in a nib file are frequently considered to be owned by the File’s Owner.
You may in some situations need an object from a nib file to exist outside of its original container. For example, you might have an outlet for a view that can be temporarily removed from its initial view hierarchy and must therefore be maintained independently.
第一种情形前面已经解释过了,对于第二种,通俗点讲,就是controller需要直接控制某一个subview并且将subview添加到其他的view tree上去。单纯从ARC的角度思考,用weak也是很显然的:因为subview添加到view上时,view会“拥有”subview。当然,给IBOutlet属性设置为strong也没有错,“纠结谁对谁错“的问题可能需要上升到模式或者编码习惯的问题,已经超出本文的范围。
<a id="10">10. nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?</a>
- atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。
- atomic的意思就是setter/getter这个函数,是一个原语操作。如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,可以保证数据的完整性。nonatomic不保证setter/getter的原语行,所以你可能会取到不完整的东西。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。
- 比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态,这样取到的东西会有问题,就是不完整的。当然如果不需要多线程支持的话,用nonatomic就够了,因为不涉及到线程锁的操作,所以它执行率相对快些
一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才醒。
例如:一个线程在连续多次读取某个属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读取到不同的属性值。
因此,iOS程序一般都会使用nonatomic属性。但是在Mac OS X程序时, 使用atomic属性通常都不会有性能瓶颈
atomic一定是线程安全的么,回答是NO
nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全,而atomic的操作是原子性的,但并不意味着他就是线程安全的,它会增加正确的几率,能够更好的避免线程错误,但仍旧是不安全的。
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为等该操作而等待。当A线程的写操作结束后,B线程进行写操作,所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。如果有线程C在A线程读操作之前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
更准确的说应该是读写安全,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
其实无论是否是原子性的只是针对于getter和setter而言,比如用atomic去操作一个NSMutableArray ,如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题,这个就跟getter和setter就木有关系了。
<a id="11">11. UICollectionView自定义layout如何实现?</a>
UICollectionViewLayout是一个应该子类化的抽象基类,用来生成collection view的布局信息。 布局对象决定cell
,追加视图
(supplementary views), 和装饰视图
(decoration views) 在collection view内部的位置,并在 collection view 获取的时候提供这些布局信息。collection view将把这些布局信息应用到相应的视图上以便在屏幕上进行展示。
如果想使用UICollectionViewLayout,你必须把它子类化。在子类化UICollectionViewLayout之前,应该先了解UICollectionViewFlowLayout,看看它是否已经能够满足你的布局需求。(注:UICollectionViewFlowLayout是官方提供的子类化的UICollectionViewLayout,能满足绝大部分需求)
详细内容请看这篇文章 ->创建自定义UICollectionView layout
和这篇文章 ->iOS: 玩转UICollectionViewLayout
<a id="12">12. 用StoryBoard开发界面有什么弊端?如何避免?</a>
优点:
1: storyboard是苹果在iOS5之后提供一种全新制作UI方式,他提供了非常强大界面可视化,可以快速进行拖拉界面,完成自己APP.
2:可以非常清晰看出每个控制器(View Controller)中界面逻辑关系,结构非常一目了
3: 开发速度快
缺点
1: 在开发过程中,只要点击一下storyboard,不做任何修改,SVN工具就要提醒重新提交,非常蛋疼。
2: 用过storyboard人都知道,storyboard复用性很差。
3: 在团队开发者中,极易造成冲突。
在相对界面简单 没有多次修改的需求上建议用storyboard 或者xib编写 这样可以提升整体项目的速度 而对于复杂页面就用纯代码编写 ,可控性强 易维护。
<a id="13">13. 进程和线程的区别?同步异步的区别?并行和并发的区别?</a>
进程和线程的区别
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
进程和线程并不是一一对应的,一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程。而这一点正是程序无法做到的,由于程序没有和数据产生直接的联系,即使是执行不同的数据的程序,他们的指令的集合依然是一样的,所以无法唯一地标识出这些运行于不同数据集上的程序。一般来说,一个进程肯定有一个与之对应的程序,而且只有一个。而一个程序有可能没有与之对应的进程(因为它没有执行),也有可能有多个进程与之对应(运行在几个不同的数据集上)。同步异步的区别
线程同步是在多线程下才会产生的,多个线程同时访问同一块资源,为了安全高效的访问,就要解决同步访问带来的一系列问题。
打个比方,两个人从一个篮子里拿苹果,如果一个拿完之后再让另一个拿,那就不会出现问题,可是效率也不高。(totalTime = timeOfA + timeOfB)
如果让他们同时拿,你拿一个我拿一个,这样效率就高了,可是会出现问题,两个人都抢到一个苹果那该归谁呢,这时可以有几个方法,A拿苹果时B先等着,等A拿到了B在拿。或者A只能拿A这边的,B只能拿B这边的。这样就能解决问题了。
在iOS上最主要的方法就是在访问期间加锁,等访问完毕在解锁。
你拿一个苹果,然后我拿一个苹果, 这叫同步执行你和我同时去拿苹果,这叫两个线程异步执行,这里为了实现异步,使用了多线程的手段,你和我各属于一个线程然后就会出现问题了,如果你和我伸手拿了同一个苹果怎么办? 这个时候就需要你和我进行线程同步,比如:谁先看到篮子,就对篮子加锁,让另一个人等着,拿完之后解锁并行和并发的区别
并发行和并行性的区别可以用馒头做比喻。前者相当于一个人同时吃三个馒头和三个人同时吃一个馒头。
并发性(Concurrence):指两个或两个以上的事件或活动在同一时间间隔内发生。并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是
对有限物理资源强制行使多用户共享以提高效率。
并行性(parallelism)指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
区别:一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。
前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生。
两者的联系:
并行的事件或活动一定是并发的,但反之并发的事件或活动未必是并行的。并行性是并发性的特例,而并发性是并行性的扩展。
<a id="14">14. 线程间通信?</a>
- 线程间通信的体现
1 .一个线程传递数据给另一个线程
2 .在一个线程中执行完特定任务后,转到另一个线程继续执行任务
- 线程间通信常用的方法
`NSThread`可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在主线程执行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
<a id="15">15. GCD的一些常用的函数?(group,barrier,信号量,线程同步)</a>
- The main queue(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行,
- Main queue 可以通过dispatch_get_main_queue()来获取。
- Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
- Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)
- Custom queue (自定义队列): 可以为串行,也可以为并发。
- Custom queue 可以通过dispatch_queue_create()来获取;
- Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。
- Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。
<a id="16">16. 如何使用队列来避免资源抢夺?</a>
<a id="17">17. 数据持久化的几个方案(fmdb用没用过)</a>
- plist文件
- NSUserDefaults
- NSKeyedArchiver
- SQLite 3
- CoreData
详细内容查看此篇文章 包含FMDB的使用 iOS中几种数据持久化方案:我要永远地记住你!
<a id="18">18. 说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?</a>
– (void)applicationDidFinishLaunching:(UIApplication *)application;
此方法基本已经弃用,改用第2个方法代替。– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);
当应用程序启动时(不包括已在后台的情况下转到前台),调用此回调。launchOptions是启动参数,假如用户通过点击push通知启动的应用,这个参数里会存储一些push通知的信息。– (void)applicationDidBecomeActive:(UIApplication *)application;
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
当应用程序全新启动,或者在后台转到前台,完全激活时,都会调用这个方法。如果应用程序是以前运行在后台,这时可以选择刷新用户界面。– (void)applicationWillResignActive:(UIApplication *)application;
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
当应用从活动状态主动到非活动状态的应用程序时会调用这个方法。这可导致产生某些类型的临时中断(如传入电话呼叫或SMS消息)。或者当用户退出应用程 序,它开始过渡到的背景状态。使用此方法可以暂停正在进行的任务,禁用定时器,降低OpenGL ES的帧速率。游戏应该使用这种方法来暂停游戏。
调用时机可能有以下几种:锁屏,按HOME键,下接状态栏,双击HOME键弹出低栏,等情况。– (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
// Will be deprecated at some point, please replace with application:openURL:sourceApplication:annotation:
这个方法已不再支持,可能会在以后某个版本中去掉。建议用下面第6个方法代替– (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation NS_AVAILABLE_IOS(4_2);
// no equiv. notification. return NO if the application can’t open for some reason
当用户通过其它应用启动本应用时,会回调这个方法,url参数是其它应用调用openURL:方法时传过来的。– (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
// try to clean up as much memory as possible. next step is to terminate app
当应用可用内存不足时,会调用此方法,在这个方法中,应该尽量去清理可能释放的内存。如果实在不行,可能会被强行退出应用。– (void)applicationWillTerminate:(UIApplication *)application;
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
当应用退出,并且进程即将结束时会调到这个方法,一般很少主动调到,更多是内存不足时是被迫调到的,我们应该在这个方法里做一些数据存储操作。// one of these will be called after calling -registerForRemoteNotifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken NS_AVAILABLE_IOS(3_0);
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error NS_AVAILABLE_IOS(3_0);
当客户端注册远程通知时,会回调上面两个方法。
如果成功,则回调第一个,客户端把deviceToken取出来发给服务端,push消息的时候要用。
如果失败了,则回调第二个,可以从error参数中看一下失败原因。
注:注册远程通知使用如下方法:
UIRemoteNotificationType t = UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound;
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:t];
– (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo NS_AVAILABLE_IOS(3_0);
当应用在前台运行中,收到远程通知时,会回调这个方法。
当应用在后台状态时,点击push消息启动应用,也会回调这个方法。– (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification NS_AVAILABLE_IOS(4_0);
当应用收到本地通知时会调这个方法,同上面一个方法类似。
如果在前台运行状态直接调用,如果在后台状态,点击通知启动时,也会回调这个方法
本地通知可见另一篇文章:http://bluevt.org/?p=70– (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
当用户从台前状态转入后台时,调用此方法。使用此方法来释放资源共享,保存用户数据,无效计时器,并储存足够的应用程序状态信息的情况下被终止后,将应用 程序恢复到目前的状态。如果您的应用程序支持后台运行,这种方法被调用,否则调用applicationWillTerminate:用户退出。– (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
当应用在后台状态,将要进行动前台运行状态时,会调用此方法。
如果应用不在后台状态,而是直接启动,则不会回调此方法。
<a id="19">19. NSCache优于NSDictionary的几点?</a>
NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象。
注意一点它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy
NSCache中存储的对象也不必实现NSCoding协议,因为毕竟是临时存储,类似于内存缓存,程序退出后就被释放了。
NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache是线程安全的,而NSDictionary则绝对不具备此优势。
<a id="20">20. 知不知道Designated Initializer?使用它的时候有什么需要注意的问题?</a>
Designated Initializer(指定初始化器)
编写类的初始化函数时,需要注意以下五点:
- 确保类的Designated Initializer中,调用了父类的Designated Initializer函数。
- 父类的Designated Initializer函数的返回值保存在变量self中。
- 第2步结束后,如果self的值为nil,不可继续初始化操作。
- 确保子类覆写(override)了父类的Designated Initializer函数。
- 确保类里的每个非Designated Initializer的初始化函数都会调用Designated Initializer。
如第4点所要求,子类继承父类时,须知道父类的Designated Initializer函数。
下面列出常被继承的Cocoa类的Designated Initializer函数。
Class | Designated Initializer |
---|---|
NSObject | -init |
NSView | -initWithFrame: |
NSCell | -initImageCell: & -initTextCell: |
NSControl | -initWithFrame: |
NSDocument | -init |
NSWindowController | -initWithWindow: |
<a id="21">21. 实现description方法能取到什么效果?</a>
因为NSObject并不是唯一的“根类”,所以许多方法都要定义再NSObject协议里。比方说NSProxy也是一个遵从了NSObject协议的“根类”。由于description等方法定义在NSObject协议里,因此像NSProxy这种“根类”及其子类也必须实现他们。如前所见,这些实现好的方法并没有打印出较为有用的内容,只不过是输出了类名和对像的内存地址。只有在你想判断两指针是否真的指向同一对象时,这种信息才有用处。除此之外,再也看不出其他有用的内容了。我们想打印出来的对象信息应该比这更多才对。
要向输出更为有用的信息也很简单,只需覆写description方法并将描述此对象的字符串返回即可
<a id="22">22. objc使用什么机制管理对象内存?</a>
1).MRC(manual retain-release)手动内存管理
2).ARC(automatic reference counting)自动引用计数
详细内容查看此篇文章->iOS内存管理:基本概念与原理
中级
Block
<a id="23">1. block的实质是什么?一共有几种block?都是什么情况下生成的?</a>
Block实质就是Object-C对象
也可以理解为是一个函数指针加上一个对应捕获上下文变量的内存块(结构体或者类)
//
根据isa指针,block一共有3种类型的block
_NSConcreteGlobalBlock 全局静态
_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁
要点一:当block在函数内部,且定义的时候就使用了函数内部的变量,那么这个 block是存储在栈上的。
要点二:当block定义在函数体外面,或者定义在函数体内部且当时函数执行的时候,block体中并没有需要使用函数内部的局部变量时,也就是block在函数执行的时候只是静静地待在一边定义了一下而不使用函数体的内容,那么block将会被编译器存储为全局block。
要点三:全局block存储在堆中,对全局block使用copy操作会返回原函数指针;而对栈中的block使用copy操作,会产生两个不同的block地址,也就是两个匿名函数的入口地址。
要点四:ARC机制优化会将stack的block,转为heap的block进行调用。
<a id="24">2. 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?</a>
默认情况下无法修改被block捕获的变量,也就是说并不会在构造函数里面传入它们的值。Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。
__block 改变存储方式。
_block修饰自动变量后,_block的变量也被转化成了一个结构体
详情可参考这篇文章iOS block 捕获外部变量以及注意点
<a id="25">3. 模拟一下循环引用的一个情况?block实现界面反向传值如何实现?</a>
- (id)init
{
if (self = [super init]) {
self.arr = @[@111, @222, @333];
self.block = ^(NSString *name){
NSLog(@"arr:%@", self.arr);
};
}
return self;
}
block实现界面反向传值如何实现
//A页面跳转代码
BViewController *b = [[BViewController alloc]init];
B.saveBlock = ^(Person *person){
//接收到B页面传过来的数据 进行操作
[contactList addObject:person];
[tableView reloadData];
};
[self.navigationController pushViewController:two animated:YES];
//在B页面的.h文件里声明块属性
@property(nonatomic,strong)void(^saveBlock)(Person *);
//在B页面的.m文件里返回A页面的跳转方法里
Person *person = [[Person alloc]init];
person.name = nameText.text;
person.psw = pswText.text;
//这里用block传值
!self.saveBlock?:self.saveBlock(person);
[self.navigationController popViewControllerAnimated:YES];
剩下的来这里->iOS面试题 - 总会有你需要的(二)