记录一下自己最近面试过程中遇到的一些面试题。
1. OC中@property的作用是什么?可以有哪些关键字修饰?
@Property是声明属性的语法,作为OC的一项特性,主要作用就在于封装对象中的数据。可以快速方便的为实例变量创建存取器,并允许通过点语法使用存取器。
@property本质就是ivar(实例变量) 和 getter / setter(存取方法)。
存取器(accessor):用于获取和设置实例变量的方法。用于获取实例变量值的存取器是getter,用于设置实例变量值的存取器是setter。
关键字修饰:
- 线程安全的(关于是否原子访问):
atomic, nonatomic
- 访问权限的(关于访问控制操作):
readonly, readwrite
- 内存管理(MRC)(关于set方法中属性引用计数相关):
assign, retain, copy
- 内存管理(ARC)(增加了weak、strong属性):
assign, strong, weak, copy
- 指定方法名称:
getter= / setter=
2. ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
- 基本数据类型默认关键字是
atomic, readwrite, assign
- 普通的OC对象默认关键字是
atomic, readwrite, strong
3. 谈谈对@protocol的理解?@property能否在@protocol中使用?
协议声明了任何类都能够选择实现的程序接口。协议能够使两个不同继承树上的类相互交流并完成特定的目的,因此它提供了除继承外的另一种选择。如果协议遵守者实现了协议中的方法,那么声明协议的类就能够通过遵守者调用协议中的方法。
总结:
- @Protocol是用来声明一系列方法定义公共接口(不能声明成员变量),不能写实现
- 只要某个类遵守了这个协议,就拥有了这个协议中的所有方法声明
- 只要父类遵守了某个协议,那么它的子类也遵守
- OC不能多继承但是能够遵守多个协议;继承
:
遵守协议<>
- 基协议:
<NSObject>
基协议,是最基本的协议其中声明了很多最基本的方法,例如要辨别id <协议名>这个指针所指的对象属于哪个类,就要用到-isMemberOf:
这个方法,而这个方法是<NSObject>
这个协议中的方法之一,所以我们自定义的协议都需要继承<NSObject>
。 - 协议可以遵守协议,一个协议遵守了另一个协议,就可以拥有另一个协议中的方法声明(称为协议继承)
协议中能够声明方法,以及属性。但是不能定义实例变量。@property包含了实例变量、setter方法和getter方法。在类中定义的属性,当然三者都有,然而由于@protocol特性的限制,@property在@protocol中并不会合成实例变量,只会合成存取方法。这就要求该协议的遵守者必须自己写出setter和getter方法的实现。但是有一种情况是不需要的,那就是遵守者本来就有这个属性,此时系统会为这个属性自动生成存取方法,既然已经实现了,那么遵守者就没必要去实现协议中的这个属性了。尽管可以实现‘伪属性’,但是,我们还是应该尽量把属性定义在主接口中,而不应该定义在协议中。
4. 通过Category给现有类添加的属性,可以直接使用吗?
比如建立个UIView的Loading分类并增加beginLoading属性,如下:
|
你会发现编译器并不会报任何错误,build一下也不会有问题,但是运行后会发生crash。这是因为在运行时找不到getter and setter methods,所以发生崩溃。如何解决?可以利用runtime
的Associated Objects动态关联属性来解决问题。
参考:iOS之Category属性的理解
- 继续问:通过Associated关联的对象被存放在什么地方,关联对象的生命周期是什么样的?
可以参考这篇文章:Objective-C Associated Objects 的实现原理
5. @synthesize和@dynamic分别有什么作用?
@synthesize
和@dynamic
是@property的两个对应词。如果@synthesize
和@dynamic
都没写,那么默认的就是@syntheszie var = _var;
@synthesize告诉编译器:如果你没有手动实现setter和getter方法,编译器会自动帮你生成。
@dynamic 告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var
,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar
,由于缺setter方法会导致程序崩溃;或者当运行到someVar = var
时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
6. 说一说ARC下的assign与weak区别?
- weak :
1)ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如最常见的delegate代理属性。
2)如果自身已经对它进行一次强引用,没有必要再次强引用时也会使用weak。比如自定义IBOutlet控件属性一般使用weak,当然也可以使用strong。 - assign :
assign是指针赋值,不对引用计数操作,适用于基本数据类型如NSInteger, int, float, struct
等值类型,不适用于引用类型。 - 不同点:
weak,表明该属性定义了一种“非拥有关系” (nonowning relationship)。为属性设置新值时,设置方法既不保留新值,也不释放旧值。
assign也可以修饰对象,但是用assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,会造成众所周知的野指针异常。然而,assign修饰的基础数据类型(例如NSInteger等)和C数据类型(int, float, double, char)等一般分配在栈空间上,栈空间的内存会由系统自动处理,当分配的栈空间的内存没有被指针指向时就会被销毁,所以不会造成野指针异常。
weak比assign多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil,这样再向 weak修饰的属性发送消息就不会导致野指针操作crash。 - 总结:
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要自己手动管理内存或通过ARC管理。
weak适用于delegate等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
7. iOS中weak的实现原理?
参考文章:iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析)
8. NotificationCenter为什么要removeObserver? 如何实现自动remove?
首先NotificationCenter是一个单例类,通过[NSNotificationCenter defaultCenter]
来获取单例对象。
它有三个核心函数和一个观察者数组:
- 订阅消息: addObserver()。订阅感兴趣的消息。
- 发布消息: postNotification()。发布消息。
- 退订消息: removeObserver()。不感兴趣了,就退订。
- 观察者数组: _observers
Notification是一个单例类,通常在释放场景或者某个对象之前,都要取消场景或对象订阅的消息,否则,注册通知的类被销毁以后再当消息产生时,会因为对象不存在,即向野指针发送了消息,而产生一些意外的BUG。
实现自动remove:
通过自释放机制,通过动态属性将remove
转移给第三者解除耦合,达到自动实现remove
9. NSNotification、KVO、Delegate和Block的区别?
KVO就是cocoa框架实现的观察者模式,通过KVO可以监测一个值得变化,比如View的高度变化。是一对多的关系,一个值得变化会通知所有的观察者。一般使用场景是数据,需求是数据变化,比如股票价格变化,一般使用KVO(观察者模式)。
NSNotification是通知,也是一对多的使用场景。NSNotification的特点就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行相应,比KVO多了发送通知的异步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
Notification一般是进行全局通知,是弱关联,消息发出后,不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出的消息。
Delegate是强关联,就是委托和代理双方互相知道,是一对一关系。
Block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且比代理的实现更直观。
10. UIViewController的生命周期及调用顺序?
UIViewController中与其生命周期有关的几个函数如下:
除了initialize,init
和initWithCoder
不是存在所有对象的声明周期中,其他函数都会在UIViewController的声明周期中有序的被调用。那么具体的调用顺序是怎样的呢,最好的办法是实践一下,通过编号打印,结果如下:
参考文章:iOS对UIViewController生命周期和属性方法的解析
说下遇到的一道选择题:如果页面A跳转到页面B,以下选项正确的是()?
A、A页面的 viewDidDisappear 方法先调用;
B、B页面的 viewDidAppear 方法先调用;
C、不一定,都有可能;
答案 我是选的B
不过准确的说应该分情况而定,经过实际测试发现如果是push跳转的话,执行顺序是
页面A - [ViewControllerA viewDidDisappear:]
页面B - [ViewControllerB viewDidAppear:]
如果是present跳转的话,执行顺序是
页面B - [ViewControllerB viewDidAppear:]
页面A - [ViewControllerA viewDidDisappear:]
如有不对,感谢纠正。
11. 如何对下面数组中的元素去重(代码,伪代码,思路都可以)?
NSArray *array = @[@"12-11", @"12-11", @"12-11", @"12-12", @"12-13", @"12-12"];
1)利用containsObject
判断
2)利用NSSet自动去重特性
以上是自己写的两种方法,若有更好的答案感谢告知。
12. 请问下面的代码会输出什么?为什么?
|
两句输出语句均输出:ClassB
简单来说,self和super都是指向当前实例的,不同的是[self class]
会在当前类的方法列表中去找class这个方法,[super class]
会直接开始在当前类的父类中去找calss这个方法,两者在找不到的时候,都会继续向祖类查询class方法,最终到NSObject类。那么问题来了,由于我们在ClassA和ClassB中都没有去重写class这个方法,最终自然都会去执行NSObject中的class方法,结果也自然应该是一样的。至于为什么是ClassB,可以看看NSObject中class的实现:
返回的都是self的类型,self此处正好就是ClassB,因此结果就会输出ClassB。
参考链接:有关super和self的问题
13. 说说你常使用的lldb调试命令?
LLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xcode运行程序,实际走的都是LLDB。熟练使用LLDB,可以让debug事半功倍。
常用LLDB命令:
expression
expression命令的作用是执行一个表达式,并将表达式返回的结果输出。可以实现2个功能:
1) 执行某个表达式。 我们在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。 假如我们在运行过程中,突然想把self.view颜色改成红色,看看效果。我们不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果
2) 将返回值输出。 也就是说我们可以通过expression来打印东西。 假如我们想打印self.view
- p & print & call
print: 打印某个东西,可以是变量和表达式
p: 可以看做是print的简写
call: 调用某个方法 - po
OC里所有的对象都是用指针表示的,所以一般打印的时候,打印出来的是对象的指针,而不是对象本身。如果我们想打印对象。我们需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O
定义了一个别名:po还有其他很多命令选项,不过一般用得比较少,所以就不具体的一一介绍了,如果想了解,在LLDB控制台上输入:help expression即可查到expression所有的信息
该题图片内容来自:熟练使用 LLDB,让你调试事半功倍
14. 如下ViewB是ViewA的子视图,其中1/4的区域在父视图ViewA上,如何让ViewB的3/4区域也响应事件?
参考这里:Cocoa Touch中的响应者链