iOS高级开发面试题整理iOS面试题iOS面试

一颗不甘平凡的心之跳槽面试总结(有酷狗iOS经历)

2018-11-03  本文已影响2人  提呐个莫

前言

俗话说得好,no zuo no die,真的是不作一下,都不知道自己是怎么死的。
本人iOS4年经验,坐标广州,前后换过3个公司。
第一家公司让我转型成了iOS开发,开始了这条“不归路”。
第二家公司是一家创业公司,也是在这家公司让我成长到了现在这个模样。在这家公司经历了太多起起落落,最后因为公司转型,无奈离场。但是在这公司的两年里,结识了许多非常不错的小伙伴,还是很有收获的,在此还是表以感激之情。
第三家公司是一家算比较大(中型?)的公司吧,在这里开始体会到了大公司的制度。( 代码规范,代码规范,代码规范!划重点
三个月后离职,主要原因:

  1. 从一家创业公司作为iOS端负责人(虽然只有两到三个人)进入大公司成了一颗小螺丝钉,心里落差还是很大的,有一种非常压抑的感觉。
  2. 代码提交需要审核,合并权限在于负责人。
  3. 项目主要基于webView开发,个人感觉没有太大发展(毕竟我是个有追求的人,追求技术深度,并非业务广度)。
  4. 公司开始推RN项目(重写现有项目),这是个大前端趋势,我并不反对。但是...需要自己去看代码熟悉前端H5、后台的业务...这就有点接受不了了!
  5. 每天写日报,记工时,绩效每月一次(填表感觉在论文,需要各种举证说明,简直吐血...)。福利一般,12薪,没有年终。

于是,迫于一颗不甘平凡的心,就开始了离职找工作的作死行为。

正文

第一家公司就去了酷狗,在这里奉劝大家,在没有作为准备的提前下,千万千万千万不要去自己想去的大公司面试。如果,像我一样,离职之后第二天就去了酷狗面试,那最终结果就是:被虐得很残!
不过,虐归虐,还是收获颇多的。
来来来,接下来,给大家介绍一下酷狗面试流程(有即将要去或想去酷狗的童鞋们,快拿小本本来记笔记了)。
酷狗每轮面试必须达到要求才能进入下一轮。

  1. 在线测试题,个人成绩80分。(选择题+数据缓存设计题,总分100,及格60。答完之后,显示的成绩只是选择题成绩。
  2. 逻辑测试,成绩未知,个人感觉应该挺高的。(总分100,及格80。去之前可以网上刷下经典逻辑测试题库,反正我是刷到了几个现成题。最后一道题是扫雷,9*7方格,根据已知信息,标出6个地雷所在位置
  3. 技术面,在此终结了,也是预料之中的事!
    我面试的岗位是酷狗直播(我之前有过一些音、视频的简单处理经验),网络协议相关的知识点是我的弱项,面试过程问了很多相关知识点,结果可想而知了。
  4. 据说后面还会有一轮技术面,然后再是HR面。

整个面试过程,感觉面试官还是不错的。他会根据过往经历问到相关知识点,会循序渐进地问得很深。基础功是否扎实,基本都能在交谈过程中暴露出来。

面试中问到的问题,记录如下(部分为引申出来的知识点):
1. 手势的传递过程、响应过程。
手势的传递是从上到下(父控件到子控件)。
事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

2. HTTP和HTTPS的区别:
https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

3. http请求中的8种请求方法:

  1. opions 返回服务器针对特定资源所支持的HTML请求方法 或web服务器发送*测试服务器功能(允许客户端查看服务器性能)
  2. Get 向特定资源发出请求(请求指定页面信息,并返回实体主体)
  3. Post 向指定资源提交数据进行处理请求(提交表单、上传文件),又可能导致新的资源的建立或原有资源的修改
  4. Put 向指定资源位置上上传其最新内容(从客户端向服务器传送的数据取代指定文档的内容)
  5. Head 与服务器索与get请求一致的相应,响应体不会返回,获取包含在小消息头中的原信息(与get请求类似,返回的响应中没有具体内容,用于获取报头)
  6. Delete 请求服务器删除request-URL所标示的资源*(请求服务器删除页面)
  7. Trace 回显服务器收到的请求,用于测试和诊断
  8. Connect HTTP/1.1协议中能够将连接改为管道方式的代理服务器
    http服务器至少能实现get、head、post方法,其他都是可选的

4. GET、POST请求有什么区别?POST是否可以完全代替GET:
GET 请求可被缓存,POST 请求不会被缓存。
GET 请求保留在浏览器历史记录中,POST 请求不会保留在浏览器历史记录中。
GET 请求可被收藏为书签,POST 不能被收藏为书签。
GET 请求有长度限制,POST 请求对数据长度没有要求。
GET把参数包含在URL中,POST通过request body传递参数。
GET 速度一般比 POST快。
GET 请求不应在处理敏感数据时使用。
重复的交互,比如取个数据,跳个页面, 用GET。
不可以重复的操作, 比如创建一个条目/修改一条记录, 用POST, 因为POST不能被缓存,所以浏览器不会多次提交。
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

5. HTTP、Socket、TCP
TCP连接和HTTP连接的区别
HTTP基于TCP

TCP连接与Socket连接的区别
基于TCP协议的Socket连接同样需要三次握手建立连接, 是可靠的
基于UDP协议的Socket连接不需要建立连接的过程, 不管对方能不能接收到都会发送过去, 是不可靠的, 大多数的及时通讯IM都是后者

HTTP连接和Socket连接的区别
HTTP是短连接, Socket(基于TCP协议的)是长连接
HTTP连接服务端无法主动发消息, Socket连接双方请求的发送先后限制

什么时候该用HTTP, 什么时候该用Socket
用HTTP的情况: 双方不需要时刻保持连接在线, 比如客户端资源的获取、文件上传等
用Socket的情况: 大部分及时通讯应用(QQ、微信)、聊天室、苹果APNs等

iOS中, 发HTTP请求一般用原生的 NSURLConnection、NSURLSession、AFNetworking(推荐)
连接Socket连接,可以用CocosAsyncSocket.

6. 基本HTTP协议的断点下载(FTP断点下载,暂时没用到
简单的断点续传实现大概如下:

  1. 客户端下载一个1024K的文件,已经下载了其中512K
    网络中断,客户端请求续传,因此需要在HTTP头中申明本次需要续传的片段:Range:bytes=512000-
    这个头通知服务端从文件的512K位置开始传输文件
    服务端收到断点续传请求,从文件的512K位置开始传输,并且在HTTP头中增加:Content-Range:bytes 512000-/1024000
    并且此时服务端返回的HTTP状态码应该是206,而不是200。

  2. 如何获取被下载文件的总字节数
    这里我们需要用到http 头部的conten-length字段。
    Content-Length用于描述HTTP消息实体的传输长度the transfer-length of the message-body。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是gzip压缩后的长度。
    简单点说,content-length表示被下载文件的字节数。
    有了这两个值,我们就可以算出下载进度了。

  3. 如何判断是否为同一个下载请求。
    我们需要把每个被下载文件的总字节数存储起来,这里我们选择使用plist文件来记载,plist文件包含一个字典。使用下载url作为key。

7. Runtime的使用

  1. MJExtesion中的JSON解析。
    主要用到知识点:
// 获取属性列表
objc_property_t *properties = class_copyPropertyList(c, &outCount); 
// 通过KVO赋值
[object setValue:value forKey:self.name];
// 类的反射
NSString *key = NSStringFromClass(c);
// 其它更详细的,可以自行学习源代码
  1. Method Swizzling黑魔法
    例子:AvoidCrash
    //主要
    Method method1 = class_getClassMethod(anClass, method1Sel);
    Method method2 = class_getClassMethod(anClass, method2Sel);
    method_exchangeImplementations(method1, method2);
  1. Category添加属性
// 利用runtime绑定getter方法
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
// 利用runtime绑定setter方法
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)

8. Method Swizzling加载时机
Swizzling应该总在+load中执行
在OC中,Runtime会在类初始加载时调用+load方法,在类第一次被调用时实现+initialize方法。由于Method Swizzling会影响到类的全局状态,所以要尽量避免在并发处理中出现竞争情况。+load方法能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。
要使用dispatch_once执行方法交换
方法交换要求线程安全,而且保证在任何情况下只能交换一次。

9. Method Swizzling原理
Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。

上图中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们添加了selector3IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了“方法互换”。

在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。

在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SELIMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP

10. NSThread、GCD、NSOperation的区别

  1. NSThread
    轻量级的线程操作,需要我们自己创建线程,调度任务,销毁线程
  2. GCD
    基于C语言
  3. NSOperation
    纯OC代码 操作队列,对GCD的封装.它是一个抽象类,只能使用其子类对象。系统提供了两个子类对象,分别是 NSInvocationOperation 和 NSBlockOperation。通常我们自定义 NSOperation 的子类,重写子类的 main 方法,把需要在分线程执行的任务放在 main 方法里。然后把 NSOperation 对象添加到 NSOperationQueue 中,就会自动在分线程执行 main 方法。

NSOperationQueue和GCD区别联系
区别
NSOperationQueue没有串行/并发队列,但可以设置最大并发数;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue可以暂停/取消操作;
NSOperationQueue支持更多的功能,比如KVO和自定义操作;
NSOperationQueue可以设置队列/操作的优先级,GCD只能设置队列的优先级。

联系
提供的功能是相似的;
NSOperationQueue是GCD的封装。

11. 死锁
定义:
所谓死锁,通常指有两个线程T1和T2都卡住了,并等待对方完成某些操作。T1不能完成是因为它在等待T2完成。但T2也不能完成,因为它在等待T1完成。于是大家都完不成,就导致了死锁(DeadLock)。
产生死锁的条件:
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。

12. 可能发生死锁的情况
在某一个串行队列中,同步的向这个串行队列添加block。

// 这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。
// 因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。
dispatch_sync(dispatch_get_main_queue(), ^(void){
            NSLog(@"这里死锁了");
        });

结论:1.异步执行block肯定不会发生死锁。 2.同步的向 并发队列 中添加block不会导致死锁。

视频直播中的弹幕系统,该怎么设计模块
预留位置,想到答案了再来补吧...

其它一些比较经典容易犯错的面试题

下面关于线程管理错误的是:B
A. GCD所用的开销要比NSThread大
B. 可以在子线程中修改UI元素
C. NSOperationQueue是比NSthread更高层的封装
D. GCD可以根据不同优先级分配线程

理由:首先,UI元素的更新必须在主线程。
GCD与Block配合使用,block需要自动捕获上下文变量信息等,因此需要更多的资源,故比NSThread开销要大一些。
NSOperationQueue与NSOperation配合使用,比NSThread更易于操作线程。
GCD提供了多个优先级,我们可以根据设置优先级,让其自动为我们分配线。
串行队列
设置了优先级后,队列不一定是按FIFO规则,出队的顺序按优先级规则。
并行队列
并发队列执行任务的顺序是不确定的。对于同一优先级的任务,他们出队的顺序一定是FIFO,先进先出,但是先执行的顺序是不确定的!

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});

下面关于Objective-C内存管理的描述错误的是:A
A. 当使用ARC来管理内存时,代码中不可以出现autorelease
B. autoreleasepool 在 drain 的时候会释放在其中分配的对象
C. 当使用ARC来管理内存时,在线程中大量分配对象而不用autoreleasepool则可能会造成内存泄露
D. 在使用ARC的项目中不能使用NSZone

Objective-C有私有方法吗?有私有变量吗? C
A. 有私有方法和私有变量
B. 没有私有方法也没有私有变量
C. 没有私有方法,有私有变量
D. 有私有方法,没有私有变量

理由:
在Objective‐C中,所有实例变量默认都是私有的,所有实例方法默认都是公有的。
1.OC中只有静态方法和实例方法。私有方法,可以通过延展(extension)实现,即在.m文件中声明与实现,只供内部使用,外部不能直接看到和使用,但是这不是真正意义上的私有方法。
2.OC中有私有变量,通过@private声明私有变量

下面代码的作用是让doSomeThing函数每隔1秒被调用1次。请问哪里有问题? A

NSTimer *myTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(doSomeThing:)  userInfo:nil repeats:YES]; 

[myTimer fire]

A. 没有将timer加入runloop
B. doSomeThing缺少参数
C. 忘记传递数据给userInfo
D. myTimer对象未通过[[myTimer alloc] init]方法初始化

简述runloop和线程有什么关系
每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
runloop和线程的关系:主线程的run loop默认是启动的,子线程的runloop默认是不开启的,需要我们自己手动开启循环。
处在当前线程就只能操作当前线程的runloop。但可以给其它runloop发送消息。
runloop同一时间只能处理一种runloopMode,其它mode无法响应。
runloop执行完毕之后,就会进入休眠,只有在某个情况下触发了,才会再次调用。

runloopMode

自动释放池是什么?如何工作的?
自动释放池是cocoa提供的帮助我们管理对象内存的一个工具。当我们像一个对象发送autorelease消息时,这个对象就自动加入到最新的自动释放池中,当自动释放池被销毁的时候,会自动向自动释放池中的所有对象发送一条release消息。也就是说我们不再需要手动向每一个对象发送release消息以释放对象,而是将其加入到自动释放池中最后统一释放。使用自动释放池也可以避免一些人为原因导致的内存泄漏。

在实际开发过程中,什么情况下需要创建自动释放池?
官方文档中有2种情况:
If you write a loop that creates many temporary object.
循环中创建了许多临时对象,在循环里使用自动释放池,用来减少高内存占用。
If you spawn a secondary thread.
开启子线程的时候要自己创建自己的释放池,否则可能会发生内存泄漏。

简述UIView与CALayer有什么关系和区别

  1. 首先UIView可以响应事件,Layer不可以。
  2. UIView主要是对显示内容的管理,而CALayer 主要侧重显示内容的绘制。
  3. 在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。
    对于每一个 UIView 都有一个 layer,把这个 layer 且称作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s。

用递归函数,实现栈中元素的逆序!(不能用其它数据结构,如新建栈)
以下答案为个人理解,并非标准答案,如有错误,欢迎指出互相交流学习。

// NSMutableArray *array = @[@5, @4, @3, @2, @1].mutableCopy;
- (void)reverseArray:(NSMutableArray *)array start:(NSInteger)start end:(NSInteger)end
{
    if (start > end) {
        return;
    }
    if (array.count <= start || array.count <= end) {
        return;
    }
    NSNumber *temp = array[start];
    array[start] = array[end];
    array[end] = temp;
    [self reverseArray:array start:++start end:--end];
}

iOS中开发中,常常用到UIImageView来显示图片,但是是如何展示出来的?
iOS从磁盘加载一张图片,使用UIImageVIew显示在屏幕上,需要经过以下步骤:

  1. 从磁盘拷贝数据到内核缓冲区
  2. 从内核缓冲区复制数据到用户空间
  3. 生成UIImageView,把图像数据赋值给UIImageView
  4. 如果图像数据为未解码的PNG/JPG,转码为位图数据
  5. CATransaction捕获到UIImageView layer树的变化
  6. 主线程Runloop提交CATransaction,开始进行图像渲染
    6-1如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐。
    6-2 GPU处理位图数据,进行渲染。

未完待续...

上一篇下一篇

猜你喜欢

热点阅读