面试题集锦
1.weak修饰的对象被释放置为nil,底层怎么实现的,其他关键字的使用
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
属性可以拥有的特质分为四类:
原子性--- nonatomic 特质
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。
读/写权限---readwrite(读写)、readonly (只读)
内存管理语义---assign、strong、 weak、unsafe_unretained、copy
方法名---getter=<name> 、setter=<name>
2.runloop的几种模式是怎么实现的,你还了解其他的runloop吗
- 系统默认注册了5个Mode常用的有3个
kCFRynLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
3.UICollectionView性能优化和iOS11的适配.
4.Swift的数组,交换第i个 和 第j个元素的位置,用元组交换, 但是交换中默认会copy一下原来的数组, 怎么让交换后的数组还是原来的数组(他说用什么inout 我也不知道)
5.第三方登录成功服务器返回什么标志着你登录成功
返回一个tocken
6.不能找到方法可以有三次挽救机会,分别都做了什么,应该在每个方法里面实现什么
1. 首先根据receiver对象的isa指针获取它对应的class.
2. 优先在class的cache查找message方法,如果找不到,再到methodLists查找.
3. 如果没有在class找到,再到super_class查找.
4. 一旦找到message这个方法,就执行该方法的实现(IMP).
7.YYWebImage支持渐进式加载,是怎么实现的
// 渐进式 : 边下载边显示
[self.imgView yy_setImageWithURL:URL options:YYWebImageOptionProgressive];
// 渐进式 : 增加模糊效果和渐变动画
[self.imgView yy_setImageWithURL:URL options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation];
8.对比一下OC和Swift的区别,Swift的可选项 ? 和 !是怎么实现的, 你能写伪代码吗
- Swift比Objective-C有什么优势?
(1)Swift容易阅读,语法和文件结构简易化。
(2)Swift更易于维护,文件分离后结构更清晰。
(3)Swift更加安全,它是类型安全的语言。
(4)Swift代码更少,简洁的语法,可以省去大量冗余代码
(5)Swift速度更快,运算性能更高。
- Swift目前存在的缺点
(1)版本不稳定,之前升级Swift3大动刀,苦了好多人(Swift4改动较小,但有着明确的去OC意图)
(2)使用人数比例偏低,目前还是OC的天下
(3)社区的开源项目偏少,毕竟OC独大好多年,很多优秀的类库都不支持Swift,不过这种状况正在改变,现在有好多优秀的Swift的开源类库了
(4)公司使用的比例不高,很多公司以稳为主,还是在使用OC开发,很少一些在进行混合开发,更少一些是纯Swift开发。
(5)偶尔开发中遇到的一些问题,很难查找到相关资料,这是一个弊端。
(6)纯Swift的运行时和OC有本质区别,一些OC中运行时的强大功能,在纯Swift中变无效了。
(7)对于不支持Swift的一些第三方类库,如果非得使用,只能混合编程,利用桥接文件实现。
- Swift的可选项 ? 和 !是怎么实现的
//? 的伪代码
if 指针指向的对象存在 {
return 该对象;
}else {
return nil;
}
//! 的伪代码
if 指针指向的对象存在 {
return 该对象
}else {
return nil;
// NSAssert 断言,判断条件是否成立,如果成立执行后续代码,不成立,成断点到此处
// 而断言只在Debug模式下才有用
NSAssert(指针指向的对象 != nil, @"指针指向的对象 is nil");
}
9.SDWebImage设置什么参数,服务器在不改变URL的情况下,本地即使有缓存也能更新头像,或者从HTTP的层面叙述一下
- SDWebImage的Options,具体枚举类型如下
10.App体积优化
http://www.jianshu.com/p/6c7b107d0321
11.给我讲一讲NSAutoreleasepool,开发中在哪里用到了
- 如果在循环中创建了大量的临时变量的时候需要在循环一开始就手动创建一个自动释放池
for (int i = 0; i < largeNumber; ++i) {
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@" - %d", i];
str = [str uppercaseString];
}
// 问 : 以上代码存在问题吗?
12.手写单例(oc和swift),你在开发中那里用到过单例
//OC单例 (懒汉式单例)
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [self new];
});
return instance;
}
//Swift单例
static let sharedTools: NetworkTools = NetworkTools()
/*
单例设计模式
1.有个全局访问点(实例化单例的类方法)
2.存储在静态存储区
3.在内存中有且只有一份
4.生命周期和APP一样长
单例设计模式使用场景
1.当某些对象在内存中必须有且只有一个时`必须`使用单例设计模式,(UIAPPlication/UIWindow/NSNotificationCenter/NSFileManager/NSUserDefaults/音乐播放器/...)
2.某些经常使用的工具类`可以`使用单例设计模式,(网络请求工具类/...)
单例的缺点
1.一直内存,无法及时释放,不能大量使用
2.在单例里面注册通知,无法移除,单例不会释放
3.单例里面一旦有循环引用,很难解除
总结 : 单例适当使用,不要随意使用,必须在关键点使用
*/
13.你都怎么检测循环引用,不许说Instrument工具里面那个
这个是个什么建波玩意
14.ARC会默认帮你加上retain和release,你知道是怎么实现的吗
- ARC本质是NSAutoreleasePool的直接应用
15.说一下MVVM,说这个的时候一定要说绑定,绑定,绑定
- MVVM 概念
在 MVVM 中,view 和 view controller 正式联系在一起,我们把它们视为一个组件
view 和 view controller 都不能直接引用 model,而是引用视图模型
view model 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码
- MVVM 使用注意事项
view 引用 view model,但反过来不行
view model 引用了 model,但反过来不行
如果我们破坏了这些规则,便无法正确地使用 MVVM
- MVVM 的优点
低耦合:View 可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上
可重用性:可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑
独立开发:开发人员可以专注于业务逻辑和数据的开发 ViewModel,设计人员可以专注于页面设计
可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 ViewModel 来进行测试
16.谈一下GCD和NSOperation的区别,那些功能用GCD比较好实现,为什么框架里面喜欢用NSOperation,你知道那些框架用了NSOperation,怎么实现的
(1) GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
(2) 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
(3) NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
(4) 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
(5) 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
(6) 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
17.什么是响应链,他是怎么工作的?
(1)比如有一个View被点击了,那么它的父View就是下一个响应者(Next Responder)。
(2)这条链路按照这个规则还在继续,在视图层级当中,直到View所在UIViewController,这个Controller就是这个View的下一个响应者。
(3)如果这个Controller是KeyWindow的RootViewController,那么window就是Controller的下一个响应者。
(4)application是KeyWindow的下一个响应者
(5)appDelegate是application的下一个响应者
18.+load 和 +ialtialize的区别是什么?
- load
顾名思义,load方法在这个文件被程序装载时调用。只要是在Compile Sources中出现的文件总是会被装载,这与这个类是否被用到无关,因此load方法总是在main函数之前调用。
- initialize
这个方法在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次。initialize方法实际上是一种惰性调用,也就是说如果一个类一直没被用到,那它的initialize方法也不会被调用,这一点有利于节约资源。
19.为什么NotificationCenter 要removeObserver? 如何实现自动remove?
- 如果不移除的话,万一注册通知的类被销毁后又发送了通知,程序会崩溃,因为野指针发送了消息.
- 实现自动remove:通过自释放机制,通过动态的将remove转移给第三者,接触耦合,达到自动实现remove
20.简要说说KVC机制通过key找value的原理,以及你在项目中用KVC处理过怎么样的问题?\
(1)去模型中查找有没有setter方法setA,就直接调用这个setter方法,给模型这个属性A赋值[self setA:dict[@"A"]];;
(2)如果找不到setter方法,接着就会去寻找有没有属性,如果有,就直接访问模型中A = dict[@"A"];
(3)如果找不到属性,接着又会去寻找_A属性,如果有,直接_A = dict[@"A"];
(4)如果都找不到就会报错 使用KVC给看不见的属性赋值
首先介绍两个基本概念:
(1)SEL数据类型:它是编译器运行Objective-C里的方法的环境参数。
(2)IMP数据类型:他其实就是一个 编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型(事实上,在Objective-C的编译器处理的时候,基本上都是C语言的)。
KVC内部的实现:一个对象在调用setValue的时候。
(1)首先根据方法名找到运行方法的时候所需要的环境参数。
(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。
(3)再直接查找得来的具体的方法实现。
KVO实现
KVC机制上加上KVO的自动观察消息通知机制。
当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名。
熟悉KVO的朋友都知道,只有当我们调用KVC去访问key值的时候KVO才会起作用。所以肯定确定的是,KVO是基于KVC实现的。
因为KVC的实现机制,可以很容易看到某个KVC操作的Key,而后也很容易的跟观察者注册表中的Key进行匹对。假如访问的Key是被观察的Key,那么在内部就可以很容易的到观察者注册表中去找到观察者对象,而后给他发送消息。
21.谈谈你对数据归档的理解,以及你在数据归档过程中需要注意的事项.
数据归档,遵守NSCoding协议,实现协议中归档解档的两个方法。注意避免在多线程的情况下操作数据
大量数据的时候不要使用归档,使用数据库,少量数据的时候使用归档
22.浅谈NSTimer的内存泄漏问题.
使用NSTimer的过程中可能会遇到控制器明明销毁了,但是就是不会走控制器的dealloc销毁方法,NSTimer也没有停止运行,这就说明在使用NSTimer过程中出现了循环引用导致的内存泄露问题
原因大致如下:
1.当前线程RunLoop对NSTimer对象强引用
2.NSTimer又对self强引用
3.这样就造成了控制器(self)如果想要销毁的话,NSTimer就要先调4.用invalidate来销毁
5.但是控制器(self)不销毁的话
6.这就产生了一个死循环,谁也释放不掉
运行循环的生命周期和app一样长,app运行运行循环就在,运行循环对timer进行强引用,timer就不会被释放,timer又对控制器进行强引用(scheduledTimerWithTimeInterval:2.0 target:self ),这样就会循环引用,从当前界面退回到上一个界面之后(pop的时候,控制器的view先消失,如果控制器没有被强引用了,就会被释放),控制器不能被释放,造成后台运行,内存泄漏。解决方法就是停止定时器,使其不再对控制器进行强引用
//
// // 停止定时器的方法:invalidate,停止以后就消失了,再也不会执行了,除非重新创建
// [_timer invalidate];
23.有a,b,c,d四个异步请求,如何判断a,b,c,d都执行完成? 如果需要a,b,c,d顺序执行,该如何实现?
a,b,c,d都执行完可以使用调度组,最后在notify函数中执行接下来的逻辑,此时a,b,c,d已经执行完了。a,b,c,d顺序执行可以使用串行队列,或者使用NSOperation添加依赖
24.block和delegate的区别?
作为非常常见,且无处不在的block和delegate,理解它们,是我们掌握iOS开发必备知识点。
1.从源头上理解和区别block和delegate
delegate运行成本低,block的运行成本高。
block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
2.从使用场景区别block和delegate
有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。当1,2个回调时,则使用block。
delegate更安全些,比如: 避免循环引用。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。
delegate回调返回的参数被限制在了 NS 类的范围内,数量也很有限(当然可以用直接调用方法的形式在绕过,并不推荐;也可以用 Array 套着传, 不过这样需要有文档支持,不然不够清晰,回调方法也需要独立的验证,故也不推荐)。
那如何使用?
如果你从其他语言转到 Objective-C 或者 Swift ,相信 Delegation 肯定让你觉得更加亲切,那么在初级阶段请使用好这个语法糖,多用,多去理解;如果你用着 AFNetworking 看着其他老前辈的说法用 Block 觉得效率很高很开心,那就开心的用,直到你被循环引用烦到了为止;然后,在你代码写多了之后,你可以开始尝试接触其他回调方式,去感受这些回调方式的不同。关键在于对于回调流程的理解。你要知道你的回调是一个什么性质的回调,如果这个回调是一个不定期触发,或者会多次触发的,那么 Delegation 应该更适合;如果这个回调是一个一次性的,并且和调用方法是单线性关系的,那么 Block 应该更适合。在不同的执行线(不是线程),不同的执行次数、执行数量上的区别,是鉴别使用哪一种回调的最好判断方法。
对于 Block 来说,他的执行线应该是和调用方法、回调方法连续在一起的;对于 Delegation 和 他的执行线可以是连续的,也可以是调用方法和回调方法之间有很长的间隔,或者说回调方法在执行线上会多次出现。