Showing Posts From
面试
从校园到职场,他们是怎么过渡的
这次分享主题是「从校园到职场 -- 我的成长之路」,视频内容可以查看 B 站链接:从校园到实习再到秋招。上次的面试分享之后,阿卡拉提到关于刚毕业的学生也会有很多找工作的困扰,而且这个阶段能获取到的信息相对比较有限,如果做一期针对这个群体的分享也会是很有意义的。然后正好也接触到了两位毕业生 hzh 和豆豆,他俩一个是本科,一个是研究生,学习能力也非常强,也都在一线互联网公司实习过,所以就拉他们来做这次分享。 分享人介绍hzh 我是来自广东工业大学计算机学院的 hzh,是 23 届秋招的一名应届生。目前是在广州字节 TikTok 直播团队实习,做的是 iOS 开发,未来会去上海小红书发展。 豆豆 我叫豆豆,是曲阜师范大学,然后现在在读研三,我的研究方向是做推荐算法,有三段实习经历,目前是接了快手的 Offer。 在校篇 在学校期间个人的学习状态怎么样?计算机专业的同学在校期间卷不卷? hzh 我的学校是广州这边的一个双非,但整个学校的就业氛围还是比较强一点。我的学习状态主要是分成两个部分,一个是课程学习,另外一个是工作室的学习。 课程学习看每个人的自觉程度,像我这种比较懒散状态。其实一些课也没有比较认真地听,为了通过期末考试,才会临时突击一下,我很大一部分课程学习都是这样过来。 工作室那边的学习,主要是看自己的兴趣,我学的是 iOS 相关的一些知识,然后帮导师做一些项目。 关于周围的学习氛围,这个其实是要分环境的。我身边的氛围就是非常不卷,寝室每个人都是疯狂打游戏,参加社团活动打球,可以说是比较摆烂,但感觉这个也是一个比较正常的一个大学状态。 但是可能是因为我们班比较特殊,然后像隔壁班的话就会有比较卷的情况。我听说好像是有一个班里面有 20 个左右是绩点4.0,每一科平均都是 90 多分。 就我的宿舍来说,我们在春招之前都已经很长一段时间去刷 leetcode 了。这些主要是因为宿舍里面有个人,他比较厉害,就很喜欢刷那些困难题,我们有什么不会的也去问他。然后这种卷的话其实就是看自己想要达到什么程度,不是跟身边人去比,而是跟自己的预期去比。 豆豆 我在学校的学习状态基本上是自学,身边的同学在本科期间可能玩得比较多一些,我会有自己比较喜欢的一些方向,比较喜欢的一些项目,然后会自己去学习、实践。 本科大家都不是很卷,读研期间的话可能会因为导师分配的各种课程任务。大家需要更多的时间花在这种课业上。然后在秋招的话我身边的同学没有冲大厂的,70% 可能会选择这种国企、事业单位,20% 可能会去读博。这是研究生阶段,本科期间差不多 80% 大家都在考研,考虑直接就业的则比较少。 在校期间如何做职业选择,职业规划 hzh 我个人是有明确预期毕业要去做什么。我跟身边的人也经常去讨论过这个问题,大部分学生对这一块都是比较迷茫的。在校期间,大家都是在学校按部就班的学习,参加各种活动,然后突然间就到了大二,大三,发现自己可能需要去选择一个职业道路了,未来的一个发展也要去思考。有一些人其实甚至不愿意去想,比较晚才醒悟,比如说大三下,大四这样子,那他的准备时间,就不一定够了。 学校在这一块给的帮助也很少,应该是只有一门叫什么「大学生创业指南」,是一个水课。就是让大家了解一下创业的一些东西,让大家写一下简历,提前适应一下面试的时候该怎么回答。但这些都不能算是说去规划你未来人生要去走一个怎么样的职业道路。 我认为一个比较重要的了解职业信息的途径是,咨询直系的师兄师姐,他们大多是已经走在就业的道路上了,已经思考过这方面的选择。然后我们可以去就了解一下他们的一个选择或者听取一些建议,可能会给我一些帮助和启发。 QA:现在这个阶段,你那个专业,整体就业情况怎么样,定好工作的比例大概占多少? 其实如果不是今年的比较特殊情况,按往年来看我们整体就业率都是很高的,比如说我在的那个工作室,去年基本上都是能进大厂的。但是今年的话就是比较难,我们那个工作室,做前端和后端的,十几个人好像就只有一两个拿到了校招Offer。客户端的话可能会比例高一些,但是也不会特别高,反正今年就是非常困难。 豆豆 我们学校跟广东那边差别好大,在我们学校基本上就是做科研,做论文。 我在大学的时候做了一个学校的教务系统。是一个iOS版的,所以当时就决定要走开发这条路了。我其实明确毕业要做什么还是比较早的。 在职业选择上还可以通过加群,特别是那些有一线开发人员的群,可以获取直接的外部信息。因为在学校的话,这个信息还是比较闭塞的,身边做过开发的人也几乎没有。 就你的观察,现阶段哪个专业处于热门状态,计算机专业还是一个好专业吗,该如何做出最优的职业选择?hzh 我感觉目前互联网还是处一个比较热门的情况,但是相比于过去可能差了不少,只不过又提高了许多门槛。但是它的一些薪资待遇、福利以及未来发展都还是有一定优势的,至少还是可以排到前10%的。 关于职业选择,我总结下来就是三种:就业、考公、考研。我们学校的一个就业比例是比较高,考公考研相对少。 我身边也有一些朋友是在字节实习之后发现互联网的生活可能不太适合,然后就立马离职,然后最终选择 all in 考公这样子。所以说计算机也不是适合所有人,还需要考虑个人兴趣以及对压力的接受程度。 豆豆 我觉得现在热门行业的话有互联网这一块,互联网医院,互联网医疗就是做做核酸这一块可能比较比可能比较热门。然后行业最难的话就是公募私就是这种量化,我觉得这个行业其实门槛还是挺高的,我觉得这个应该是相对来说会难一些。职业选择就我身边同学来说的话,基本上都是以往这种稳定方向发展,计算机还是一个好的选择吧。这个我觉得如果没有一个很好的背景或者是家里有比较好的一个资源,我觉得相对其他专业来说的话,计算机还是一个比较好的选择。 考公是另一个热门选项,但成功的比例还是比较少的,一个班里去尝试的,可能最多 10% 的同学能够考上公务员。 实习篇 实习为什么选择了 iOS 呢?然后学习过程中遇到了哪些困难又如何克服的?为实习准备了哪些? hzh 选择 iOS 是因为学校有个工作室,他大一下的时候有过一个宣讲,当时听下来就是有各种方向,前端、后台、客户端还有各种机器学习。自己算是果粉,对移动端也感兴趣,然后就选择了iOS 。当时手边也有一个 Mac 电脑,然后直接就咔咔整了。 学习之中遇见的困难还是挺多的,主要来源于入门的初期阶段。要去学习计算机的思维,还有解决问题的整个思路和方法。工作室里也有一些考核,我之前会在一些简单问题上面卡很久,会怀疑自己的能力等等。但过了这个阶段就好很多饿了。 到字节实习准备的东西就是计算机基础,算法,iOS 知识点还有一些项目这几个方面。 计算机基础的话其实就是在校的课程,计算机网络、操作系统、数据结构。这些知识点更重要的是理解和记忆,最重要的还是要能够通过自己的话将这些内容讲出来,在学习之中也可以沉淀出自己的笔记,后面反复过。 关于算法,我是跟着一个公众号,「代码随想录」。里面会循序渐进,链表、二叉树、贪心、动态规划等,然后我跟着里面刷,大概刷了 500 多题。 因为当时做学校的项目,都比较简单,很难提炼说里面的难点内容。我就自己去网上看一些博客,偷学一些性能优化、滑动优化等这些内容。 然后再去学 iOS 的和 OC 的一些底层原理,我还去看了一些 objc 4 的一些源码。但我感觉这个应该没有太大作用,因为面试一个校招生其实是不会去问一些非常底层和源码的一些东西。 实习岗的竞争个人感觉还是蛮卷的,我面试了挺多公司的,最后也没通过几家。印象比较深刻的应该是拼多多在面完三轮技术面之后,在 HR 面挂了,这也侧面体现出了竞争大吧。 豆豆 我选 iOS 当时是因为给学校做了那个教务系统,就是大二,大家的时候学校没有这个iOS版教务系统,然后就自己去学习,把它给做了出来。然后大三大四也接触了一些 PHP 还有 Java。 真正选择 iOS 是在研一的上学期,当时是想了一些行业的问题,就是这个互联网不可能一直跟当时一样,那种如日中天肯定会达到一个瓶颈期,过后也会遇到困难。但就可能在客户端方面没那么卷,然后就选择了 iOS。 学习困难方面的话,肯定会有的,但很难去表述这个东西。一般是遇到问题然后去把它解决掉。其实更多的是把这些问题,答案,还有解决问题的方式给内化,就是形成了一个解决问题的能力。 实习准备的话刚刚跟 hzh 说的差不多,算法、计算机网络、操作系统,还有组成原理。还有这个平时写过的一些项目。我觉得实习的话可能竞争,对于客户端来说的没那么大,对后端和前端可能竞争会更大一些。 实习跟在校课设有什么不同?实习期间进行转正的比例大概是多少? hzh 实习跟在校课设最大的不同,我感觉可能就是一个规范的流程,跟我们就是自己去随便写点 demo 不一样。然后公司里面其实它整个流程其实还是比较长的,会有各种需求初评、细评,然后技术评审,然后排期开发,提测,一些比较规范的流程。 还有一些合作方面,自己做的一些东西其实是不用跟别人合作的。你在实习的时候是要经常遇到跟别人合作的情形,这沟通能力会有一些要求。然后实习期间最大的收获,我在面试实习的时候就是觉得自己的项目经历是比较弱,然后在实习的时候也去补了这一块知识,然后参与了整个我们公司内部的那个项目的开发,了解了其中一些知识,也做了很多需求。算是补齐了我之前的一个短板,这样子秋招竞争力会更强一点。 在字节,今年的转正应该是比较困难的,往年其听那些师兄说 是 80、90% 的转正率,今年保守估计应该是40、50%。 豆豆 实习跟课设不同的一点就是在实习过程中那个接触到的项目,它是有排期的,有时间规划。在学校自己做项目,可能就没有这么完整的时间观念了,就是哪天想起来要做一点,就花上时间去做一下。 实习期间最大的收获,我觉得更多的是能接触到更多的人。然后我会跟他们很多人都交流想法,我觉得这种人际交往是我收获比较大的一点。 转正率不好说,我是没转正就跑了。 秋招篇 今年秋招被称为最难就业季,计算机也被冠以最卷专业,大家的秋招是怎么准备的?秋招的结果怎么样,有哪些值得总结的经验?hzh 我是边实习边秋招的状态,因为实习的强度比较大,准备的时间也比较短。我大概准备一两个月,就直接投简历了。这一两个月边上班,然后下班的时间去刷一下题,准备的内容就跟上面实习准备的差不多。 秋招会偏向实际做的东西去准备多一点。秋招从结果上来讲的话其实还行,但是过程是非常痛苦的,边实习边秋招,整个人是一个非常累的状态。 我个人觉得值得总结的经验就是千万不要边实习边秋招。除非你的实习工作强度是比较轻松的,或者你对于转正有比较大的把握。 还有投简历的时机,我觉得越早越好,就算你还在一个准备状态,已经可以开始投了。因为有些公司在提前批的时候是不需要笔试的,对简历筛选也没有那么严格。到正式批的时候,会多一个笔试的流程,最终走下来可能会浪费更多的时间,还有一些公司在提前批就已经招完了。 豆豆 我准备秋招其实还算比较早的,就是在研一期间,包括实习阶段就开始为秋招准备了。当时是把计算机网络还有操作系统这些知识点去列成一个思维导图。在每次面试之前,我都会把他们有针对性地再复习一遍。我还针对牛客网上的一些面试题,把它们进行了一个归类整理,然后再用自己的语言去表述,也做了一个思维导图,把它给存起来。 然后就是去刷题,其实这个是断断续续的,把一些比较常考的那种热门题先刷一遍。今年秋招其实拿了六七家 Offer。经验的话,我觉得刷题还是要坚持下来,因为每次面试都是必须经历的,而且算法比重一般比较大,几乎每场面试都会有这种算法题,大概占 80%。如果这个题写不出来,可能就要被挂掉。 保持刷题的这种节奏,这样才能让自己的选择广一点。 成长篇 学校的教学环境,实习,个人学习,哪个对自己的成长帮助更大?为什么?从大学到职场的转变,是否经历过迷茫期,又是如何克服的? hzh 从我现在的角度来看是个人学习 > 实习环境 > 学校教学。学习这个事情永远都是自己的,无论你有没有学到或者是有没有去沉淀下来,都是你自己的事情。比如像我实习时遇到的一些问题,解决不了就会去直接问同事或者 mentor。问完之后可以直接解决,但是后续遇见相同或者类似的问题的话,还是要去问别人,这样自己的能力提升是很有限的。后来我意识到这一点,就会投入更多时间去自我学习。 我的迷茫期是对于一些工作内容强度和节奏比较不适应,感觉实习的过程中压力也比较大。然后我去跟我的一些朋友、导师或者同事讨论这个问题,发现大家在刚刚从学校到社会这个阶段,都是会有这个问题的。 这个自己要去想清楚这件事,你是想要一个怎么样的生活,期望生活是怎么一个节奏,是坚持往互联网这个方向,还是考虑其他更适合自己的事情。然后可以多去征询别人的意见,多去跟别人讨论自己的一些困惑。 但这些都是一些输入,是外部的建议,最终还是要靠自己去做归纳和总结。 豆豆 我也认为个人学习 > 实习 > 学校教学。因为在学校,更重要的是培养学生的科研能力,挖掘问题,然后再去解决这个问题。这对就业的帮助可能没那么大,对于就业,更需要的还是自我学习能力。 大学到职场的迷茫期,我其实当时也有过,就是在实习过程中不知道自己要干嘛,我觉得这个还是要多多交流,多请教别人,我一般会去在问一些职场上的朋友或者是比较熟悉的一些人。其实每个人的成长都会有这种处于十字路口的阶段,也不需要太焦虑。 对于现在计算机专业的学生有哪些建议?有哪些道理是步入社会才学到的? hzh 其实也不是建议,总结一些我的看法吧。 首先是学习,我大学的一些课程大部分应该都是一个混的状态,像数据结构,操作系统这些基础课,都是上课经常玩手机,最终期末的时候再去突击学习。这是一个非常不好的习惯。如果能够重新来的话,我会在上课时更专注一点,多听老师见解,去培养自己计算机思维还有逻辑能力,因为这些能力是会伴随自己整个职业生涯的。 对于一些比较水的课,可以不用在意老师的看法,只要不影响毕业可以随意一些。一些课外活动和社团,如果自己感兴趣的话,可以多参加几个。 最重要的是找到一个志同道合的伙伴,做一些有意义的事情。在这个过程拓宽我们的人脉,锻炼一些社交能力、合作能力、沟通能力。这些都是蛮重要的,无论是遇见了什么问题,都可以找人去沟通,去听听看他对这件事情的意见。 步入社会才学习到的道理:要有 Owner 意识,在职场上,你写的这段代码,你做的这个东西,你要对它负责,任何时候,任何相关的问题,都要做到可以找我解决的程度。 培养自我学习能力,不要遇到什么困难就去请求别人帮助,我们要摆脱对外界的依赖。 说话要有分寸一点,谨言慎行,少说多做。比如说公司的一些红线内容或者其他涉密内容,都谨慎讨论。豆豆 我觉得对于计算机专业的学生,还是要提前准备,无论是去就业或者是去读研或者是去读博,一定要提前决定,提前准备,不要到了临近报考的那个时间段,才去准备。 第二个就是学习跟实践相结合,学到了一些东西,自己也手动去操作一遍,去实践一遍,这样可能会对这个知识点掌握得会扎实一点样。 步入社会才学到的道理:更多的去理解业务,把技术当做一种工具。 团结好身边同事,跟大家要保持一个良好的相处模式。
iOS 求职寒冬?听听他们怎么说
这是一次线上分享的文字整理版。原版视频内容可以点这里查看:线上视频。 为啥会有这场分享最近在帮团队招人,像朋友圈、公众号也加了很多推广渠道,但能捞到的简历有限,即使是有简历,能过公司筛选且进入面试流程的都非常少。另一方面,现在找工作也挺难的,因为今年被裁员的人有很多,很多公司都已经不再招人了,导致竞争压力大,面试难度也跟着上升。像是裁员应该会导致更容易捞简历,更容易找到合适的人,事实却相反,这就很奇怪了,那这里面的 gap 在哪里呢? 最近正好有两个朋友都如愿找到了满意的工作,所以就想拉他们来一起分享下找工作的经历。 分享者介绍@阿卡拉 我是阿卡拉,毕业于郑州大学本科软件工程专业,2019 年 6 月进入腾讯。在腾讯主要负责的工作一直都是客户端基础平台建设,在工程效能方面不停的探索。 @JY 我是 JY,17 年毕业,是 iOS 摸鱼周报的联合编辑。我之前是在微盟的 App 基础技术部门工作、主要负责 APM 以及线上 Bug 排查等。因为公司裁员不得不重新找工作,目前是拿到了小红书的 Offer。 如何准备面试@阿卡拉 腾讯在 2022 年上半年就已经开始在慢慢的砍各种业务线,当时,对于我来说,感知比较弱,主要的原因是我一直在工程效能这一块,对于业务线的情况了解不是太多。仅仅是了解到外部的一些声音。 在5月30号,我们工程效能部门很多小组也开始在裁员,但是我们组的工具平台在最近的两年发展是挺不错的,这一次裁员没有涉及到组内任何同学。 或许是因为部门裁员的力度还是不够,接下来,开始在6月30号开始裁员,我们组涉及到的有16个人左右,最后仅剩下4个人做日常维护。 在腾讯的这三年,我发现自己的能力提升是比较快的,从进入公司的小职员,慢慢到一个大的项目的负责人,这也得益于我的 leader 和身边同事的配合。 所以,总的来说,并不是我要考虑换工作,而是公司的环境让自己被迫开始换一个工作环境。 本来在公司是 2 个月的缓冲期,所以缓冲期阶段除了处理手里的交接工作,同时也让自己休息一段时间,对这些接踵而来的消息做一个消化。然后就开始了我自己资料整理计划:整理 iOS 基础知识(八股文)【计划是 2 个周,实际花费 3 周】 计算机基础知识:主要是网络,操作系统等基础知识 iOS 的 dispatch:一直想把该模块的源码看完,但都是比较零散的,所以去看源码做了总结 iOS 的 dyld/objc:这个是 iOS 动态库加载的原理,所以需要去深入了解,objc 中包括很多的技术支持,如 autoreleasepool, 消息转发,AssociatedObject 机制。 Runloop 机制,KVO 与 KVC 机制,Block 管理机制,iOS 事件处理机制等等整理曾经看过的开源库:自己之所以喜欢去研究这些源码,是因为他们给我代码能力提升了很多,无论是从设计上和实现方案上都能有比较好的选择。【计划1周,实际花费2周】 AFNetworking:iOS 网络访问最出名的网络库,没有之一 YYCache,YYModule等由 ibireme 大神的开源组件合集 CocoaLumberjack:也是 iOS 出名的日志库,整个仓库的设计将设计模式很好的应用 OCMock:这个是一个单元测试组件,但是如果想验证自己的基础如何,这个仓库我觉得是最佳开源库,可以将 objc 的很多知识点应用进去。项目相关【计划 1 周,实际花费 2 周】 单元测试自动化:整理整个单元测试的执行流程和之前的实现方案。 质量组件化:熟悉自己开发的所有的 SDK ,包括语音 SDK,大文件上传 SDK,屏幕录制SDK 等等。 变异测试:我主要负责的一个平台。变异测试的整理设计方案与之后的优化方向;梳理 Objective C 热重载执行方案与设计;LLVM 对 Objective C 语言的语法树分析并做各种能力;LLVM 的 pass 化服务梳理。算法【每天早上刷算法】 leetcode上刷剑指 offer 第一版和第二版,然后刷 leetcode 热题 100(热题100要看着题目就马上写出来的那种) 回忆算法:主要是算法小抄和这个作者的网站:https://labuladong.github.io/algo/总结下来,因为本身自己也想去做一个总结,所以整体上花了大概 1 个半月的时间,所以基本都是在腾讯的缓冲期每天静下心来一步一步的梳理。其中准备算法耗时最多吧,前后刷了差不多 300 题。 @JY 我换工作的契机是因为公司裁员。在国庆节前两天,被通知 Last day,不需要交接。所以我是从国庆节前那几天才开始准备的。我主要准备的是简历,这里我的想法是需要明确自己想要去什么样的公司,然后根据公司要求和自己擅长的点,可以准备多份简历。简历里最主要的内容就是自己的项目经历,项目最好能体现出具体的优化指标。我面了很多家公司,都会被问到最后优化后的指标。 其次就是八股文,因为我之前有记一些笔记,再结合网上别人总结的一些内容,每天早晚都会大概看一遍。 算法这块,我在 Leetcode上开了一个会员,这样能看到热门公司题库,我把想要面的公司的算法都刷了一遍,有些题目可能不止一遍。 这里最耗时的就是算法这块了,有些 Hard 级别的算法题会很难写出来,但很多时候即使写不出来,有大概思路也行。所以如果算法比较弱的话,需要多刷一刷找找题感,才能更好的应对各种复杂的问题。 如何写简历 简历是面试过程中的敲门砖,只有简历被捞到,才会有后面的面试过程,所以它的优先级是很高的。很多人都有分析过简历应该怎么写,不应该怎么写,但我感觉讲再多都不如亲自去看一个优秀的简历是如何写的。下面是阿卡拉和JY两个人的简历,部分内容做了脱敏和抹除。 @阿卡拉@JY如何获取更多应聘机会@阿卡拉 招聘渠道的话,小公司直接去 Boss 上面找,大公司找内推。我对下个阶段其实考虑挺多的,结合自己的诉求,我主要有三个方面的要求: 1、大公司:字节,阿里,腾讯这一类的大公司,只要招聘的业务不是太偏就可以。业务上我希望继续在工程效能方向继续深挖。 2、有前景的公司,看中公司的进步和发展,看中公司的业务 3、加班压力小的公司 @JY 我基本也都是通过 Boss 或者内推方式,我会让朋友来面试我,培养自己的面感,这个过程也会一点点增加自信。因为现在大环境不好,很多大公司的 HC 都是很宝贵的,最好是准备很充分了再去面。 对于意向工作,我更关注的是该组是负责哪一块,是否是核心部门,公司发展前景如何。最重要的还有自身的发展方向和工作内容是否匹配。 @zhangferry 找工作前需要考虑的这几个问题,第三个是非常重要的。不管因为什么原因需要换工作,我们都应该把换工作这个节点当做整个职业规划的下一个起点。之前的工作有哪些不好,踩了哪些坑,我要避免,我了解到哪些知识,对哪个领域更感兴趣,可以往那个方向多去靠。 @东野浪子 挑选工作时要提前想好自己的诉求是什么,更高的薪资?更看好的行业?想从业务开发切换为技术开发?甚至就是想工作的轻松点,这都是没问题的。把这些诉求按照优先级进行排序,赋予不同的权重值,然后再分门别类的把你所能投的一些公司或者职位按照权重进行打分,分数最高的那个就是最适合你的岗位。 很多时候都不可能一个 Offer 覆盖你所有诉求,这时就可以利用这个方法论进行选择了。 如何进行面试@阿卡拉 我投的简历还是蛮多的,大概有十几家,最终进入面试环节的有七到八家,但是真正满足我那几个诉求的只有两到三家。我觉得寒冬的感受还是挺明显的,阿里、腾讯、大江、虾皮这些都已经不怎么招人了,因此可选的公司范围没有那么广。 技术面中基础知识,也就是我们常说的八股文还是比较重要的,也经常被问到。像是 Runloop、KVC/KVO、Objc、Block、GCD、Autorelease 这些技术点要非常清楚,每个点都需要深入。我自己在整理文档的时候基本也是从这几个方向展开的。 总共走完流程的有 4 家,有两家因为薪资不满意,还有一家其实挺想去的,但感觉他的发展前景不太好,最终就选择了字节。不说别的,就薪资这一块,字节还是棒棒的。 最终通过面试感觉还是因为自己准备比较充足,比较全面,哪些相关的知识点,我会把里面的方法论也都提炼出来。比如说 Autorelease 或者 Cache 相关内容,我会考虑如何把他们应用到自己的项目中去。只有真真实实地去考虑了这个东西,而且了解它的各方面内容,面试官问你的时候你就会感觉很轻松。 关于这些知识点的学习,我还会分有很大比重去看开源库。说实话我觉得腾讯这边,很多业务代码拿出来,大家都不想去看的,这些东西不会给人带来提升。而开源库是经过社区检验的,都是质量非常高的内容,仔细研究,更能学到东西。 字节给我面试体验就是他们的面试深度,整个面试过程就是带着你一步步往深了聊,但这随之而来的就是难度提升,这也是我为啥会把面字节的面试安排放到最后。 @JY 我投的简历不多,都是自己比较想去的才会投,除了两三家没有面试,其他都有。寒冬的感觉还是很明显的,因为我很多朋友去年找工作的时候,HC 很多,难度也不算高。今年因为有很多公司在裁员,有一些公司也锁了 HC,市面上人确实比较多,所以每次面试都应该把握好机会。 我在技术面上感觉稍微有些不同,一面遇到问八股的比较多,后面几面基本都是围绕项目在问。最经常被问到的就是你们团队为什么要做这个?为什么要采用这种方案?过程中遇到了哪些问题?你是如何去解决的?其次就是结果,带来了哪些收益,我感觉大厂的面试很像项目讨论会,双方就一个功能点不断地探讨,技术深度慢慢深化,这样整体面下来也会感觉比较舒服。 有一些没有走完的面试流程,可能是复习的时候没有准备到位,也有一些是平常工作中很少使用到的,因为答的不太好就被 pass 了。 面试通过的原因,我觉得主要还是平常的积累,平常做了什么项目,需要经常复盘总结。把遇到的问题以及收益都记录下来,组织自己的语言表达出来。 小红书整体的面试还是比价舒适的。整个面试都是从浅到深的,基本没有问什么八股,都是围绕项目开展的。 面试回顾及总结@阿卡拉 面试中踩过的坑,第一不要盲目面试,盲目面试会发现其实很多岗位都是不符合自己预期的,这些过程会有很多无效沟通,浪费了挺多时间。第二面试一定要做好总结,像是每次面试被问到的问题,如果我不知道或者感觉答的不好,下面我会再去总结和调整,短板补充是一个比较重要的提升过程。 面试对我还有一点改变是,因为在腾讯这三年都没有考虑过面试这件事,一直是比较佛系的状态,这次面试经历基本是反向的去提升自己的能力。因为面试的缘故我有充足的时间去系统的学习之前遗漏的知识点,并为下个阶段做一个规划。 如果面试不顺的话,就当做跟这家公司没有缘分吧。选择是双向的,我经常跟一些同事开玩笑说,在腾讯小马哥拥有我是他的幸福,而不是我的幸福。现在在字节也一样,他拥有我是他的幸福,但反过来对我来说,也促成了我的幸福。 关于选择这块,因为在腾讯时组内相处比较和谐,我也更倾向于氛围和谐的团队,工作和生活都要做最真实的自己。另外我觉得大家还是要选一个自己感兴趣的东西,一直坚持做下去,丰富自己的生活。 @JY 踩过的坑是,简历一定要写自己熟悉的(至少能够展开来讲的),不要写一些不是很熟悉,或者临时抱佛脚的东西,一旦被面试官问到,会很影响在面试官心中的印象。面试的时候,如果可以的话,就把面试官引到自己的舒适区,因为每个人技术侧重不同,这样的话更能体现你的优势,这样面试成功的几率也会更大。 面试过程做的特别对的事情是,我每次面试都会录音,面试完会听着录音进行复盘。有些人可能觉得自己面完感觉很不错,但有时候自己再听一遍时会发现自己有很多可以改进的地方。 做的不错的地方是调整自己心态吧,刚开始投简历的时候一个面试都没有,自己心态受到了一些影响,因为是被裁的,心情有些失落。然后我趁国庆节,前三天,出去旅游了一下,回来就放松下来了,再去调整心情,安心准备复习,这样效果会好很多。 换工作的话,主要是看公司能否满足自身发展,因为我们换一份工作一般都是期望工作一年以上的(大家肯定不希望自己简历花掉),如果这段经历跟自己不匹配,那整体工作状态可能会不开心。另外像阿卡拉说的,团队氛围,可能进入团队之前会比较难判断出团队氛围好不好,一般二面或者三面就是你的领导,这时你可以看你自己的感觉和面试官契不契合,也可以先以是否符合自身发展为主。我接触的程序员都是蛮好相处的。 如何筛选候选人-面试官角度 @东野浪子我简单自我介绍下吧,我是 15 年毕业,16 年开始做面试官,我刚看了一下面试记录,这些年来总共面试了大概有 570 多位候选人。我现在是在字节的客户端架构部门下的 APM 部门,带客户端团队。 好简历体现在哪里 先说第一点,作为面试官视角,第一眼关注的是你的硬性指标:学校、工作经历、技能匹配程度。重点大学和大厂经验会是加分项,另外还会去看你的技能匹配程度,如果你之前是做基础技术的,你应聘的也是基础技术,这就属于比较匹配。但是如果你的这些条件都不算出彩的话,就需要用项目经历去弥补了,这是第二点我比较关注的内容。 对于项目经历的整理和描述要条理清晰,可以使用 STAR 法则,我做这个东西的背景是什么,基于这个背景,你的目标是什么,你通过什么行动达成了什么样的目标,最终一定要有结果,而且最好是可以用数据量化的结果。项目中一定要有重点,一些重要的结果和数据,可以使用加粗的形式去强化。 简历也可以提现个人风格,我看过一些简历,一看就知道这个人非常的极客。因为它里面有非常多的专业术语,能明显感觉出他对技术的理解非常深刻。如果你面试的是基础技术部门,那就应该在简历里多写一些你在底层技术的探索,或者你在技术优化上做过的一些事情。这些都是简历相关的内容。 好的应聘者需要具备哪些特征 对面试阶段候选人的表现,最重要的就是基础一定要扎实,基础决定了你以后的发展潜力,甚至决定你在职场中的天花板。这里基础又包括很多方面,比如计算机基础、操作系统原理、网络等等,这些东西一点要多查漏补缺,不要有明显硬伤。这一点如果表现不好,那肯定是过不了面试的。 再就是数据结构和算法,有一点需要澄清下就是不要畏惧算法,至少在我们团队算法考察最多就是 medium 级别,不会在算法层面太难为大家。很多同学面试挂掉,如果把结果归结于算法没写好,是需要纠正的。因为算法是作为一个侧面角度考察的,他不是直接决定面试结果,如果你前面回答很出色,就算是算法答的不太好,还是会过的。 第二点是领域知识,做iOS开发对苹果开发相关的比如UI动画、多线程、内存管理、性能稳定性等都要有一定的了解。如果是做底层技术,你还需要掌握 dyld、runtime 相关的底层原理和常见的优化手段。很多人会把这说成八股文,我感觉更合适的是把它作为对你技能点的考察。这类问题通常的考察方式是从一个非常简单的问题切入,然后抽丝剥茧,逐渐加大难度,看你能够顺利的通过多少关。 第三点属于项目亮点,比如一个很常见的面试问题:你做过最满意的一个项目是什么,项目里有哪些亮点内容?亮点内容应该体现出一定的复杂度,你跟其他人的方案有什么差别,使用了哪些设计能力,涉及哪些技术选型,如何协作和执行的。同时也不能只谈难度不谈收益,它达到了哪些业务价值,你在这里承担了什么角色,是否是跨部门项目,这些方面都可以作为亮点去讲。业务价值描述时可以通过方案前后的对比来体现,之前指标是多少,使用该方案之后达到了多少,这种展现是非常直观的,更能引起面试官的共鸣,这个技巧同样也可以用到汇报工作和晋升答辩中。 软素质也会作为面试考察中比较重要的一点,因为应聘者最终会成为我的同事或者下属,我会关注自驱力,你对技术有好奇心,会主动地探索。然后是规划能力、沟通协作能力、迁移复用能力等。这些点比较多,如果无法通过一次沟通完全展示出来,那可以根据自己的需要选择一两个方面有体现也行。比如你想转技术栈或跨专业,那你就要说服面试官为什么之前积累的经验可以继续复用。 一些面试小技巧对于没有把握的问题,不要不懂装懂,面试官会很讨厌不懂装懂的情况,这会严重扣分。还有就是对于你没有把握的问题,最好不要一言不发或者直接说我不了解。如果遇到了这类问题可以尝试从自己熟悉的角度切入,或者给出一个简单版本的答案。 第二个技巧是不要在面试过程中与面试官发生冲突,有可能他老问八股文,你感觉面试官在刁难你,或者你觉得面试官很菜,都不要起冲突。因为一般面试都会有面评记录,如果有冲突,这个问题被记录,后面会影响你再面其他部门,得不偿失。 还有一个技巧就是多复盘总结,很多人会有一个面试误区,感觉面试没发挥好,就不去想他了,而产生一些抵触情绪,这其实对自己提升是无益的。一定要有复盘,也可以跟小伙伴一起讨论自己的面试情况,避免自己有认知偏差。因为有时候我们感觉自己回答的很好,但最终却没面上,有一种可能就是你回答没有达到点子上,或者回答的比较浅显,跟面试官期待有偏差。 @zhangferry 对于软素质,我感觉它体现了一个隐藏因素--兴趣,有了兴趣做为驱动力,才会在没人约束的情况下自发去探索,去做到更好。其实很多人对编程,对开发这份工作是不感兴趣的,只是当做挣钱的一种手段,这没什么不好,反过来完全出于兴趣,敲代码时快乐的不得了的人也很少。但不管怎样的状态,工作中还是应该尝试去找些让自己兴奋起来的点,比如我解决了什么技术问题,老板给我奖励;我写了一篇文章,大家都给我点赞;我在团队做了什么分享,大家都来向我请教相关问题等等。这些跟技术联通的点和个人成就感结合起来,能更好的激发创造性。上班很辛苦,但不应该只有辛苦,毕竟这份工作我们可能还要在做几年或者十几年时间,一直都感觉苦肯定是很难精进的。这一点大家也可以结合自身情况多想一想啊。 面试结果由什么决定@zhangferry:本次分享的目的是给大家提供一些值得借鉴的面试方法,其实刚才也聊了很多面试之外的事,包括如何挑选岗位,如果在工作中补足自己学历或者经历的短板。回到一个比较重要的问题上,一次面试,我有多大概率能够成功。我认为,日常工作经验和总结占 60%,面试准备占 30%,面试相关的技巧和运气占剩余的 10%。对,有时候运气也会决定面试的结果。 如果是处于工作状态,近期并没有打算换工作的同学,可以在工作期间多考虑下如果想要优化自己的简历我应该做好哪方面的事情。如果是正在准备面试的同学,工作内容已经没法改变了,但还可以把时间多放到面试准备上来,充分的准备也能提供我们通过的概率。 还有一点想要强调的是,有不少人会在准备不充分的情况下去面试,这个其实是挺不好的。因为现在面试机会真的不多,每次面试都很宝贵,另外以字节为例,不超过半年的面试,面评记录会一直留着。如果你之前面试效果不好,那后面你准备充分了,换个部门去面,就很大概率会因为面评不好直接被刷下来。我遇到过好几次这种情况,有些人会说是不知怎么简历就被 HR 捞到了,然后电话联系要约面试,自己稀里糊涂就参加了。如果遇到这种情况,一定要以自己实际准备的情况来决定是否要约面试。 QA 遇到面试就紧张,脑袋空白,面试过程不会吹怎么办? @东野浪子:紧张背后的原因大多数情况就是准备不充分,导致不自信。我们应该从这个方面去解决紧张的问题。充分的准备就是上面说的一些基础知识,领域知识,要非常熟悉;涉及项目经历这块则可能需要更长期的准备。比如日常工作中主动承担一些有挑战性的业务,对一些技术问题进行深入探索。这些点都是面试过程中可以让自己出彩的地方,所以工作的一部分也是在为自己的简历打工。 不会吹可能侧面想说的是不会表达的意思,会有些人面试很强,但是实战一般。比如一个人实力是 60 分,他能说成 80,而有的人实力是 80 分,因为不会表达,让人听起来只有60 分。这样的话,就需要培养一下自己的演讲或者表达能力了。这种能力在日常工作工作、汇报或者晋升答辩时都非常重要,这个能力本身也属于面试考察的一部分。所以平时可以刻意得去培养锻炼一下。 写了两年 UI 怎么办,就做了基本的页面开发,感觉项目经历很空洞 @东野浪子:如果一直重复一种开发模式,那确实不太好。你可能很难改变开发的需求,但是使用什么样的技术却是没人会限制你的。即使是开发UI,从 Frame、Autolayout、Masonry 再到 SwiftUI,包括 RN 和 Flutter,这一步步流程都会涉及很多 UI 内容,不同框架的出现是解决什么问题的,怎么才能更适合当前工作且提高效率,这些都是自己可以延伸思考的事情。另一方面,你做UI会不会涉及到流畅性问题、会不会涉及一些Crash,那这些问题都是可以再延伸很多内容的。所以即使复杂度不高的项目,也不会说不涉及复杂的问题,就看你有没有主动给自己增加难度,去做有调整性的那部分。没有大厂经历也类似,没人会限制你学习大厂的技术,而且现在有很多途径去获取他们的技术方案,自己可以多学习,多思考,然后实践到自己的项目中。我们是非常欢迎这种有自驱力和主动性的同学的。 如何寻找自己的第二增长曲线 @东野浪子:第二增长曲线,往大了说比如你现在是一个程序员,这是你的主页,第二曲线就可能是一个副业。比如炒股、做新媒体、公众号、拍抖音等等。往小了说,比如你现在是 iOS 开发,你只会 OC,那 Swift 以后可能会是一个趋势,你精通 Swift,那这就会成为你的第二曲线。你如你现在是做UI页面,做纯业务,那是不是可以去了解一些底层技术,像是 APM、DevOps、编译链接、端智能等,或者往跨端、全栈方面考虑,这些都是可以成为第二曲线的。第二曲线的东西一定要跟你的兴趣匹配,你要对他有热情,即使没有收益也能够坚持下去那种。 请问“横向协作能力,迁移复用能力”,这两个软素质一般会如何考察和作答? 是否是先回答自己的方法论,然后举例来说明? @东野浪子:横向协作能力,一般会在大型一些的跨团队合作项目中会有。比如我们是做客户端监控的,有些东西就会是一个双端方案。比如客户端上报流量,太高的时候后端会触发一些容灾或者限流,这个跟每个端的特性都没有关系。那如果作为这个项目的 Owner,你怎么把事情推进下去,怎么保证项目周期可控。他会涉及一些跨团队的沟通协作、目标对齐还有项目管理,这里体现的就是横向协作能力。 迁移复用能力属于比如你在一个领域 APM 做了很久,那现在需要专做音视频,相对于 APP 来说,怎么对音视频进行性能优化呢?这里有很多方法论其实是可以迁移出来再次适用的,像是线上容灾、问题响应、防裂化等处理方式都有一定的相似性。 内推信息 因为是为了信息互通,各位分享者团队有都有招聘需求,这些是内推信息:内推人(微信) 岗位描述 招聘需求zhangferry(zhangferry) [北/上/杭]抖音iOS基础技术-研发效能 有静态分析、LLVM、单元测试、自动测试框架、架构、工程效率、全栈开发等经验者优先,有业务背景但对技术有深度追求者优先。东野浪子(569087164) [北京]字节跳动 APM客户端-iOS 负责字节跳动所有移动端产品的性能优化和问题排查。北京还有 3 个 HC阿卡拉(myself439664) [深]抖音iOS基础技术-自动化测试 提升抖音等产品的研发代码质量,降低测试成本,参与自动化测试服务建设。JY(q491964334) [北/上]小红书-客户端基础架构 有过千万 DAU APP 的基础架构方面的开发经验,具有钻研 iOS 系统底层实现与优化的能力对于内推岗位,我也维护了一个公共链接,内推信息汇总,大家如果有招人需求可以到这里来填写。老司机技术周报很早也在维护一份内推信息,会更全一些,可以点击这个查看:iOS靠谱内推专题
iOS面试总结(2020年6月)参考答案
- 17 Aug, 2020
上个月发了这篇iOS面试总结(2020年6月),没想到挺受大家欢迎,本来是没打算为它写答案,但有几个人建议我最好出一篇答案,提的人多了我就答应了下来。因为最近比较忙,断断续续总算补完了,就有了这篇文章,希望它对大家还有用处。这些都属于参考答案,如果大家感觉有不对不准确的地方也欢迎指出,我会及时更新。关于面试题 打个比方,如果把找工作理解成考大学,面试就是高考,市面上的“真题”就是模拟试卷。我们会很容易倾向于在面试前寻找对应公司的面试“真题”,重点准备,期待“押题”成功。但实际上,即使面试同一家公司,它会有不同部门,不同业务线,不同面试官,即使遇到同一面试官,他也不一定就每次考察完全一样的内容。想想高考中那些考的好的同学,他们肯定不是靠“押题”才能取得好成绩吧,他们大多靠的是平常积累及对知识点灵活掌握,那面试也一样啊。执着于搜题,把面试题当做重点进行“复习”,还不如自己划出“考纲”,各个知识点逐一检查掌握情况,复习的更全面呢。 我对于面试题的看法一直是相对保守的,这类文章一般只是内容搬运,它会存在一些偏差和误读,最重要的那就是几道题往那一扔,并没有产出有价值的东西。这也是为什么我上篇面试总结,会加了一些面试技巧,整理面试题时,也没提他们是出自哪家公司,就是不希望大家把题目区别看待。 说了这些并不是说面试题没用啊,而是希望大家不要迷信面试题,更多地去关注那些有质量有深度的技术文章。面试考核的是知识点而不是具体的某些题目,面试题的作用在于,衡量我们的知识掌握情况,便于我们查漏补缺,越说越像是针对一次“考试”了😄。 总结不易,希望这份参考答案能对你有所帮助,如果想持续关注我,欢迎订阅微信公众号:iOS成长之路。 面试题及参考答案 Swift 1、Swift中struct和class有什么区别? struct是值引用,更轻量,存放于栈区,class是类型引用,存放于堆区。struct无法继承,class可继承。 2、Swift中的方法调用有哪些形式? 答:直接派发、函数表派发、消息机制派发。派发方式受声明位置,引用类型,特定行为的影响。为什么Swift有这么多派发形式?为了效率。 参考文章:深入理解 Swift 派发机制3、Swift和OC有什么区别? Swift和OC的区别有很多,这里简要总结这几条:Swift Objective-C语言特性 静态语言,更加安全 动态语言,不那么安全语法 更精简 冗长命名空间 有 无方法调用 直接调用,函数表调用,消息转发 消息转发泛型/元组/高阶函数 有 无语言效率 性能更高,速度更快 略低文件特性 .swift 单文件 .h/.m包含头文件编程特性 可以更好的实现函数式编程/响应式编程 面向对象编程4、从OC向Swift迁移的时候遇到过什么问题? 可以参考这篇文章:OC项目转Swift指南 里的混编注意事项。 5、怎么理解面向协议编程? 面向对象是以对象的视角观察整体结构,万物皆为对象。 面向协议则是用协议的方式组织各个类的关系,Swift底层几乎所有类都构建在协议之上。 面向协议能够解决面向对象的菱形继承,横切关注点和动态派发的安全性等问题。 参考喵神的面向协议编程与 Cocoa 的邂逅 (上) OC语法 1、Block是如何实现的?Block对应的数据结构是什么样子的?__block的作用是什么?它对应的数据结构又是什么样子的? block本质是一个对象,底层用struct实现。 数据结构如下: struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); };struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };isa 指针,所有对象都有该指针,用于实现对象相关的功能。flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。reserved,保留变量。invoke,函数指针,指向具体的 block 实现的函数调用地址。descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。__block的作用是让block可以捕获该变量,捕获之后的变量会进入到block内部,通过反编译的代码我们可以看到该对象是这样的: struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int val; //变量名 };对于block的深入了解,可以参考《Objective-C高级编程》第二章或者唐巧的这篇谈Objective-C block的实现 2、GCD中的Block是在堆上还是栈上? 堆上。可以通过block的isa指针确认。 3、NSCoding协议是干什么用的? 一种编码协议,归档时和解档时需要依赖该协议定义的编码和解码方法。Foundation和Cocoa Touch中的大部分类都遵循了这个协议,一般被NSKeyedArchiver做自定义对象持久化时使用。 4、KVO的实现原理 利用Runtime生成一个中间对象,让原对象的isa指针指向它,然后重写setter方法,插入willChangeValueForKey和didChangeValueForKey方法。当属性变化时会调用,会调用这两个方法通知到外界属性变化。 5、NSOperation有哪些特性,比着GCD有哪些优点,它有哪些API? NSOperation是对GCD的封装,具有面向对象的特点,可以更方便的进行封装,可以设置依赖关系。 API可以查看NSOperation文档。 6、NSNotificaiton是同步还是异步的,如果发通知时在子线程,接收在哪个线程? 同步。子线程。 UI 1、事件响应链是如何传递的? 手势的点击会发生两个重要事情,事件传递和事件响应。 事件传递:从UIApplication开始,到window,再逐步往下层(子视图)找,直到找到最深层的子视图,其为first responder。用到的判断方法是pointInside:withEvent和hitTest:withEvent。 事件响应:从识别到的视图(first responder)开始验证能否响应事件,如果不能就交给其上层(父视图)视图,如果能相应将不再往下传递,如果直到找到UIApplication层还没有相应,那就忽略盖茨点击。用到的判断方法是touchesBegan:withEvent、touchesMoved:withEvent等。 这两个过程大致的相反的。 2、什么是异步渲染? 异步渲染就是在子线程进行绘制,然后拿到主线程显示。 UIView的显示是通过CALayer实现的,CALayer的显示则是通过contents进行的。异步渲染的实现原理是当我们改变UIView的frame时,会调用layer的setNeedsDisplay,然后调用layer的display方法。我们不能在非主线程将内容绘制到layer的context上,但我们单独开一个子线程通过CGBitmapContextCreateImage()绘制内容,绘制完成之后切回主线程,将内容赋值到contents上。 这个步骤可以参照YYText中YYTextAsyncLayer.m文件中的实现方式。 3、layoutsubviews是在什么时机调用的?init初始化不会触发。addSubview时。设置frame且前后值变化,frame为zero且不添加到指定视图不会触发。旋转Screen会触发父视图的layoutSubviews。滚动UIScrollView引起View重新布局时会触发layoutSubviews。4、一张图片的展示经历了哪些步骤? 这个可以参考我之前写的一篇文章iOS开发图片格式选择 中的前半部分内容。 5、什么是离屏渲染,什么情况会导致离屏渲染? 如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。以阴影为例,为什么它会导致离屏渲染。因为GPU的渲染是遵循“画家算法”,一层一层绘制的,但阴影很特殊,它需要全部内容绘制完成,再根据外轮廓进行绘制。这就导致了,阴影这一层要一直占据一块内存区域,这就导致了离屏渲染。 类似导致离屏渲染的情况还有:cornerRadius+clipsToBounds group opacity 组透明度 mask 遮罩 UIBlurEffect 毛玻璃效果有一篇文章详细的讨论了这些情况:关于iOS离屏渲染的深入研究 6、CoreAnimation这个框架的作用什么,它跟UIKit的关系是什么? CoreAnimation虽然直译是核心动画,但它其实是一个图像渲染框架,动画实现只是它的一部分功能。看这张图我们可以知道,它是UIKit和AppKit的底层实现,位于Metal、Core Graphics和GPU之上之上。 苹果官方文档:About Core Animation 引用计数 1、ARC方案的原理是什么?它是在什么时候做的隐式添加release操作? ARC(Automatic Reference Cunting)自动引用计数,意即通过LLVM编译器自动管理对应的引用计数状态。ARC开启时无需再次键入retain或者release代码。 它是在编译阶段添加retain或者release代码的。 2、循环引用有哪些场景,如何避免? 循环引用及两个及以上对象出现引用环,导致对象无法释放的情况。一般在block,delegate,NSTimer时容易出现这个问题。 解决方案就是让环的其中一环节实现弱引用。 3、为什么当我们在使用block时外面是weak 声明一个weakSelf,还要在block内部使用strong再持有一下? block外界声明weak是为了实现block对对象的弱持有,而里面的作用是为了保证在进到block时不会发生释放。 4、Autoreleasepool是实现机制是什么?它是什么时候释放内部的对象的?它内部的数据结构是什么样的?当我提到哨兵对象时,会继续问哨兵对象的作用是什么,为什么要设计它? Autoreleasepool的原理是一个双向列表,它会对加入其中的对象实现延迟释放。当Autoreleasepool调用drain方法时会释放内部标记为autorelease的对象。 class AutoreleasePoolPage { magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; };哨兵对象类似一个指针,指向自动释放池的栈顶位置,它的作用就是用于标记当前自动释放池需要释放内部对象时,释放到那个地方结束,每次入栈时它用于确定添加的位置,然后再次移动到栈顶。 关于自动释放池的底层探究可以看draveness的这篇自动释放池的前世今生 ---- 深入解析 autoreleasepool 5、哪些对象会放入到Autoreleasepool中? 有两种情况生成的对象会加入到autoreleasepool中:非alloc/new/copy/mutablecopy 开始的方式初始化时。 id的指针或对象的指针在没有显示指定时引用计数带来的一次讨论 6、weak的实现原理是什么?当引用对象销毁是它是如何管理内部的Hash表的?(这里要参阅weak源码) runTime会把对weak修饰的对象放到一个全局的哈希表中,用weak修饰的对象的内存地址为key,weak指针为值,在对象进行销毁时,用通过自身地址去哈希表中查找到所有指向此对象的weak指针,并把所有的weak指针置位nil。 Runtime 1、消息发送的流程是怎样的? OC中的方法调用会转化成给对象发送消息,发送消息会调用这个方法: objc_msgSend(receiver, @selector(message))该过程有以下关键步骤:先确定调用方法的类已经都加载完毕,如果没加载完毕的话进行加载从cache中查找方法cache中没有找到对应的方法,则到方法列表中查,查到则缓存如果本类中查询到没有结果,则遍历所有父类重复上面的查找过程,直到NSObject2、关联对象时什么情况下会导致内存泄露? 关联对象可以理解就是持有了一个对象,如果是retain等方式的持有,而该对象也持有了本类,那就是导致了循环引用。 3、消息转发的流程是什么? 消息转发是发生在接收者(receiver)没有找到对应的方法(method)的时候,该步骤有如下几个关键步骤:消息转发的时候,如果是实例方法会走resolveInstanceMethod:,如果是类方法会走resolveClassMethod:,它们的返回值都是Bool,需要我们确定是否进行转发。 如果第一步返回YES,确定转发就会进到下个方法forwardingTargetForSelector,这个方法需要我们指定一个被用receiver。 methodSignatureForSelector用于指定方法签名,forwardInvocation用于处理Invocation,进行完整转发。 如果消息转发也没有处理即为无法处理,会调用doesNotRecognizeSelector,引发崩溃。更多了解可以参考iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承) 4、category能否添加属性,为什么?能否添加实例变量,为什么? 可以添加属性,这里的属性指@property,但跟类里的@property又不一样。正常的@property为:实例变量Ivar + Setter + Getter 方法,分类里的@property这三者都没有,需要我们手动实现。 分类是运行时被编译的,这时类的结构已经固定了,所以我们无法添加实例变量。 对于分类自定义Setter和Getter方法,我们可以通过关联对象(Associated Object)进行实现。 5、元类的作用是什么? 元类的作用是存储类方法,同时它也是为了让OC的类结构能够形成闭环。 对于为甚设计元类有以下原因;在OC的世界里一切皆对象(借鉴于Smalltalk),metaclass的设计就是要为满足这一点。在OC中Class也是一种对象,它对应的类就是metaclass,metaclass也是一种对象,它的类是root metaclass,在往上根元类(root metaclass)指向自己,形成了一个闭环,一个完备的设计。如果不要metaclass可不可以?也是可以的,在objc_class再加一个类方法指针。但是这样的设计会将消息传递的过程复杂化,所以为了消息传递流程的复用,为了一切皆对象的思想,就有了metaclass。 关于这一话题的深入讨论可以参考这两篇文章: 为什么要存在MetaClass 为什么要设计metaclass 6、类方法是存储到什么地方的?类属性呢? 类方法和类属性都是存储到元类中的。 类属性在Swift用的多些,OC中很少有人用到,但其实它也是有的,写法如下: @interface Person : NSObject // 在属性类别中加上class @property (class, nonatomic, copy) NSString *name; @end // 调用方式 NSString *temp = Person.name;需要注意的是跟实例属性不一样,类属性不会自动生成实例变量和setter,getter方法,需要我们手动实现。具体实现方法可以参考这个文章:Objective-C Class Properties 7、讲几个runtime的应用场景hook系统方法进行方法交换。 了解一个类(闭源)的私有属性和方法。 关联对象,实现添加分类属性的功能。 修改isa指针,自定义KVO。Runloop 1、讲一下对Runloop的理解? Runloop就是一个运行循环,它保证了在没有任务的时候线程不退出,有任务的时候即使响应。Runloop跟线程,事件响应,手势识别,页面更新,定时器都有着紧密联系。 深入了解推荐ibireme的这篇深入理解RunLoop 2、可以用Runloop实现什么功能?检测卡顿 线程包活 性能优化,将一些耗时操作放到runloop wait的情况处理。性能优化 1、对TableView进行性能优化有哪些方式?缓存高度 异步渲染 减少离屏渲染2、Xcode的Instruments都有哪些调试的工具?Activity Monitor(活动监视器):监控进程的CPU、内存、磁盘、网络使用情况。是程序在手机 运行真正占用内存大小Allocations(内存分配):跟踪过程的匿名虚拟内存和堆的对象提供类名和可选保留/释放历史Core Animation(图形性能):显示程序显卡性能以及CPU使用情况Core Data:跟踪Core Data文件系统活动Energy Log:耗电量监控File Activity:检测文件创建、移动、变化、删除等Leaks(泄漏):一般的措施内存使用情况,检查泄漏的内存,并提供了所有活动的分配和泄漏模块的类对象分配统计信息以及内存地址历史记录Network:用链接工具分析你的程序如何使用TCP/IP和UDP/IP链接System Usage:记录关于文件读写,sockets,I/O系统活动,输入输出Time Profiler(时间探查):方法执行耗时分析Zombies:测量一般的内存使用,专注于检测过度释放的野指针对象。也提供对象分配统计以及主动分配的内存地址历史3、讲一下你做过的性能优化的事情。 这个根据自己情况来说吧。 4、如何检测卡顿,都有哪些方法?FPS,通过CADisplayLink计算1s内刷新次数,也可以利用Instruments里的Core Animation。 利用Runloop,实时计算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 两个状态区域之间的耗时是否超过某个阀值 子线程检测,每次检测时设置标记位为YES,然后派发任务到主线程中将标记位设置为NO。接着子线程沉睡超时阙值时长,判断标志位是否成功设置成NO,如果没有说明主线程发生了卡顿。参考ANREye的实现5、缩小包体积有哪些方案?图片压缩,无用图片删除 一些大图可以动态下发 删除无用类,无用方法 减少三方库的依赖计算机相关 1、项目编译的流程是什么?手机上的应用程序自点击图标开始到首屏内容展示都经历了哪些步骤? 编译流程:预处理:处理宏定义,删除注释,展开头文件。词法分析:把代码切成一个个token,比如大小括号等于号还有字符串语法分析:验证语法是否正确,合成抽象语法树AST静态分析:查找代码错误类型检查:动态和静态目标代码的生成与优化,包括删除多余指令,选择合适的寻址方式,如果开启了bitcode,会做进一步的优化汇编:由汇编器生成汇编语言机器码:由汇编语言转成机器码,生成.o文件应用启动的流程: 启动的前提是完成编译,运行程序即运行编译过后的目标程序,它分为main函数前和main函数后: main前加载可执行文件(App的.o文件集合)加载动态链接库(系统和应用的动态链接库),进行rebase指针调整和bind符号绑定Objc运行时的初始处理,包括Objc相关类的注册,category注册,selector唯一性检查初始化,包括执行+load()、attribute(constructor)修饰的函数的调用、创建C++静态全局变量main后首页初始化所需要配置文件的读写操作首页界面渲染2、对于基本数据类型,一般是存储到栈中的,它有没有可能存在堆上,什么情况下会存储到堆上? 栈和堆都是同属一块内存,只不过一个是高地址往低地址存储,一个从低地址往高地址存储,他们并没有严格的界限说一个值只能放在堆上或者栈上。所以基本数据类型也是可以存储到堆上的。 至于什么情况会存储到堆上,我没想到,有知道的同学可以告知一下。 3、数据库中的事务是什么意思? 事务就是访问并操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行。如果其中一个步骤出错就要撤销整个操作,回滚到进入事务之前的状态。 4、使用过什么数据库(我回答的Sqlite,Realm),Realm在使用时有哪些注意事项,如何实现批量操作? 对于Realm感兴趣的同学可以看下其官方文档。 Realm需要注意的主要就是不能直接跨线程访问同一对象。 批量操作可以在一个单独的事务中执行多个数据库的修改。 5、LRU算法是否了解,如何实现一套LRU算法? LRU(Least recently used 最近最少使用)算法是一个缓存淘汰算法,其作用就是当缓存很多时,该淘汰哪些内容,见名知意,它的核心思想是淘汰最近使用最少的内容。实现它的关键步骤是:新数据插入到链表的头部每当缓存命中时,则将数据移动到链表头部链表满时,将尾部数据清除这个算法在SDWebImage和Kingfisher等需要处理缓存的库中都有实现。 6、知道哪些设计模式,怎么理解设计模式的作用? 工厂模式、观察者模式、中介者模式、单例模式。这个根据实际情况说吧。 7、如果有1000万个Int类型的数字,如何对他们排序? 这里的隐藏含义是,内存不够用时如何排序,还有一个隐藏含义是硬盘足够大。这是可以采用分而治之的方法,将数据分成若干块,使每一小块满足当前内容大小,然后对每块内容单独排序,最后采用归并排序对所有块进行排序,就得到了一个有序序列。 8、设计一套数据库方案,实现类似微信的搜索关键词能快速检索出包含该字符串的聊天信息,并展示对应数量(聊天记录的数据量较大) 可以对聊天记录的文本值加上索引。正常情况下数据库搜索都是全量检索的,加上索引之后只会检索满足条件的记录,大大降低检索量。 简历相关问题 1、Lottie实现动画效果的原理是什么? iOS里的动画基本都是基于CoreAnimation里的API实现的,Lottie也是如此。在AE上实现动画效果,通过插件导出对应的json文件,Lottie的库解析该json,转成对应的系统API方法。图片的引用可以使用Base64编到json里,也可以通过项目集成,通过路径引用。 2、OClint实现静态分析的原理是什么,它是如何做到的? 具体可以参考我之前写的如何通过静态分析提高iOS代码质量。 3、MVVM和MVC有什么区别? 对比架构时,可以从是否职责分离,可测试性,可易维护性三个维度对比。 更多对比可以参考我翻译的一篇文章:【译】iOS 架构模式--浅析MVC, MVP, MVVM 和 VIPER 4、静态库和动态库的区别是什么? 静态库:链接时被完整复制到可执行文件中,多次使用就多份拷贝。 动态库:链接时不复制,而是由系统动态加载到内存,内存中只会有一份该动态库。 5、了解Flutter吗?它有没有使用UIKit?它是如何渲染UI的?UIKit是基于CoreAnimation渲染的,而Flutter并没有用到它,而是自己基于C++实现了一套渲染框架。 6、二进制重排的核心依据是什么? 修改链接顺序,减少启动时的缺页中断。 实践步骤可以参考李斌同学的这篇iOS 优化篇 - 启动优化之Clang插桩实现二进制重排 7、如何设计一套切换主题的方案? 核心思路是观察者模式+协议(通知),当获取到主题切换时,通知各个实现了主题协议的类进行更新。 8、AVPlayer和IJKPlayer有什么区别?用IJKPlayer如何实现一个缓存视频列表每条视频前1s的内容? 因为对IJKPlayer和FFmpeg了解的不是很深,这个我也没有确切答案,如果有了解的小伙伴可以评论告知我。 9、类似微博的短视频列表,滑动停留播放,如何实现? 这个主要就是检测contentOffset和屏幕中间位置,设置一些边界条件,处理滑动过程中的切换行为。 10、使用python做过哪些事?如何理解脚本语言? 多语言管理,csv多语言文件读取,然后写入到项目Localizable.strings中;抓取项目中的多语言字符串。 脚本(script) 其实就是一系列指令,计算机看了指令就知道自己该做什么事情。像常见的Python,Shell,Ruby都是脚本语言,他们通常不需要编译,通过解释器运行。 数据结构与算法 1、什么是Hash表,什么是Hash碰撞,解决Hash碰撞有什么方法? 哈希表(Hash Table,也叫散列表),是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。我们常用的Dictionary就是一种Hash表。 那什么是Hash碰撞呢,我们知道Hash表的查找是通过键值进行定位的,当两个不同的输入对应一个输出时,即为Hash碰撞,也被称为Hash冲突。 如果使用字典的例子你可能联想不到冲突的情况,我们假设另一种情况:假设hash表的大小为9(即有9个槽),现在要把一串数据存到表里:5,28,19,15,20,33,12,17,10。我们使用的hash函数是对9取余。这样的话会出现hash(5)=5,hash(28)=1,hash(19)=1。28和19都对应一个地址,这就出现了Hash冲突。 解决Hash冲突的方式有开放定址法和链地址法。 2、如何遍历二叉树?二叉树的遍历有三种方式,对于上面这棵二叉树,他们的遍历结果为: 前序遍历:根节点 > 左子节点 > 右子节点。 10,6,4,8,14,12,16 中序遍历:左子节点 > 根节点 > 右子节点。 4,6,8,10,12,14,16 后序遍历:左子节点 > 右子节点 > 根节点。 4,8,6,12,16,14,10 3、简述下快速排序的过程,时间复杂度是多少? 快排的思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。 一个简单的Swift实现方式如下: func quicksort<T: Comparable>(_ a: [T]) -> [T] { guard a.count > 1 else { return a } let pivot = a[a.count/2] let less = a.filter { $0 < pivot } let equal = a.filter { $0 == pivot } let greater = a.filter { $0 > pivot } return quicksort(less) + equal + quicksort(greater) }快速排序是有好几种的,他们的区别在于如何实现filter和分区基准值的选取。 快排的时间复杂度是O(nlogn),空间复杂度是O(logn) 4、有一个整数数组,如何只遍历一遍就实现让该数组奇数都在前面,偶数都在后面? 这个是《剑指offer》里的一道题,leedcode也有对应题目:剑指offer 21 这个相对比较简单,因为不要求有序,可以采用收尾遍历的方式,进行交换,我这有个参考答案: func sorted( _ nums: inout [Int]) -> [Int] { guard !nums.isEmpty else { return [] } var start = 0 var end = nums.count - 1 while start < end { if nums[start] % 2 != 0 { start += 1 continue } if nums[end] % 2 == 0 { end -= 1 continue } (nums[start], nums[end]) = (nums[end], nums[start]) } return nums }5、假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? leetcode 20 6、给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转 leetcode 7 7、有红、黄、蓝三种颜色的气球。在牛客王国,1个红气球+1个黄气球+1个蓝气球可以兑换一张彩票 2个红气球+1个黄气球可以兑换1个蓝气球。 2个黄气球+1个蓝气球可以兑换1个红气球。 2个蓝气球+1个红气球可以兑换1个黄气球。 现在牛牛有a个红气球,b个黄气球, c个蓝气球,牛牛想知道自己最多可以兑换多少张彩票。 这个是牛客网里的一道算法题,这里有个题解可以参考。
iOS面试总结(2020年6月)
都说今年互联网行情很差,作为被大家喊了好几年“iOS开发没人要了”的iOS行情更差。那真实情况是什么样的呢,以我的经历给大家分析下。应某个朋友建议,去掉这一句啊,目前iOS岗位还是挺多的,你可以这么想只要苹果爸爸不倒,iOS开发就不会没人要。但另一方面,招聘方对iOS开发的要求是在不断提高的,我们不能固步自封,满足现状,只有不断学习,不断进步,才能保持自身竞争力。 我的面试的阶段基本都在6月份,准备的阶段则要再往前推个半个月吧。期间约到了不少一二线互联网公司面试机会,前期由于准备不足也错失了一些机会,在之后的面试中不断总结经验,越来越有信心了,最终选择了爱奇艺。整体来看求职情况还算可以,不是很好但也不是很差,其中会带有一定运气成分,所以要换工作的话一定不要裸辞。 这里总结下这段时间的面试经历和一些心得,后面会附上期间遇到的面试题,大家可以尝试作答一下。求职准备 如果确定了想要换工作就应该为求职做准备了。 知识准备 在确定了换工作的想法之后,我们就应该为面试做准备了。在回顾知识点的时候我建议分类去梳理:OC语法,Runtime,Runloop,多线程,性能优化等,这些是优先级高的内容,其次是网络知识,数据结构与算法等计算机通识知识。 有一本书非常推荐:《Objective-C高级编程》,建议精读。 开源库的话看Runtime(最新为可编译799.1版本)吧,把类的定义,Runloop,weak,Autoreleasepool相关的代码都看下。 网络的知识点可以参考我的那篇:iOS面试备战-网络篇。数据结构与算法,按照类别刷个几十题应该能应付大多数情况了,iOS面试一般不会有太难的算法题。 简历 简历是求职的第一步,也是你能否获得面试机会的敲门砖,我们一定要好好打磨下。下面是我在脉脉上看到的HR在筛选简历时主要关注的点:我在今年3月份的时候尝试投过几次简历,并没有太好的结果,后来进行了一些调整优化。6月份再投的时候相对好了些,陆续收到了些回应。本人之前并没有大厂经历,不是一流本科,但也能收到不少大厂的面试机会,所以我感觉自己的简历内容还是起到了一定的作用的。如果想参考我简历的话,可以关注公众号:「iOS成长之路」,回复:简历,进行下载。 上面有提到“高光时刻”,可以理解成亮点。怎么让自己的简历跟同能力水平的求职者不同,那就是找到属于我们的亮点。有一个建议,我们在写简历时,可以刻意夸大自己的能力,或者写我们想成为的样子,再之后我们就对着简历让这些内容一一实现,让它们变成自己的亮点。一定要注意不能只吹牛,不落实,因为被发现“造假”可是很严重的。 简历投递 以我的经历来说,相对靠谱的简历投递方式有:Boss直聘、脉脉、内推。 需要注意的是,Boss直聘和脉脉只有别人联系你,你再投递,反馈率才会高一些。如果是你主动联系的招聘方,那大概率是不会收到回应的。推测很多企业并没有很多的招聘岗位也会把招聘信息挂在上面,这种时候HR是不会关注投递的简历的。这也是为什么能看到很多人晒出投递上百个简历确一个回应的都没有的情况,不要气馁,这不一定代表你能力不行。 等招聘者联系是相对被动的,主动出击会更有效。那就是寻找内推,一般公司内推都有奖励的,所以公司内部人员都乐意去发布职位获取内推人选。脉脉,掘金,V2EX,一些知名公众号都能发现不少内推岗位,我们可以自己去挖掘。 面试流程 目前互联网公司大部分是2轮技术面+1轮HR,或三轮技术面+1轮HR。目前的面试形式多为视频面试,也有些是电话面试。视频面试的话,如果是通过Zoom,企业微信,钉钉等一般是不考察手写代码的。如果是通过牛客网,一般是会考察手写代码的。对于手写代码,仅有算法题会要求准确性,可运行,对于设计类题目,我们写出伪代码即可。 如果到了HR轮基本说明我们已经通过了面试,如果确定入职,接下来就是背调,薪资证明,学历证明,入职体检等一系列操作。 面试题 以下是我面试过程中遇到的面试题,其中网络和多线程问题已经分成两篇单独讲解了,这里就去除了这两部分。 Swift 因为我最近两年多一直在用Swift,面试开始的自我介绍环节,我也会着重提这一点。但是很不幸,我得到的答案基本都是:面试主要考察OC。这也说明了大部分公司对Swift态度还是非常保守的,所以除非招聘信息里写了要求Swift技能,否则我们是没有必要专门准备Swift相关面试的。 当然面试过程中也遇到了几个Swift问题: 1、Swift中struct和class有什么区别? 2、Swift中的方法调用有哪些形式? 3、Swift和OC有什么区别? 4、从OC向Swift迁移的时候遇到过什么问题? 5、怎么理解面向协议编程? OC语法 1、Block是如何实现的?Block对应的数据结构是什么样子的?__block的作用是什么?它对应的数据结构又是什么样子的? 2、GCD中的Block是在堆上还是栈上? 3、NSCoding协议是干什么用的? 4、KVO的实现原理 5、NSOperation有哪些特性比着GCD有哪些优点,它有哪些API? 6、NSNotificaiton是同步还是异步的,如果发通知时在子线程,接收在哪个线程? UI 1、事件响应链是如何传递的? 2、什么是异步渲染? 3、layoutsubviews是在什么时机调用的? 4、一张图片的展示经历了哪些步骤? 5、什么是离屏渲染,什么情况会导致离屏渲染? 6、CoreAnimation这个框架的作用什么,它跟UIKit的关系是什么? 引用计数 1、ARC方案的原理是什么?它是在什么时候做的隐式添加release操作? 2、循环引用有哪些场景,如何避免? 3、为什么当我们在使用block时外面是weak 声明一个weakSelf,还要在block内部使用strong再持有一下? 4、Autoreleasepool是实现机制是什么?它是什么时候释放内部的对象的?它内部的数据结构是什么样的?当我提到哨兵对象时,会继续问哨兵对象的作用是什么,为什么要设计它? 5、哪些对象会放入到Autoreleasepool中? 6、weak的实现原理是什么?当引用对象销毁是它是如何管理内部的Hash表的?(这里要参阅weak源码) Runtime 1、消息发送的流程是怎样的? 2、关联对象时什么情况下会导致内存泄露? 3、消息转发的流程是什么? 4、category能否添加属性,为什么?能否添加实例变量,为什么? 5、元类的作用是什么? 6、类方法是存储到什么地方的?类属性呢? 7、讲几个runtime的应用场景 Runloop 1、讲一下对Runloop的理解? 2、可以用Runloop实现什么功能? 性能优化 1、对TableView进行性能优化有哪些方式? 2、Xcode的Instruments都有哪些调试的工具? 3、讲一下你做过的性能优化的事情。 4、如何检测卡顿,都有哪些方法? 5、缩小包体积有哪些方案? 计算机相关 1、项目编译的流程是什么?手机上的应用程序自点击图标开始到首屏内容展示都经历了哪些步骤? 2、对于基本数据类型,一般是存储到栈中的,它有没有可能存在堆上,什么情况下会存储到堆上? 3、数据库中的事务是什么意思? 4、使用过什么数据库(我回答的Sqlite,Realm),Realm在使用时有哪些注意事项,如何实现批量操作? 5、LRU算法是否了解,如何实现一套LRU算法? 6、知道哪些设计模式,怎么理解设计模式的作用? 7、如果有1000万个Int类型的数字,如何对他们排序? 8、设计一套数据库方案,实现类似微信的搜索关键词能快速检索出包含该字符串的聊天信息,并展示对应数量(聊天记录的数据量较大)。 简历相关问题 1、Lottie实现动画效果的原理是什么? 2、OClint实现静态分析的原理是什么,它是如何做到的? 3、MVVM和MVC有什么区别? 4、静态库和动态库的区别是什么? 5、了解Flutter吗?它有没有使用UIKit?它是如何渲染UI的? 6、二进制重排的核心依据是什么? 7、如何设计一套切换主题的方案? 8、AVPlayer和IJKPlayer有什么区别?用IJKPlayer如何实现一个缓存视频列表每条视频前1s的内容? 9、类似微博的短视频列表,滑动停留播放,如何实现? 10、使用python做过哪些事?如何理解脚本语言? 数据结构与算法 1、什么是Hash表,什么是Hash碰撞,解决Hash碰撞有什么方法? 2、如何遍历二叉树? 3、简述下快速排序的过程,时间复杂度是多少? 4、有一个整数数组,如何只遍历一遍就实现让该数组奇数都在前面,偶数都在后面? 5、假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 6、给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。leetcode 7 7、有红、黄、蓝三种颜色的气球。在牛客王国,1个红气球+1个黄气球+1个蓝气球可以兑换一张彩票。 2个红气球+1个黄气球可以兑换1个蓝气球。 2个黄气球+1个蓝气球可以兑换1个红气球。 2个蓝气球+1个红气球可以兑换1个黄气球。 现在牛牛有a个红气球,b个黄气球, c个蓝气球,牛牛想知道自己最多可以兑换多少张彩票。 软技能 1、做过哪些工作职责之外的事情? 2、经历过最难的一次业务开发是什么样的,最终怎么解决的? 3、最近有学习什么新技术吗?有何收获? 4、你最擅长iOS哪方面的知识?怎么体现出来的? 5、常用哪些开源库,有没有研究过他们的原理? 6、如何保持个人成长? 流程型问题 流程性问题基本都会包含下面四个,最好提前准备好 1、请做下自我介绍。 2、你有什么问题要问我的吗? 3、为什么离职? 4、对下份工作的期望是什么样的? 这些问题看似不起眼,但其实还挺重要的,很有可能面试官就是通过这几个问题决定了要不要你通过面试。 自我介绍就不说了,简明扼要介绍自己近几年的经历和成绩就行,控制在一分钟以内。 第二个,最好不要直接说没有问题了,提问面试官是我们整个面试过程中少有的掌握主动权的时刻,它可以体现我们自主思考的能力。最好提前了解下公司和招聘需求,准备几个问题,或者面试过程中提出我们产生的一些疑问。 离职原因,这个如实回答即可,只要不说是因为钱或者跟领导同事不和基本都没有问题。 下份工作的期望,这个就看各自的需求吧。 总结 通过这些面试题,我们可以看出一些端倪。 1、面试官更喜欢“刨根问底”,对着一个概念不断的往深处延展,不断深入的问。这类问题会有很大的区分度,第一问第二问第三问难度逐次提高,用于筛选不同的面试者。这也提醒我们某些知识点不光要知道原理,还要知道为什么这么设计,这么设计的好处是什么。 2、问题范围更全面化,特别是二面时,问题不再局限于iOS端,而是更通用的计算机方向问题,这个需要我们平常多积累;还有就是开始重视个人软技能,学习能力和上进心。 3、围绕简历,还记得上面说过写简历时要吹牛逼吗。在面试的时候一定要把他们成为自己真正掌握的知识。 4、注重软技能,这个比前面几条作用稍微小些,但是如果被问到了,而我们也有很好的贴合点,那绝对就是加分项。我的一次经历是,当我向面试官说自己有写博客的习惯,他问我是否知道medium,我说知道,还翻译过几篇里面的文章,接着说了些我理解的国内外博客平台的现状分析。这种情况就属于加分项了。 另外面试是一次考察自己知识掌握程度的考核,考的好能提升自己自信心,考的不好可以帮助我们定位自身问题,不管怎么说都是不亏的。面试还可以帮助我们了解市场行情,薪资待遇,自身竞争力,流行技术栈等一系列情况。所以真的建议即使不考虑换工作,每年固定时间也可以出去面试几次。
iOS面试备战-多线程
iOS面试中多线程绝对是最重要的知识点之一,它在日常开发中会被广泛使用,而且多线程是有很多区分度很高的题目可供考察的。这篇文章会梳理下多线程和GCD相关的概念和几个典型问题。因为GCD相关的API用OC看着更直管一些,所以这期实例就都用OC语言书写。概念篇 在面对一些我们常见的概念时,我们常有种这个东西我熟的感觉,但是如果没有深入研究它们的概念和区别,还是很容易弄混或者讲不清楚的。所以这里单独抽一节讲下多线程中的概念。 进程,线程,任务,队列 进程:资源分配的最小单位。在iOS中一个应用的启动就是开启了一个进程。 线程:CPU调度的最小单位。一个进程里会有多个线程。 大家可以思考下,进程和线程为什么是从资源分配和CPU调度层面进行定义的。 任务:每次执行的一段代码,比如下载一张图片,触发一个网络请求。 队列:队列是用来组织任务的,一个队列包含多个任务。 GCD GCD(Grand Central Dispatch)是异步执行任务的技术之一。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程执行该任务。这里的线程管理是由系统处理的,我们不必关心线程的创建销毁,这大大方便了我们的开发效率。也可以说GCD是一种简化线程操作的多线程使用技术方案。 安卓没有跟GCD完全相同的一套技术方案的,虽然它可以处理GCD实现的一系列效果。 串行,并行,并发 GCD的使用都是通过调度队列(Dispatch Queue)的形式进行的,调度队列有以下 几种形式: 串行(serial):多任务中某时刻只能有一个任务被运行; 并行(parallel):相对于串行,某时刻有多个任务同时被执行,需要多核能力; 并发(concurrent):引入时间片和抢占之后才有了并发的说法,某个时间片只有一个任务在执行,执行完时间片后进行资源抢占,到下一个任务去执行,即“微观串行,宏观并发”,所以这种情况下只有一个空闲的某核,多核空闲就又可以实现并行运行了; 我们常用的调度队列有以下几种: // 串行队列 dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL); // 并发队列 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); // 全局并发队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 主队列 let mainQueue = DispatchQueue.main注意GCD创建的是并发队列而不是并行队列。但这里的并发队列是一个相对宽泛的定义,它包含并行的概念,GCD作为一个智能的中心调度系统会根据系统情况判断当前能否使用多核能力分摊多个任务,如果满足的话此时就是在并行的执行队列中的任务。 同步,异步 同步:函数会阻塞当前线程直到任务完成返回才能进行其它操作; 异步:在任务执行完成之前先将函数值返回,不会阻塞当前线程; 串行、并发和同步、异步相互结合能否开启新线程串行队列 并发队列 主队列同步 不开启新线程 不开启新线程 不开启新线程异步 开启新线程 开启新线程 不开启新线程主线程和主队列 主线程是一个线程,主队列是指主线程上的任务组织形式。 主队列只会在主线程执行,但主线程上执行的不一定就是主队列,还有可能是别的同步队列。因为前说过,同步操作不会开辟新的线程,所以当你自定义一个同步的串行或者并行队列时都是还在主线程执行。 判断当前是否是主线程: BOOL isMainThread = [NSThread isMainThread];判断当前是否在主队列上: static void *mainQueueKey = "mainQueueKey"; dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL); BOOL isMainQueue = dispatch_get_specific(mainQueueKey));队列与线程的关系 队列是对任务的描述,它可以包含多个任务,这是应用层的一种描述。线程是系统级的调度单位,它是更底层的描述。一个队列(并行队列)的多个任务可能会被分配到多个线程执行。 问题 代码分析 1、分析下面代码的执行逻辑 - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self syncMainTask]; }- (void)syncMainTask { dispatch_queue_main_t mainQueue = dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ NSLog(@"main queue task"); }); }这段代码会输出task1,然后发生死锁,导致crash。 追加问题一:为什么会死锁?死锁就会导致crash? 我们先分析crash的情况,正常死锁应该就是卡死的情况,不应该导致carsh。那为什么会carsh呢,看崩溃信息:是一个EXC_BAD_INSTRUCTION类型的crash,执行了一个出错的命令。 然后看__DISPATCH_WAIT_FOR_QUEUE__的调用栈信息:右侧汇编代码给出了更详细的crash信息:BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread。 在当前线程已经拥有的队列中执行dispatch_sync同步操作会导致crash。 在libdispatch的源码中我们可以找到该函数的定义: DISPATCH_NOINLINE static void __DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq) { uint64_t dq_state = _dispatch_wait_prepare(dq); if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) { DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread"); } /*...*/ }所以我们知道了,这个carsh是libdispatch内部抛出的,当它检测到可能发生死锁时,就直接触发崩溃,事实上它不能完全判断出所有死锁的情况。 我们分析这里为什么会发生死锁。首先syncMainTask就是在主队列中的,我们在主队列先添加dispatch_sync然后再添加其内部的block。主队列FIFO,只有sync执行完了才会执行内部的block,而此时是一个同步队列,block执行完才会退出sync,所以导致了死锁。 对于死锁的解释我也查了好几篇文章,有些说法其实是经不起推敲的,这个解释是我认为相对合理的。 附一篇参考文章:GCD死锁 引出问题二:什么情况下会发生死锁? GCD中发生死锁需要满足两个条件:同步执行串行队列 执行sync的队列和block所在队列为同一个队列引出问题三:如何避免死锁?这段代码应该如何修改? 根据上面提到的条件,我们可以将任务异步执行,或者换成一个并发队列。另外将block放到一个非主队列里执行也是可以的。 2、分析一下代码执行结果 int a = 0; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); while (a < 2) { dispatch_async(queue, ^{ a++; }); } NSLog(@"a = %d", a);首先该段代码会编译不过,编译器检测到变量a被block截获,并尝试修改就报以下错误: Variable is not assignable (missing __block type specifier)。如果我们要在block里对外界变量重新复制,需要添加__block的声明:__block int a = 0; 我们分析这段代码,在开始while之后加入一个异步任务,再之后呢,这个是不确定了,可能是执行a++也可能是因不满足退出条件再次执行加入异步任务,直到满足a<2才会退出while循环。那输出结果也就是不确定了,因为可能在判断跳出循环和输出结果的时候另外的线程又执行了一次a++。 再扩展下,如果将那个并发队列改成主队列,执行逻辑还是一样的吗? 首先主队列是不会开启新线程的,主队列上的异步操作执行时机是等别的任务都执行完了,再来执行添加的a++。显然在while循环里,主队列既有任务还未执行完毕,所以就不会执行a++,也就导致while循环不会退出,形成死循环。 其它问题 什么是线程安全,为什么UI操作必须在主线程执行 线程安全:当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的。 为什么UI操作必须放到主线程:首先UIKit不是线程安全的,多线程访问会导致UI效果不可预期,所以我们不能使用多个线程去处理UI。那既然要单线程处理UI为什么是在主线程呢,这是因为UIApplication作为程序的起点是在主线程初始化的,所以我们后续的UI操作也都要放到主线程处理。 关于这个问题展开讨论可以参阅这篇文章:iOS拾遗——为什么必须在主线程操作UI ###开启新的线程有哪些方法 1、NSThread 2、NSOperationQueue 3、GCD 4、NSObject的performSelectorInBackground方法 5、pthread 多线程任务要实现顺序执行有哪些方法 1、dispatch_group 2、dispatch_barrier 3、dispatch_semaphore_t 4、NSOperation的addDependency方法 如何实现一个多读单写的功能? 多读单写的意思就是可以有多个线程同时参与读取数据,但是写数据时不能有读操作的参与切只有一个线程在写数据。 我们写一个示例程序,看下在不做限制的多读多写程序中会发生什么。 // 计数器 self.count = 0; // 并发队列 self.concurrentQueue = dispatch_get_global_queue(0, 0); for (int i = 0; i< 10; i++) { dispatch_async(self.concurrentQueue, ^{ [self read]; }); dispatch_async(self.concurrentQueue, ^{ [self write]; }); } // 读写操作 - (void)read { NSLog(@"read---- %d", self.count); }- (void)write { self.count += 1; NSLog(@"write---- %d", self.count); }// 输出内容 2020-07-18 11:47:03.612175+0800 GCD_OC[76121:1709312] read---- 0 2020-07-18 11:47:03.612273+0800 GCD_OC[76121:1709311] read---- 1 2020-07-18 11:47:03.612230+0800 GCD_OC[76121:1709314] write---- 1 2020-07-18 11:47:03.612866+0800 GCD_OC[76121:1709312] write---- 2 2020-07-18 11:47:03.612986+0800 GCD_OC[76121:1709311] write---- 3 2020-07-18 11:47:03.612919+0800 GCD_OC[76121:1709314] read---- 2 2020-07-18 11:47:03.613252+0800 GCD_OC[76121:1709312] read---- 3 2020-07-18 11:47:03.613346+0800 GCD_OC[76121:1709314] write---- 4 2020-07-18 11:47:03.613423+0800 GCD_OC[76121:1709311] read---- 4每次运行的输出结果都会不一样,根据这个输出内容,我们可以看到在还没有执行到输出write----1的时候,就已经执行了read----1,在write---- 3之后 read的结果却是2。这绝对是我们所不期望的。其实在程序设计中我们是不应该设计出多读多写这种行为,因为这个结果是不可控。 解决方案之一是对读写操作都加上锁做成单独单写,这样是没问题但有些浪费性能,正常写操作确定之后结果就确定了,读的操作可以多线程同时进行,而不需要等别的线程读完它才能读,所以有了多读单写的需求。 解决多读单写常见有两种方案,第一种是使用读写锁pthread_rwlock_t。 读写锁具有一些几个特性:同一时间,只能有一个线程进行写的操作 同一时间,允许有多个线程进行读的操作。 同一时间,不允许既有写的操作,又有读的操作。这跟我们的多读单写需求完美吻合,也可以说读写锁的设计就是为了实现这一需求的。它的实现方式如下: // 执行读写操作之前需要定义一个读写锁 @property (nonatomic,assign) pthread_rwlock_t lock; pthread_rwlock_init(&_lock,NULL); // 读写操作 - (void)read { pthread_rwlock_rdlock(&_lock); NSLog(@"read---- %d", self.count); pthread_rwlock_unlock(&_lock); }- (void)write { pthread_rwlock_wrlock(&_lock); _count += 1; NSLog(@"write---- %d", self.count); pthread_rwlock_unlock(&_lock); } // 输出内容 2020-07-18 12:00:29.363875+0800 GCD_OC[77172:1722472] read---- 0 2020-07-18 12:00:29.363875+0800 GCD_OC[77172:1722471] read---- 0 2020-07-18 12:00:29.364195+0800 GCD_OC[77172:1722469] write---- 1 2020-07-18 12:00:29.364325+0800 GCD_OC[77172:1722472] write---- 2 2020-07-18 12:00:29.364450+0800 GCD_OC[77172:1722470] read---- 2 2020-07-18 12:00:29.364597+0800 GCD_OC[77172:1722471] write---- 3 2020-07-18 12:00:29.366490+0800 GCD_OC[77172:1722469] read---- 3 2020-07-18 12:00:29.366703+0800 GCD_OC[77172:1722472] write---- 4 2020-07-18 12:00:29.366892+0800 GCD_OC[77172:1722489] read---- 4我们查看输出日志,所以的读操作结果都是最近一次写操作所赋的值,这是符合我们预期的。 还有一种实现多读单写的方案是使用GCD中的栅栏函数dispatch_barrier。栅栏函数的目的就是保证在同一队列中它之前的操作全部执行完毕再执行后面的操作。为了保证写操作的互斥行,我们要对写操作执行「栅栏」: // 我们定义一个用于读写的并发对列 self.rwQueue = dispatch_queue_create("com.rw.queue", DISPATCH_QUEUE_CONCURRENT);- (void)read { dispatch_sync(self.rwQueue, ^{ NSLog(@"read---- %d", self.count); }); }- (void)write { dispatch_barrier_async(self.rwQueue, ^{ self.count += 1; NSLog(@"write---- %d", self.count); }); }这个输出结果跟读写锁实现是一样的,也是符合预期的。 这里多说几句,这里的读和写分别使用sync和async。读操作要用同步是为了阻塞线程尽快返回结果,不用担心无法实现多读,因为我们使用了并发队列,是可以实现多读的。至于写操作使用异步的栅栏函数,是为了写时不阻塞线程,通过栅栏函数实现单写。如果我们将读写都改成sync或者async,由于栅栏函数的机制是会顺序先读后写。如果反过来,读操作异步,写操作同步也是可以达到多读单写的目的的,但读的时候不立即返回结果,网上有人说只能使用异步方式,防止发生死锁,这个说法其实不对,因为同步队列是不会发生死锁的。 用GCD如何实现一个控制最大并发数且执行任务FIFO的功能? 这个相对简单,通过信号量实现并发数的控制,通过并发队列实现任务的FIFO的执行 int maxConcurrent = 3; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(maxConcurrent); dispatch_async(queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // task dispatch_semaphore_signal(semaphore); });
iOS面试备战-网络
计算机网络是计算机科学与技术专业的必修课,也是移动端,前端,后端都会涉及到的知识点,同时它也是iOS面试中大概率会出现的问题。所以准备面试的话,网络相关的知识点一定不能错过。这里总结了一些我认为有用的和最近面试遇到的网络相关知识点。 去年写过一篇《图解TCP/IP》总结的文章,也可以对着看下。 计算机网络是如何分层的 网络有两种分层模型,一种是ISO(国际标准化组织)制定的OSI(Open System Interconnect)模型,它将网络分为七层。一种是TCP/IP的四层网络模型。OSI是一种学术上的国际标准,理想概念,TCP/IP是事实上的国际标准,被广泛应用于现实生活中。两者的关系可以看这个图:注:也有说五层模型的,它跟四层模型的区别就是,在OSI模型中的数据链路层和物理层,前者将其作为两层,后者将其合并为一层称为网络接口层。一般作为面试题的话都是需要讲出OSI七层模型的。 各个层的含义以及它们之间的关系可以看这张图:Http协议 http协议特性HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。 无状态:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传。请求方法GET:请求获取Request-URI标识的资源,请求参数附加在url上,明文展示。POST:在Request-URI所标识的资源后附加新的数据,常用于修改服务器资源或者提交资源到服务器。POST请求体是放到body中的,可以指定编码方式,更加安全。HEAD:请求获取由Request-URI所标识的资源的响应消息报头。PUT:请求服务器存储一个资源,并用Request-URI作为其标识。DELETE:请求服务器删除Request-URI所标识的资源。TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断。OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求。请求和响应报文 以该链接为例:https://zhangferry.com/2019/08/31/diagram_tcpip_concepts/ 在Chrome查看其请求的Headers信息。 General这里标记了请求的URL,请求方法为GET。状态码为304,代表文件未修改,可以直接使用缓存的文件。远程地址为185.199.111.153:443,此IP为Github 服务器地址,是因为我的博客是部署在GitHub上的。 除了304还有别的状态码,分别是:200 OK 客户端请求成功 301 Moved Permanently 请求永久重定向 302 Moved Temporarily 请求临时重定向 304 Not Modified 文件未修改,可以直接使用缓存的文件。 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因 404 Not Found 请求的资源不存在,例如,输入了错误的URL 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。Response Headers:content-encoding:用于指定压缩算法 content-length:资源的大小,以十进制字节数表示。 content-type:指示资源的媒体类型。图中所示内容类型为html的文本类型,文字编码方式为utf-8 last-modified:上次内容修改的日期,为6月8号 status:304 文件未修改状态码 注:其中content-type在响应头中代表,需要解析的格式。在请求头中代表上传到服务器的内容格式。 Request Headers::method:GET请求 :path:url路径 :scheme:https请求 accept:通知服务器可以返回的数据类型。 accept-encoding:编码算法,通常是压缩算法,可用于发送回的资源 accept-language:通知服务器预期发送回的语言类型。这是一个提示,并不一定由用户完全控制:服务器应该始终注意不要覆盖用户的显式选择(比如从下拉列表中选择语言)。 cookie:浏览器cookie user-agent:用户代理,标记系统和浏览器内核 更多请求头的字段含义可以参考这里:HTTP headers TCP三次握手和四次挥手的过程以及为什么要有三次和四次 在了解TCP握手之前我们先看下TCP的报文样式:其中控制位(Control Flag)标记着握手阶段的各个状态。TCP三次握手 示意图如下:三次握手是指建立一个TCP连接时,需要客户端和服务器总共发送3个数据包。 1、第一次握手(SYN=1, seq=x) 客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。 发送完毕后,客户端进入 SYN_SEND 状态。 2、第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1) 服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。 3、第三次握手(ACK=1, ACKnum=y+1) 客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1 发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。 问题一:为什么需要三次握手呢? 在谢希仁著的《计算机网络》里说,『为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误』。怎么理解呢,我们假设一种情况,有一个建立连接的第一次握手的报文段因为滞留到网络中过了较长时间才发送到服务端。这时服务器是要做ACK应答的,如果只有两次握手就代表连接建立,那服务器此时就要等待客户端发送建立连接之后的数据。而这只是一个因滞留而废弃的请求,是不是白白浪费了很多服务器资源。 从另一个角度看这个问题,TCP是全双工的通信模式,需要保证两端都已经建立可靠有效的连接。在三次握手过程中,我们可以确认的状态是: 第一次握手:服务器确认自己接收OK,服务端确认客户端发送OK。 第二次握手:客户端确认自己发送OK,客户端确认自己接收OK,客户端确认服务器发送OK,客户端确认服务器接收OK。 第三次握手:服务器确认自己发送OK,服务器确认客户端接收OK。 只有握手三次才能达到全双工的目的:确认自己和对方都能够接收和发送消息。 TCP四次挥手 示意图如下:四次挥手表示要发送四个包,挥手的目的是断开连接。 1、第一次挥手(FIN=1, seq=x) 假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。 发送完毕后,客户端进入 FIN_WAIT_1 状态。 2、第二次挥手(ACK=1,ACKnum=x+1) 服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。 3、第三次挥手(FIN=1,seq=y) 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。 发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。 4、第四次挥手(ACK=1,ACKnum=y+1) 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。 服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。 问题一:为什么挥手需要四次呢?为什么不能将ACK和FIN报文一起发送? 当服务器收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端『你发的FIN我收到了』。只有等到服务端所有的报文都发送完了,才能发FIN报文,所以要将ACK和FIN分开发送,这就导致需要四次挥手。 问题二:为什么TIMED_WAIT之后要等2MSL才进入CLOSED状态? MSL是TCP报文的最大生命周期,因为TIME_WAIT持续在2MSL就可以保证在两个传输方向上的尚未接收到或者迟到的报文段已经消失,同时也是在理论上保证最后一个报文可靠到达。假设最后一个ACK丢失,那么服务器会再重发一个FIN,这是虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK。 ###HTTPS的流程 HTTPS = HTTP + TLS/SSL,它的建立可以用以下示意图表示:1、客户端首次请求服务器,告诉服务器自己支持的协议版本,支持的加密算法及压缩算法,并生成一个随机数(client random)告知服务器。 2、服务器确认双方使用的加密方法,并返回给客户端证书以及一个服务器生成的随机数(server random) 3、客户端收到证书后,首先验证证书的有效性,然后生成一个新的随机数(premaster secret),并使用数字证书中的公钥,加密这个随机数,发送给服务器。 4、服务器接收到加密后的随机数后,使用私钥进行解密,获取这个随机数(premaster secret 5、服务器和客户端根据约定的加密方法,使用前面的三个随机数(client random, server random, premaster secret),生成『对话密钥』(session key),用来加密接下来的整个对话过程(对称加密)。 有一篇由浅入深介绍HTTPS的文章可以阅读一下:看图学HTTPS 问题一:为什么握手过程需要三个随机数,而且安全性只取决于第三个随机数? 前两个随机数是明文传输,存在被拦截的风险,第三个随机数是通过证书公钥加密的,只有它是经过加密的,所以它保证了整个流程的安全性。前两个随机数的目的是为了保证最终对话密钥的『更加随机性』。 问题二:Charles如何实现HTTPS的拦截? Charles要实现对https的拦截,需要在客户端安装Charles的证书并信任它,然后Charles扮演中间人,在客户端面前充当服务器,在服务器面前充当客户端。 问题三:为什么有些HTTPS请求(例如微信)抓包结果仍是加密的,如何实现的?我在聊天过程中并没有抓到会话的请求,在小程序启动的时候到是抓到了一个加密内容。我手动触发该链接会下载一个加密文件,我猜测这种加密是内容层面的加密,它的解密是由客户端完成的,而不是在HTTPS建立过程完成的。 另外在研究这个问题的过程中,又发现了一些有趣的问题:1、图中所示的三个https请求分别对应三个不同类型的图标,它们分别代表什么意思呢? 感谢iOS憨憨的回答。 第一个图标含义是HTTP/2.0,第二个图标含义是HTTP/1.1,第三个图标加锁是因为我用charles只抓取了443端口的请求,该请求端口为5228,所以不可访问。 2、第三个请求https://mtalk.google.com:5228图标和请求内容都加了锁,这个加锁是在https之上又加了一层锁吗? 这些问题暂时没有确切的答案,希望了解的小伙伴告知一下哈。 DNS解析流程 DNS(Domain name system)域名系统。DNS是因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户通过域名访问到对应的服务器(IP地址)。具体的解析流程是这样的: 1、浏览器中输入想要访问的网站域名,操作系统会检查本地hosts文件是否有这个网址的映射关系,如果有就调用这个IP地址映射,完成域名解析。没有的话就走第二步。 2、客户端回向本地DNS服务器发起查询,如果本地DNS服务器收到请求,并可以在本地配置区域资源中查到该域名,就将对应结果返回为给客户端。如果没有就走第三步。 3、根据本地DNS服务器的设置,采用递归或者迭代查询,直至解析完成。其中递归查询和迭代查询可以用如下两图表示。 递归查询 如图所示,递归查询是由DNS服务器一级一级查询传递的。迭代查询 如果所示,迭代查询是找到指定DNS服务器,由客户端发起查询。DNS劫持 DNS劫持发生在DNS服务器上,当客户端请求解析域名时将其导向错误的服务器(IP)地址。 常见的解决办法是使用自己的解析服务器或者是将域名以IP地址的方式发出去以绕过DNS解析。 Cookie和Session的区别 HTTP 是无状态协议,说明它不能以状态来区分和管理请求和响应。也就是说,服务器单从网络连接上无从知道客户身份。 可是怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。Cookie:Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,实际上Cookie是服务器在本地机器上存储的一小段文本,并随着每次请求发送到服务器。Cookie技术通过请求和响应报文中写入Cookie信息来控制客户端的状态。Session:Session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息。当有用户请求创建一个session时,服务器会先检查这个客户端里是否已经包含了一个Session标识(session id),如果有就通过session id把session检索出来。如果没有就创建一个对应此Session的session id。这个session id会在本次响应中返回给客户端。两者有以下区别: 1、存储位置:Cookie存放在客户端上,Session数据存放在服务器上。 2、Session 的运行依赖 session id,而 session id 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie ,同时 Session 也会失效 3、安全性:Cookie存在浏览器中,可能会被一些程序复制,篡改;而Session存在服务器相对安全很多。 4、性能:Session会在一定时间内保存在服务器上,当访问增多,会对服务器造成一定的压力。考虑到减轻服务器压力,应当使用Cookie CDN CDN(Content Delivery Network),根本作用是将网站的内容发布到最接近用户的网络『边缘』,以提高用户访问速度。概括的来说:CDN = 镜像(Mirror) + 缓存(Cache) + 整体负载均衡(GSLB)。 目前CDN都以缓存网站中的静态数据为主,如CSS、JS、图片和静态网页等数据。用户在从主站服务器请求到动态内容后再从CDN上下载这些静态数据,从而加速网页数据内容的下载速度,如淘宝有90%以上的数据都是由CDN来提供的。 CDN工作流程 一个用户访问某个静态文件(如CSS),这个静态文件的域名假如是www.baidu.com,而这个域名最终会被指向CDN全局中CDN负载均衡服务器,再由这个负载均衡服务器来最终分配是哪个地方的访问用户,返回给离这个访问用户最近的CDN节点。之后用户就直接去这个CDN节点访问这个静态文件了,如果这个节点中请求的文件不存在,就会再回到源站去获取这个文件,然后再返回给用户。参考:深入理解Http请求、DNS劫持与解析 Socket socket位于应用层和传输层之间:它的作用是为了应用层能够更方便的将数据经由传输层来传输。所以它的本质就是对TCP/IP的封装,然后应用程序直接调用socket API即可进行通信。上文中说的三次握手和四次挥手即是通过socket完成的。 我们可以从iOS中网络库分层找到BSD Sockets,它是位于CFNetwork之下。在CFNetwork中还有一个CFSocket,推测是对BSD Sockets的封装。WebRTC WebRTC是一个可以用在视频聊天,音频聊天或P2P文件分享等Web App中的 API。借助WebRTC,你可以在基于开放标准的应用程序中添加实时通信功能。它支持在同级之间发送视频,语音和通用数据,从而使开发人员能够构建功能强大的语音和视频通信解决方案。该技术可在所有现代浏览器以及所有主要平台的本机客户端上使用。WebRTC项目是开源的,并得到Apple,Google,Microsoft和Mozilla等的支持。 如果某一请求只在某一地特定时刻失败率较高,会有哪些原因 这个是某公司二面时的问题,是一个开放性问题,我总结了以下几点可能: 1、该时刻请求量过大 2、该地的网络节点较不稳定 3、用户行为习惯,比如该时刻为上班高峰期,或者某个群体的特定习惯 如果有对网络方面比较熟悉的小伙伴也可以补充。
快手iOS面经
- 28 Mar, 2020
背景 过完年来北京之后,有准备看看机会,也是想了解下市场行情。简历没有投太多,只定向投了头条教育部门、抖音、快手、阿里,这些公司。头条和阿里的简历都没过,肯定是亮点太少吧。只有快手简历过了,快手是三轮技术面+一轮HR面,前两轮技术都比较顺利,到第三轮却栽了,很痛心o(╥﹏╥)o。目前就不考虑换工作了,等下半年再说了,接下来的时间再好好精炼一下。 快手是视频面试,不支持周末,但是可以选择晚上时间,我这几次都是定在了晚上九点。视频面试是通过牛客网进行的,以下是我还记得下来的各轮面试题,对于一些iOS基础知识就不做解答了。一面 1、用递归写一个算法,计算从1到100的和。 func sum(value: Int) -> Int { if value <= 0 { return 0 } var number = value return value + sum(value: number - 1) } // 计算过程 let result = sum(value: 100) print(result)写完算法之后又围绕着问了几个问题,都是算法基础:算法的时间复杂度是多少 递归会有什么缺点 不用递归能否实现,复杂度能否降到O(1)2、property的作用是什么,有哪些关键词,分别是什么含义? 3、父类的property是如何查找的? 4、NSArray、NSDictionary应该如何选关键词? 5、copy和muteCopy有什么区别,深复制和浅复制是什么意思,如何实现深复制? 6、用runtime做过什么事情?runtime中的方法交换是如何实现的? 7、讲一下对KVC合KVO的了解,KVC是否会调用setter方法? 8、__block有什么作用 9、说一下对GCD的了解,它有那些方法,分别是做什么用的? 10、对二叉树是否了解? 面试官是想接着问这方面的问题的。我当时说了不了解,然后就没有后续了。 二面 1、ARC和MRC的区别,iOS是如何管理引用计数的,什么情况下引用计数加1什么情况引用计数减一? 2、在MRC下执行[object autorelease]会发生什么,autorelease是如何实现的? 3、CoreAnimation是如何绘制图像的,动画过程中的frame能否获取到? 4、谈一下对Runlop的了解? 5、OC如何实现多继承? 这个当时没有答好。其实借助于消息转发,protocol和类别都可以间接实现多继承。 6、对设计模式有什么了解,讲一下其中一种是如何使用的。 7、有没有哪个开源库让你用的很舒服,讲一下让你舒服的地方。 我这里说了RxSwift中的观察者模式,和响应式编程。然后面试官问,如果要用OC实现一套RxSwift那样的逻辑应该怎么做。我回答的是结合KVO,将一些需要观察的属性,通过KVO进行监听,然后通过block回调出来。 8、一张100*100,RGBA的png图像解压之后占多大内存空间。 RGBA > FFFFFFFF > 4字节 所以会占用:(100 * 100 * 4) / 1024 = 39KB 9、算法题 题目:给定一个个数字arr,判断数组arr中是否所有的数字都只出现过一次。 这个并没有要求写出来,说是提供思路就行了。我当时给的方案是在便利数组的时候,用一个字典把便利的元素存起来,如果在后面的便利过程中新元素在字典中存在过就说明,有重复数字出现。时间复杂度是O(n)。 当时也问了有没有办法进行优化,我当时想到了将数组转成Set,然后和原数组比较,两个集合的数量是否变化。 10、因为我跟他介绍自己Swift用的多一些,然后问了些Swift跟OC的区别,各自的优缺点。 11、为什么离职,有什么职业规划。 三面 1、给定一个Int型数组,用里面的元素组成一个最大数,因为数字可能非常大,用字符串输出。 输入: [3,30,34,5,9] 输出: 9534330这个是leetcode的179题,难度中等。面试官让先说思路,再去做题。事先说一下这个题我没有做过。当时的思路是用冒泡法进行排序,排序的前提是将较少位数的数字进行循环补齐,例如3和30的比较,变成33和30的比较,34和4的比较变成34和44的比较,然后将结果从大到小整合成字符串输出。 但是做题是却发现没那么简单,位数的补齐对于2位和3位数的比较还需要求位数的最小公倍数,将他们都转成6位数才能比较。在挣扎了5分钟做了就做罢了。 后来再去做这道题,其实这就是一个排序而已,只不过他的规则是按高位优先级更高的原则,而这一点跟字符串的比较保持一致,如果再加一些Swift的高阶函数,就可以写成: func largestNumber(_ nums: [Int]) -> String { let sort = nums.map {"\($0)"}.sorted { (lStr, rStr) -> Bool in return lStr + rStr > rStr + lStr } let result = sort.joined() if result.prefix(1) == "0" { return "0" } else { return result } }2、项目中有这么一个方法func findfile(dir: String suffix: String) -> [String] ,可以通过输入文件夹目录,和后缀检索出所需的文件。 例如需要在某个文件中检索txt文件或者mp4文件,那就传入dir和suffix就行了。现在又有一些需求,例如需要检索utf8格式的txt或者h264编码的mp4,也会有一些例如查找最近一周更新过的文件这样的需求,你如何优化这个类,让它满足这些情况? 我首先想到的是这么多需求不可能一个方法就完成,需要根据不同场景拆出不同的方法,但是这些同属于文件操作,会有一个共同使用的方法就是检索文件。这个方法需要传入文件目录,然后递归的返回当前目录所有文件路径。外部不同场景的调用逻辑就用一个enum完成,不同值对应相同范围的不同种类。 面试官比较关注内部共用的文件检索怎么写,他说子文件如果过多怎么办,如何优化。我有点懵,查找文件至少是要遍历一遍的,子文件过多,这个应该是没法优化的啊。中间卡了一段时间,后来他给了提示说是不是可以用block实现,将文件路径返回出去,由外部决定当前文件是否可用,最终外部的调用类是这个样子。 //我的方案 //func findDir(_ dir: String) -> [String] //block方案 func findDir(_ dir: String, block: ((String) -> Bool))我想来确实没毛病,用block返回内容至少不会将该目录的所有文件都由一个对象持有,而前面一堆的铺垫其实也都是为验证block方案的好处。 其实事后想下这个问题没啥难的,这种写法自己也有写过,但当时就是没想起来,可能前面一圈的铺垫给我带偏了吧,说亏也不亏,以后多多努力吧。 总结 整体来看,快手的面试题跟我在别处看到的iOS面试题对比要简单些,一面主要是基础知识,二面考察更全面一些,更多让自己谈一些对技术的理解,三面则是更偏实践一些。 算法虽然三轮都有,但相对比较简单,即使写不出来,有思路也是可以的。当然写出来肯定是加分项,所以大家准备面试时,应该都看一下。算法相关的,排序,数组,二叉树,这几类是重点。
面试题总结(From J_Knight)
- 30 Jun, 2017
测试前一段时间看了J_Knight的2017年5月iOS找人心得(附面试题)。作为一个在编程前线奋斗了将近两年的iOS从业人员,面对这些题目时,有些竟感觉生疏,甚至答不上来,很是惭愧。个人感觉,像runtime、线程、信号量相关的偏底层知识虽然平时基本用不到。特别是很多人可能都没参与过稍复杂项目的开发,优化,这些内容对于很多新手iOS开发来说只存在于理论。但并不是说这些知识不重要,相反,它是我们进阶的必经之路。此篇文章的目的一方面自己整理,一方面希望和大家共同学习进步。以下内容多数为整理,时间仓促可能有不准确的地方,如果缺漏,欢迎指正。部分答案出处:iOSInterviewQuestions 基础 为什么说Objective-C是一门动态语言? 动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如Ruby、Python等就是动态语言,而C、C++等语言则不属于动态语言。 所谓的动态类型语言,意思就是类型的检查是在运行时做的。 1、动态类型。 如id类型。实际上静态类型因为其固定性和可预知性而使用得更加广泛。静态类型是强类型,而动态类型属于弱类型。运行时决定接收者。 2、动态绑定。让代码在运行时判断需要调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法。 3、动态载入。让程序在运行时添加代码模块以及其他资源。用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有和程序运行时整合的新类。 讲一下MVC和MVVM,MVP iOS 架构模式--解密 MVC,MVP,MVVM以及VIPER架构 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别? 防止循环引用。 另外,不建议使用assign weak 当计数器为0 时对象被释放,地址指针就置为了nil 了。 assign 当计数器为0 时 对象被释放,地址指针还是指向那个地址,就会产生野指针 datasource协议里面东西是跟内容有关的,主要是cell的构造函数,各种属性 delegate协议里面的方法主要是操作相关的,移动编辑之类的,你都写上要用什么方法自己去翻就是了 delegate控制的是UI,是上层的东西;而datasource控制的是数据。他们本质都是回调,只是回调的对象不同。 block 和 delegate 都可以通知外面。block 更轻型,使用更简单,能够直接访问上下文,这样类中不需要存储临时数据,使用 block 的代码通常会在同一个地方,这样读代码也连贯。delegate 更重一些,需要实现接口,它的方法分离开来,很多时候需要存储一些临时数据,另外相关的代码会被分离到各处,没有 block 好读。 多个相关方法,避免循环引用,建议用delegate。 临时性的,只在栈中,需要存储,只调用一次,一个完成周期用block 属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的? 属性的本质就是实现实例变量和存取方法。 @property = ivar + getter + setter;对应基本数据类型默认关键字是atomic,readwrite,assign 对于普通的 Objective-C 对象atomic,readwrite,strong@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var; @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。 @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。 NSString为什么要用copy关键字,如果用strong会有什么问题? 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性. 如何令自己所写的对象具有拷贝功能? 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。 可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么? [immutableObject copy] // 浅复制 [immutableObject mutableCopy] //深复制 [mutableObject copy] //深复制 [mutableObject mutableCopy] //深复制但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制 为什么IBOutlet修饰的UIView也适用weak关键字? 因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。 不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系 nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现? atomic会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销。 一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 atomic,也还是会读到不同的属性值。 UICollectionView自定义layout如何实现?新建一个类继承UICollectionViewFlowLayout,实现prepareLayout方法。 新建UICollectionViewFlowLayout类,设置属性。 实现UICollectionViewDelegateFlowLayout方法。用StoryBoard开发界面有什么弊端?如何避免?难以维护 性能瓶颈 错误定位不准确解决多Storyboard协作弊端,就是尽量将项目的界面分割在多个Storyboard文件中。一个最佳实践是,按照项目功能模块来区分故事板,例如Login.Storyboard,Chat.Storyboard,Person.Storyboard等。尽量把每个Storyboard的Scene数量控制在20个以内 进程和线程的区别?同步异步的区别?并行和并发的区别? 一个进程可以包括多个线程。一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。 一个程序至少有一个进程,一个进程至少有一个线程. 线程只能归属于一个进程并且它只能访问该进程所拥有的资源。 同步会造成阻塞,异步非阻塞,网络请求操作。 一个形象的例子: 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』。 “并行”概念是“并发”概念的一个子集 线程间的通信 NSThread可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在主线程执行的方法 GCD的一些常用的函数?(group,barrier,信号量,线程同步)group 队列组通知监听函数(异步函数)dispatch_group_notify 队列组等待函数(同步函数)dispatch_group_wait 应用场景:下载两张图片,拼接图片后到主线程中刷新barrier 栅栏函数执行顺序:栅栏函数之前的任务(执行完毕)--> 栅栏函数的任务(执行完毕)--> 栅栏函数之后的任务 栅栏函数前面和后面追加的操作执行顺序都不固定,但是前面的三个输出操作必然先执行,然后再执行栅栏函数中的操作,最后执行后面的三个输出操作。 栅栏函数信号量 信号量大小是用于控制并发数量的 信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理线程同步: 线程同步:@synchronized NSLock dispatch_semaphore(信号量设置为1)如何使用队列来避免资源抢夺? 当我们使用多线程来访问同一个数据的时候,就有可能造成数据的不准确性。这个时候我么可以使用线程锁的来来绑定。也是可以使用串行队列来完成。 如:fmdb就是使用FMDatabaseQueue,来解决多线程抢夺资源。 数据持久化的几个方案(fmdb用没用过)plist CoreData FMDB说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法? ```//第一次启动: didFinishLaunchingWithOptions: applicationDidBecomeActive: //前台到后台: applicationWillResignActive: applicationDidEnterBackground: //后台到前台: applicationWillEnterForeground: applicationDidBecomeActive:```NSCache优于NSDictionary的几点?当系统资源将要耗尽时,NSCache具备自动删减缓冲的功能。并且还会先删减“最久未使用”的对象。 NSCache不拷贝键,而是保留键。因为并不是所有的键都遵从拷贝协议(字典的键是必须要支持拷贝协议的,有局限性)。 NSCache是线程安全的:不编写加锁代码的前提下,多个线程可以同时访问NSCache。知不知道Designated Initializer?使用它的时候有什么需要注意的问题? 便利初始化函数只能调用自己类中的其他初始化方法 指定初始化函数才有资格调用父类的指定初始化函数 构造便利函数 实现description方法能取到什么效果? description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址),可定义输出自己想要的内容。 objc使用什么机制管理对象内存? 1.Objective-C中所有对象都在堆区建立,由程序员负责释放对象所占用的内存。内存管理机制由3种:垃圾回收、引用计数、C语言方式。 2.垃圾回收是Mac OS10.5提供的新方案,在系统存在一个垃圾收集器。如果发现某个对象没有被任何对象使用,该对象被自动释放。 3.C语言方式,原始内存管理方式。用户手动调用malloc、calloc函数分配内存,free回收内存。 4.引用计数机制:对象创建后,运行时系统通过对象维护的一个计数器来描述有多少个其他对象在使用自己,当计数器为0时,释放该对象占用的内存空间(该对象调用dealloc方法)。 5,内存管理规则:当使用alloc,new或copy创建一个对象时,对象的引用计数被设置为1.;向对象发送retain消息,对象引用计数加1;向对象发送release消息时,对象引用计数减1;当对象引用计数为0时,运行时系统向对象发送dealloc消息并回收对象所占用的内存。 中级 Block block的实质是什么?一共有几种block?都是什么情况下生成的? Block是iOS开发中一种比较特殊的数据结构,它可以保存一段代码,在合适的地方再调用,具有语法简介、回调方便、编程思路清晰、执行效率高等优点,受到众多猿猿的喜爱。_NSConcreteGlobalBlock: 存储在全局数据区 _NSConcreteStackBlock: 存储在栈区 _NSConcreteMallocBlock: 存储在堆区 其中,_NSConcreteGlobalBlock 和 _NSConcreteStackBlock 可以由程序创建,而 _NSConcreteMallocBlock 则无法由程序创建,只能由 _NSConcreteStackBlock 通过拷贝生成。为什么在默认情况下无法修改被block捕获的变量? __block都做了什么? Block不允许修改外部变量的值。Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。 又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。于是栈区变成了红灯区,堆区变成了绿灯区。 将变量由栈区移到堆区。 模拟一下循环引用的一个情况?block实现界面反向传值如何实现? 一个对象中强引用了block,在block中又强引用了该对象,就会发射循环引用。 解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用. id weak weakSelf = self; 或者 weak __typeof(&*self)weakSelf = self该方法可以设置宏 id __block weakSelf = self; 或者将其中一方强制制空 xxx = nil。 Runtime objc在向一个对象发送消息时,发生了什么?通过对象的isa指针获取类的结构体。 在结构体的方法表里查找方法的selector。 如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到父类,并在父类的方法表里查找方法的selector。 依次会一直找到NSObject。 一旦找到selector,就会获取到方法实现IMP。 传入相应的参数来执行方法的具体实现。 如果最终没有定位到selector,就会走消息转发流程。什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步? 找不到执行方法。动态方法解析 对象接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。 备用接收者 如果这个方法返回一个对象,则这个对象会作为消息的新接收者。注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector,则应该 return [super forwardingTargetForSelector:aSelector]。 完整消息转发 这是最后一次机会将消息转发给其它对象。创建一个表示消息的NSInvocation对象,把与消息的有关全部细节封装在anInvocation中,包括selector,目标(target)和参数。在forwardInvocation 方法中将消息转发给其它对象。能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么? 不能向编译后得到的类中增加实例变量; 能向运行时创建的类中添加实例变量; 解释下: 因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量; 运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair之前,原因同上。 runtime如何实现weak变量的自动置nil? runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。 给类添加一个属性后,在类结构体里哪些元素会发生变化?instance_size :实例的内存大小 objc_ivar_list *ivars:属性列表 RunLoop runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢? 循环检测线程任务。为了在我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。主线程的run loop默认是启动的。对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。 NSRunLoop *runloop = [NSRunLoop currentRunLoop];runloop的mode是用来做什么的?有几种mode? model 主要是用来指定事件在运行循环中的优先级的,分为: NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态 UITrackingRunLoopMode:ScrollView滑动时 UIInitializationRunLoopMode:启动时 NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合 苹果公开提供的 Mode 有两个: NSDefaultRunLoopMode(kCFRunLoopDefaultMode) NSRunLoopCommonModes(kCFRunLoopCommonModes) 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了? RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响ScrollView的滑动。 如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。 同时因为mode还是可定制的,所以: Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。 //将timer添加到NSDefaultRunLoopMode中 [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES]; //然后再添加到NSRunLoopCommonModes里 NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];苹果是如何实现Autorelease Pool的? autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成. objc_autoreleasepoolPush objc_autoreleasepoolPop objc_autorelease 看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。 举例说明:我们都知道用类方法创建的对象都是 Autorelease 的,那么一旦 Person 出了作用域,当在 Person 的 dealloc 方法中打上断点,我们就可以看到这样的调用堆栈信息:备注:Objective-C Autorelease Pool 的实现原理 可能苹果在ARC的处理上又优化了,或者变更了。我自己测试的结果跟以上分析不一致,但原理可以参考。 类结构 isa指针?(对象的isa,类对象的isa,元类的isa都要说) 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中) 成员变量的列表, 属性列表, 它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。 类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。类方法和实例方法有什么区别?类方法: 类方法是属于类对象的 类方法只能通过类对象调用 类方法中的self是类对象 类方法可以调用其他的类方法 类方法中不能访问成员变量 类方法中不能直接调用对象方法实例方法: 实例方法是属于实例对象的 实例方法只能通过实例对象调用 实例方法中的self是实例对象 实例方法中可以访问成员变量 实例方法中直接调用实例方法 实例方法中也可以调用类方法(通过类名)介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法? 分类可以在不知道系统类源代码的情况下,为这个类添加新的方法。分类只能用来添加方法,不能添加成员变量。通过分类增加的方法,系统会认为是该类类型的一部分 Category实现原理 运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么? 可以添加属性,不可以添加成员变量。 OC类成员变量深度剖析 objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体) 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如: Person *motherInlaw = [[aPerson spouse] mother];如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。高级 UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染) 一般在网络请求结束后,在更新界面之前就把每个 cell 的高度算好,缓存到相对应的 model 中。 另外绘制 cell 不建议使用 UIView,建议使用 CALayer。 简单的形式参考: ```//异步绘制 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ CGRect rect = CGRectMake(0, 0, 100, 100); UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0); CGContextRef context = UIGraphicsGetCurrentContext(); [[UIColor lightGrayColor] set]; CGContextFillRect(context, rect); //将绘制的内容以图片的形式返回,并调主线程显示 UIImage *temp = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // 回到主线程 dispatch_async(dispatch_get_main_queue(), ^{ //code }); });```CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在 GPU 中。当一个列表视图中出现大量圆角的 CALayer,并且快速滑动时,可以观察到 GPU 资源已经占满,而 CPU 资源消耗很少。这时界面仍然能正常滑动,但平均帧数会降到很低。为了避免这种情况,可以尝试开启 CALayer.shouldRasterize 属性,但这会把原本离屏渲染的操作转嫁到 CPU 上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。