2015网易iOS岗实习面试小记(广州)

面试已经过去有一段时间了,之前一直忙于其他零零碎碎的事情,没有把网易的面试记录下来,今天就靠着记忆,写写网易的面试经历。


先从投简历+在线笔试说起,在网易实习招聘的官网按提示填好信息、投递简历即可,本人是iOS APP开发方向,所以投的是网易的“平台开发工程师”,据网易的说法,不是所有投了简历的人都有参加在线笔试的资格的,按我和我几个都过了简历筛选的同学的情况来看:只要是计算机相关专业,正常的本科院校,有一定项目经历的简历基本都可以通过简历筛选这一关(当然你的简历还是要写的比较简洁、主次分明、突出自己的优势)。

在线笔试类似以前的ACM比赛,在hihoCoder上有三道算法题,两个小时答题时间,答对越多、提交越少、提交越早,排名越高。我大概看了看,参加这个在线笔试的人大概有500-600人,三题中有一题比较简单的,一道适中,还有一个稍难一些的,最后三题加起来的AC人数也就300个左右..所以基本上说,只要能够AC一题,就可以过这个在线笔试(我就只AC了一题..是一道小人推箱子的模拟题),当然,笔试的成绩肯定也会对之后的面试有一定的影响。


一面


没几天就接到网易HR的电话,说我通过了在线笔试,约我找时间进行电话面试(可能是岗位不同,我有一个同学投的游戏开发部,连笔试都没有直接就面试了),最后约了当周周二晚上6:00开始面试,HR说面试大概会持续一个小时左右。

经过之前微信和腾讯的面试,我大致了解了企业面试的“模式”,而且通过一段时间来对内存分配管理、Objective-C Runtime知识的恶补,我对这次面试还是充满了信心的;面试官很准时的刚过6点就打来了电话,进行简单的问好之后就开始正式面试了:

首先是自我介绍,接着面试官直接开始问项目上的问题,先简单的介绍了一下自己的项目,接着面试官会针对他对项目中感兴趣的一些技术提问,由于我的项目涉及到本地文件通过FTP上传至服务器,所以面试官问了我一下上传的原理和过程,答:NSInputStream按字节读入本地数据流,NSOutputStream向FTP服务器写数据流,读完写完即上传完毕,再问:由于本地读写速度常常快于网络传输速度,如何保证读入的数据能够全部传至服务器?答:NSInputStream读入的字节流的方法将返回一个实质读入字节数的值,我将其记为bytesRead,而NSOutputStream向网络中写入数据的方法会返回实际写入字节数,记为bytesWritten,再设置一个变量totalBytesWritten,每次 totalBytesWritten += bytesWritten,直至bytesRead == totalBytesWritten ,证明字节流已经传输完毕。面试官又问道:为什么我没有实现断点上传? 答:NSOutputStream不支持断点上传,没有用于配置命令的方法(FTP连接建立后有一条数据链路,一条命令链路,想实现断点上传肯定需要发送Appe命令以将当前上传的文件append到指定文件末尾,按我的理解NSOutputStream默认地发送的是STOR命令,即覆盖同名文件,而这个命令NSOutputStream类并没有给出配置接口),接着面试官追问:为什么NSOutputStream支持直接向FTP写数据,而不是好像一般的文件输入输出流方法一样只支持本地的流操作,到这里我就答不上来了,接着沿着FTP这个协议的方向继续深入,问了我FTP的报文格式是什么(FTP的报文格式?其实我觉得可能他想问的是TCP?),看我对FTP没有深入理解,面试官又开始问我HTTP的相关知识:

首先问了这样一个比较出乎我意料的问题:“当你平时上网时,出现访问不了目标网页的时候,你会觉得是哪里出错了?”,我当时听到就蒙了,平时从来没有考虑过类似的问题(事后想想,他应该是在考察HTTP请求的格式,浏览器如何将网址转换成ip以及端口号,如何自动添加HTTP Method,而HTTP的响应报文又包含哪些信息等等,分析在这些过程中可能出现的错误等,有关HTTP的更多内容,可以参看:http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html ;再往低走可能就可以答一些网络丢包,服务器接收缓冲区溢出什么的东西)。接着面试官又问了我HTTP中GET和POST的区别,我的理解仅仅停留在GET一般用于请求服务器的数据,POST一般结合HTTP的请求报头和请求正文,用来修改服务器上的数据。

到这时候我觉得自己已经挂了(网络部分基本问什么不会什么),随后面试官转到了iOS以及ObjC上来,首先问了我Extension的问题(好几次面试都问到Extension,Category的问题),我答了一下extension的用途,面试官也没有继续深入(之前看ObjC Runtime的一些文章, 有提到这种技术主要是依赖于C的addMethod之类的底层的黑科技实现的,有关ObjC Runtime,推荐:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/)。接着问了一下声明property时的关键字strong的作用,并借此问了一下引用计数的问题,以及解决循环引用的方法,其实无非是合理运用weak,或者编程的时候在某一恰当时刻手动将引用一方置为nil。接着让我解释一下MVC架构,以及MVC的作用和优缺点,我借此提了一下MVVM和VIPER,这两个最近在iOS项目架构上非常火的架构,但是面试官好像也不是非常感兴趣(包括微信、腾讯,对架构只字不提),接着问了我本地数据存储的问题,Core Data我用过,但对CoreData整个框架不熟悉,它的Context,Coordinator具体的分工以及这样设计的原因我都不太清晰,接着问我有没有在项目中使用过SQLite,我答说没有,虽然会写select语句,但在项目中没有遇到运用到SQLite。

最后,问了一些算法问题,首先是说:有几百万个URL地址存在一个文件中,如何计算每个URL的出现次数?

我想了想,并没有想出比较高效的算法,以前遇到这样的问题,一般操作对象是字符或整型数,因此可以比较简单地转换成哈希的形式,但这次是URL,是长串的字符串,所以我一时并没有想到要如何设计算法。

第二个问题则是让我解释一下二叉搜索树是什么,以及树的三种基本遍历方式。

至此,电话面试基本结束,最后当然面试官给你一个问他问题的机会,我就让他说说平台开发工程师这个职位,或者说网易的移动开发部(不是移动游戏开发噢)大概是做什么的,面试官说大概是做一些网易游戏周边的手机客户端,譬如藏宝阁、充值平台等等。



二面

实话说真的没有想到自己那么差的表现也过了电话面,所以HR打电话通知我二面的时候还是非常激动的,针对一面不会的问题,我进行了一些知识上的补充,满怀期待的参加二面!

面试地点是广州网易大厦旁的一个写字楼内,那里也是网易移动开发部的工作地点;这次面试我的是两名面试官:一名主攻算法的面试官,一名iOS开发工程师。二面的开始也和电话面相似,先是自我介绍、项目介绍,接着iOS的面试官同样问到了一些FTP、NSStream的问题,我都基本解释清楚,接着面试官问我有没有用过网易新闻app,我说没有(以后面什么公司一定要提前去看他们的app,对其实现、优缺点进行思考!),接着他给我演示了一下网易新闻,其实和大部分的新闻app区别不大,主界面是一个类UITableView的列表,但面试官随后问道:你有没有使用过ListView? 我还以为面试官说错了(其实没有,他又强调了是listView,但我确实没听过),我就说我一般做类似的界面是使用UITableView,于是面试官就让我大致说说如果让我开发,我会如何开发。我就说界面上就是一个简单的UITableView,而每次app launch之后都先加载固定数目的新闻条目,譬如说50条,当用户翻页到第40条的时候,再后台下载后50条,依此类推,面试官又接着问道:是否需要针对每一条新闻创建独立的cell?我回答当然是不用,接着面试官让我解释一下cell是如何重用的,还问我屏幕上一次有5个Cell时是不是重用队列的数目就是5,我说会超出屏幕2-3个cell,以防出现cell未完全离开屏幕但又被重用导致信息混乱的情况,接着面试官问我,在Cell重用的时候,我有没有遇到过什么问题,有没有需要注意的地方,可能我遇到的情况都偏简单,所以只是很简单的从重用队列中deque,修改信息就可以得到正确的结果,但是在最近的开发中,我确实因为重用遇到了一些问题,在这里也记录一下:

在我的iPad应用程序中,可以通过一个button打开一个Popup形式的UITableView,呈现用户可上传的内容,在对内容进行选择并确认后,该TableView就只呈现准备上传的条目,并显示上传状态和进度条。为了实现这样的效果,我所采用的方法是:自定义UITableViewCell,在cell上加一个progressView,包含上传状态的label和上传进度条,在未选择上传内容时,progressViewew透明,在上传过程中,呈现progressView。
拿一个实际例子说说我遇到的问题:可上传条目共8条,则初始共有8行cell,各自独立,此时progressView全部透明。用户选择3条上传条目,则新的tableView只需呈现3条内容,重置tableView的DataSource,并且将前三行的cell的progressView置为非透明,再reload tableView的data就可以实现我想要的效果了。问题来了:由于这是一个popup形式的tableView,因此用户可以在上传时点击屏幕其他区域关闭这个tableView,想看的时候再通过button唤出tableView,当用户关闭tableView,再次打开tableView时,在cellForRowAtIndexPath方法中,我们通过deque得到的可重用cell,将不一定是那3个已经呈现出progressView的cell,而可能是最开始生成的8个cell中的任何一个!所以此处就会出现progressView未正确显示的情况。
解决方案不算麻烦,只需要在cellForRowAtIndexPath方法中添加判断,动态决定progressView透明与否即可。由此可见,deque reuseableCell固然实用,但当隶属同一个类的cell可能有不同的样式的时候,还是非常容易出现错误的!

回到面试中:接着面试官问了我atomic和nonatomic的区别,问了一下锁的机制、锁的优缺点,结合我的项目问了我一些后台数据更新时,更新UI时的线程问题(其实就是一切更新UI的操作都一定要在mainQueue中进行,我试验过不在mainQueue中更新,也可以更新到UI,但是首先是更新时间不确定,其次是,更新UI时,若主线程捕捉到用户操作,同时也要更新UI,则很可能产生访问冲突,但是如果所有UI操作都在主线程中,那么一切更新UI的逻辑都是线性执行),这些内容我基本都答得不错,不过其中有一个我答不上来的问题:对与一个Cocoa Object,若其已被置为nil,对该object调用方法的时候,会发生什么?我说跑的时候发现这个object是nil,编译器会跳过这条语句,而不会crash程序(与c++不同,c++会犹豫对空内存进行方法调用而出错),面试官接着问为什么要设计成不crash,这里我答不上来了。


接着问了一些算法问题:在一个拥有很多行数据的文件中(将数据全部读入程序开销过大,在读取完之前不知道一共有多少行数据),如何在分次读完后,等概率的从这些数据中随机抽取一行?当时我给出的方案是:每读入一百条随机抽一条,当随机抽选的行达到一百条时,再从这一百条中再随机抽一条,依此类推。这个算法写出来是一个树状结构,但是算法的问题主要在于:由于行数总数未知,不能保证树的子树深度相同,因而不能真正做到等概率,正确的做法:http://www.cnblogs.com/luxiaoxun/archive/2012/09/09/2677267.html


接着问了一个问题:长度为n的数组,给定一个m的范围,从0~~m-1开始滑动,1~~m,2~~m+1 ...... 一直滑到数组末尾,每次都求这m长度内的最大值。我给出的是一个不稳定的算法:每次记录当前最大值的下标,向后移动时,若当前最大值未被移出当前长度为m的框,则只需进行一次比较即可得到m长度的最大值,由于后移时,将当前最大值移出的概率为1/m,因此平均情况下,这个算法的效率还是比较接近O(n)的,但是面试官希望我能提供一个稳定的O(n)时间的算法,我就没有答上来了。回来之后问了一个大神同学,用的是一个叫优先队列的算法:

首先建立一个结构体,用于记录数组下标(index)和该下标对应位置的值(value),并创建一个结点为该结构体的链表。接着开始读入数据:读入第一个数据后,对之后的每个结构体:

对于读入的新结构体,从链尾开始,若结点的value小于当前新结构体value,则丢弃链尾,将链尾的前一个值作为新的链尾,再继续进行相同操作,直至当前结点value大于等于新结构体value,则将这个新结构体作为新链尾,否则,这个新结构体将成为新的链头。当当前链尾的index与链头index相差超过m时,即则将整个链表”前移“,抛弃链头,将原第二结点设为新的链头。

如此,对于链表中的每个结点来说,链表表头都是index-m+1 ~~ index这个范围内数组中的最大值。结合此算法,只需要在合适的时候访问表头,即可得到目标范围内的最大值。

为什么这个算法快呢?从之前的分析中可以看出,我之前所提出的算法不稳定,主要在于:当数组是降序时,每次目标范围的后移,我们都需要重新在长度为m的范围中进行遍历,重新寻找最大值,因此最差情况时,算法时间复杂度为O(mn),

而当前算法什么时候是最差情况呢?答案是:新读入的数据结点比当前链表中所有值都大时,需要比较至多m次(由于对于读入的新结构体,当当前链尾的index与链头index相差超过m时,则将整个链表”前移“,抛弃链头,将原第二结点设为新的链头的规定,链表最长长度为m),而当m次比较完毕后,由于每次比较都会丢弃链表中小于新结构体value的链表结点,因此在”最差情况“下,我们经过m次比较,就会将链表长度立刻缩小到1 ---- 一个非常理想的状态!

而关于这个算法的正确性,大家可以自己走几遍验证一下,而对于为什么每次对于新读入的结构体,从链尾开始,每次遇到value比自己小的链表结点,就可以将链表节点丢弃,可以这样理解:读入新结构时,若当前读入的结构体下标为currentIndex,则当前链表中所有结点的下标范围必定是currentIndex - m ~~ currentIndex - 1,所以,当新结构体的value大于 当前比较的链表节点的value时,对于当前比较的链表结点下标:nodeIndex, 新结构体必然也是 nodeIndex ~~ nodeIndex + m 中的最大值,因此nodeIndex可以被丢弃,因为它已经失去了成为最大值的可能(对于nodeIndex - m  ~~ nodeIndex,链表中前面的前面的值是最大值,对于nodeIndex ~~ nodeIndex +m ,新结构体是最大值)。


算法题问完之后,基本上就是一些闲聊,问能够上班的时间,还问我平时是如何与team中的组员沟通合作的,遇到问题的时候是怎么找答案的等等 .. 我一度觉得自己通过面试的可能性很大,只是在面试结束两天后我收到了”非常感谢,但是“的婉拒邮件。



总结

经过网易的两轮面试(我感觉我真的离offer只有一步之遥 ),我还是感觉说自己无论是算法基础,还是对于Cocoa的理解,都不够深入,譬如说NSStream的工作原理,譬如说Core Data、SQLite异同、各自优缺点,还有许多没有问到的,Core Graphics、NSOperationQueue、ObjC Runtime的消息传递、重定向、转发等,还有很多原理、底层性的东西,譬如UITableView渲染过程及这样设计的原因,UIResponder响应链等等等等。
而通过这么多次面试,我发现网络、大数据问题是所有公司都关注的问题,网络通信安全、上传下载断点,以及海量数据的存储和检索,是每家公司必问的题目。
虽然说微信、腾讯、网易面试均以失败告终,但这其中的收获不是简单几个字能够描述的,我希望自己在接下来的学习生活中真正去深入、去钻研,也希望这篇文章能给读者你一点点启发、一点点感想。



你可能感兴趣的:(网易,面试,移动开发,ios开发,技术)