前段时间更新了一篇 给ios中高级面试官的一份招聘要求 收到很多小伙伴的点赞与关注。可能有很多小伙伴已经带着我在那篇文章给大家提供的一些面试技巧 & 其中的面试题 已经开始招聘或者应聘了!这里应大家要求,对里面的面试题提供相关答案!相信无论是面试官还是求职者都是有所收获的~~
ps:篇幅有点长,大家可以关注或者点赞收藏以备不时之需!!!
1:讲讲你对atomic & nonatomic的理解
2:被 weak 修饰的对象在被释放的时候会发生什么?是如何实现的?知道sidetable 么?里面的结构可以画出来么?
被weak修饰的对象在被释放时候会置为nil,不同于assign;
runtime 维护了一个 weak表,用于存储指向某个对象的所有 weak指针。weak表 其实是一个 hash(哈希)表,key 是所指对象的地址,value 是 weak指针 的地址(这个地址的值是所指对象指针的地址)数组。
struct sidetable { // 保证原子操作的自旋锁 spinlock_t slock; // 引用计数的 hash 表 refcountmap refcnts; // weak 引用全局 hash 表 weak_table_t weak_table; } struct weak_table_t { // 保存了所有指向指定对象的 weak 指针 weak_entry_t *weak_entries; // 存储空间 size_t num_entries; // 参与判断引用计数辅助量 uintptr_t mask; // hash key 最大偏移值 uintptr_t max_hash_displacement; };
3:`block` 用什么修饰?strong 可以?
4:block 为什么能够捕获外界变量? __block 做了什么事?
研究block的捕获外部变量就要除去函数参数这一项,下面一一根据这4种变量类型的捕获情况进行分析。
首先 全局变量global_i 和 静态全局变量static_global_j 的值增加,以及它们被 block 捕获进去,这一点很好理解,因为是全局的,作用域很广,所以 block 捕获了它们进去之后,在 block 里面进行 ++ 操作, block 结束之后,它们的值依旧可以得以保存下来。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; __block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
__main_block_impl_0结构体 就是这样把自动变量捕获进来的。也就是说,在执行 block 语法的时候, block 语法表达式所使用的自动变量的值是被保存进了 block 的结构体实例中,也就是 block 自身中。
这里值得说明的一点是,如果 block 外面还有很多自动变量,静态变量,等等,这些变量在 block 里面并不会被使用到。那么这些变量并不会被 block 捕获进来,也就是说并不会在构造函数里面传入它们的值。
`block`捕获外部变量仅仅只捕获`block`闭包里面会用到的值,其他用不到的值,它并不会去捕获。
5:谈谈你对事件的传递链和响应链的理解
回到响应链,响应链是由 uiresponser 组成的,那么是按照哪种规则形成的。
我们使用一个现实场景来解释这个问题:当一个用点击屏幕上的一个按钮,这个过程具体发生了什么。
通过两种方法来做这个事情。
// 先判断点是否在view内部,然后遍历subviews - (nullable uiview *)hittest:(cgpoint)point withevent:(nullable uievent *)event; //判断点是否在这个view内部 - (bool)pointinside:(cgpoint)point withevent:(nullable uievent *)event; // default returns yes if point is in bounds
6:谈谈 kvc 以及 kvo 的理解?
7:runloop 的作用是什么?它的内部工作机制了解么?
字面意思是“消息循环、运行循环”,runloop 内部实际上就是一个 do-while循环 ,它在循环监听着各种事件源、消息,对他们进行管理并分发给线程来执行。
8:苹果是如何实现 autoreleasepool 的?
arc下编译器会优化成
void *context = objc_autoreleasepoolpush(); // {}中的代码 objc_autoreleasepoolpop(context);
9:谈谈你对 frp (函数响应式) 的理解,延伸一下 rxswift 或者 rac !
[参考文章:rxswift(1)— 初探] 看这一篇文章也就够了!然后结合 rxswift 映射到 rac !函数响应式的思想是不变的!至于内部的封装有所不同,但是最终却是殊途同归!
10:平时开发有没有玩过 instrument ?
分析:这里的内容非常有意思,对于一个ios高级开发人员,我觉得还有很有必要掌握的!尤其开发3-5年,如果没有掌握这些内容我觉得是不合格的
我个人建议在掌握面试题的同时还需要求职者更多的去分析和拓展!比如你的探索思路,你在这个知识点意外的延伸。还有你再实际开发过程的落地!而这些都是加分项!
1:什么是 isa,isa 的作用是什么?
2:一个实例对象的 isa 指向什么?类对象指向什么?元类 isa 指向什么?
类方法:
实例方法:
4: load 和 initialize 的区别?
+load
+initialize
5: _objc_msgforward 函数是做什么的?直接调用会发生什么问题?
当对象没有实现某个方法 ,会调用这个函数进行方法转发。 (某方法对应的`imp`没找到,会返回这个函数的`imp`去执行)
如果直接调用这个方法,就算实现了想调用的方法,也不会被调用,会直接走消息转发步骤。
6:简述下 `objective-c` 中调用方法的过程
ps: runtime 铸就了 objective-c 是动态语言的特性,使得c语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。
7:能否想向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
解释:
8:谈谈你对切面编程的理解
维基百科对于切面编程(aop)的解释是这样的:面向切面的程序设计( aspect-oriented programming,aop,又译作面向侧面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为切面的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类、函数)中的横切关注点。[参考文章]
分析:runtime 这个模块ios面试无论初中高都会面试。我觉得这个模块不光只是仅仅问问关于知识点内容,我更新想要听到求职者在这里面的爬坑探索辛历路程!runtime 这个模块是刷开页面开发的关键点!
1:http的缺陷是什么?
http 主要有这些不足,例举如下。
这些问题不仅在 http上出现,其他未加密的协议中也会存在这类问题。
2:谈谈三次握手,四次挥手!为什么是三次握手,四次挥手?
[参考文章] 我觉得这个地方还是需要自我理解,用自己的话去表达出来!
3: socket 连接和 http 连接的区别
http 是基于 socket 之上的。socket 是一套完整的 tcp,udp 协议的接口。
tcp/ip是传输层协议,主要解决数据如何在网络中传输,而http协议是应用层协议,主要解决如何包装数据。
socket是对tcp/ip 协议的封装,它本身不是协议,而是一个调用接口,通过 socket ,我们才能使用 tcp/ip协议 。
http 是客户端用 http 协议进行请求,发送请求时候需要封装 http 请求头,并绑定请求的数据,服务器一般有 web 服务器配合。 http 请求方式为客户端主动发起请求,服务器才能给响应,一次请求完毕后则断开连接以节省资源。服务器不能主动给客户端响应。 iphone 主要使用的类是 nsurlconnection 。 socket 是客户端跟服务器直接使用 socket“套接字” 进行拼接,并没有规定连接后断开,所以客户端和服务器可以保持连接,双方都可以主动发送数据。一般在游戏开发或者股票开发这种即时性很强的并且保持发送数据量比较大的场合使用。主要类是 cfsocketref。
4:https,安全层除了ssl还有,最新的? 参数握手时首先客户端要发什么额外参数
5:什么时候pop网络,有了 `alamofire` 封装网络 `urlsession`为什么还要用`moya` ?
pop网络:面向协议编程的网络能够大大降低耦合度!网络层下沉,业务层上浮。中间利用 pop网络 的 moya 隔开。如果你的项目是 rxswift 函数响应式的也没有关系!因为有 rxmoya
参考文章:
6:如何实现 dispatch_once
+ (instancetype)sharedinstance { /*定义相应类实例的静态变量; 意义:函数内定义静态变量,无论该函数被调用多少次, 在内存中只初始化一次,并且能保存最后一次赋的值 */ static classname *instance = nil; /*定义一个dispatch_once_t(其实也就是整型)静态变量, 意义:作为标识下面dispatch_once的block是否已执行过。 static修饰会默认将其初始化为0,当值为0时才会执行block。 当block执行完成,底层会将oncetoken设置为1,这也就是为什 么要传oncetoken的地址(static修饰的变量可以通过地址修改 oncetoken的值),同时底层会加锁来保证这个方法是线程安全的 */ static dispatch_once_t oncetoken; /*只要当oncetoken == 0时才会执行block,否则直接返回静态变量instance*/ dispatch_once(&oncetoken, ^{ instance = [[classname alloc] init]; //... }); return instance; }
[ios原理之cgd-dispatch_once的底层实现]
7:能否写一个读写锁?谈谈具体的分析
8:什么时候会出现死锁?如何避免?
9:有哪几种锁?各自的原理?它们之间的区别是什么?最好可以结合使用场景来说
分析:这个模块可能是一般开发人员的盲区。对于这一块一定要有自己的理解!学习的方向就是查漏补缺,一步一个吃掉!如果你一整块去啃,你会发现很枯燥!虽然开发过程中你可能用不到,但是面试这一块是你必须要掌握的!
1.数据结构的存储一般常用的有几种?各有什么特点?
数据的存储结构是数据结构的一个重要内容。在计算机中,数据的存储结构可以采取如下四中方法来表现。
索引存储方式
散列存储方式是根据结点的关键字直接计算出该结点的存储地址的一种存储的方式。 在实际应用中,往往需要根据具体数据结构来决定采用哪一种存储方式。同一逻辑结构采用不同额存储方法,可以得到不同的存储结构。而且这四种节本存储方法,既可以单独使用,也可以组合起来对数据结构进行存储描述。
2.集合结构 线性结构 树形结构 图形结构 3.单向链表 双向链表 循环链表 4.数组和链表区别 5.堆、栈和队列
- [ios常用算法和数据结构]
- [数据结构初探]这里面介绍了很多数据结构,大家还可以自行查阅更多资料
6.输入一棵二叉树的根结点,求该树的深度?
如果一棵树只有一个结点,它的深度为1。 如果根结点只有左子树而没有右子树, 那么树的深度应该是其左子树的深度加1,同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1\. 如果既有右子树又有左子树, 那该树的深度就是其左、右子树深度的较大值再加1。
public static int treedepth(binarytreenode root) { if (root == null) { return 0; } int left = treedepth(root.left); int right = treedepth(root.right); return left > right ? (left + 1) : (right + 1); }
7.输入一课二叉树的根结点,判断该树是不是平衡二叉树?
1.时间复杂度
在[计算机科学]中,时间复杂性,又称时间复杂度,[算法],它定性描述该算法的运行时间。这是一个代表算法输入值的[字符串]的长度的函数。时间复杂度常用[大o符号]表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是[渐近]的,亦即考察输入值大小趋近无穷时的情况。 [时间复杂性]
2.空间复杂度
空间复杂度(space complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做s(n)=o(f(n))。比如直接[插入排序]的[时间复杂度],空间复杂度是o(1) 。而一般的[递归]算法就要有o(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面[衡量]。 [时间复杂度&空间复杂度]
3.常用的排序算法
4.字符串反转
- (nsstring *)reversalstring:(nsstring *)originstring{ nsstring *resultstr = @""; for (nsinteger i = originstring.length -1; i >= 0; i--) { nsstring *indexstr = [originstring substringwithrange:nsmakerange(i, 1)]; resultstr = [resultstr stringbyappendingstring:indexstr]; } return resultstr; }
5.链表反转(头差法)
public node reverselist(){ node cur = head; node prev = null; node curnext = head.next; node reverhead = null; while(cur!=null){ cur.next = prev; cur = curnext; prev = cur; curnext = curnext.next; } reverhead = cur; return reverhead; }
6.有序数组合并
objc - (void)merge { /* 有序数组a:1、4、5、8、10...1000000,有序数组b:2、3、6、7、9...999998,a、b两个数组不相互重复,请合并成一个有序数组c,写出代码和时间复杂度。 */ //(1). nsmutablearray *a = [nsmutablearray arraywithobjects:@4,@5,@8,@10,@15, nil]; // nsmutablearray *b = [nsmutablearray arraywithobjects:@2,@6,@7,@9,@11,@17,@18, nil]; nsmutablearray *b = [nsmutablearray arraywithobjects:@2,@6,@7,@9,@11,@12,@13, nil]; nsmutablearray *c = [nsmutablearray array]; int count = (int)a.count+(int)b.count; int index = 0; for (int i = 0; i < count; i++) { if (a[0]<b[0]) { [c addobject:a[0]]; [a removeobject:a[0]]; } else if (b[0]<a[0]) { [c addobject:b[0]]; [b removeobject:b[0]]; } if (a.count==0) { [c addobjectsfromarray:b]; nslog(@"c = %@",c); index = i+1; nslog(@"index = %d",index); return; } else if (b.count==0) { [c addobjectsfromarray:a]; nslog(@"c = %@",c); index = i+1; nslog(@"index = %d",index); return; } } //(2). //时间复杂度 //t(n) = o(f(n)):用"t(n)"表示,"o"为数学符号,f(n)为同数量级,一般是算法中频度最大的语句频度。 //时间复杂度:t(n) = o(index); }
7.查找第一个只出现一次的字符(hash查找)
两个思路:
# define size 256 char getchar(char str[]) { if(!str) return 0; char* p = null; unsigned count[size] = {0}; char buffer[size]; char* q = buffer; for(p=str; *p!=0; p++) { if(++count[(unsigned char)*p] == 1) *q++ = *p; } for (p=buffer; p<q; p++) { if(count[(unsigned char)*p] == 1) return *p; } return 0; }
8.查找两个子视图的共同父视图
这个问的其实是数据结构中的二叉树,查找一个普通二叉树中两个节点最近的公共祖先问题 假设两个视图为 uiviewa 、 uiviewc ,其中 uiviewa 继承于 uiviewb , uiviewb 继承于 uiviewd , uiviewc 也继承于 uiviewd ;即 a->b->d,c->d
- (void)viewdidload { [super viewdidload]; class commonclass1 = [self commonclass1:[viewa class] andclass:[viewc class]]; nslog(@"%@",commonclass1); // 输出:2018-03-22 17:36:01.868966+0800 两个uiview的最近公共父类[84288:2458900] viewd } // 获取所有父类 - (nsarray *)superclasses:(class)class { if (class == nil) { return @[]; } nsmutablearray *result = [nsmutablearray array]; while (class != nil) { [result addobject:class]; class = [class superclass]; } return [result copy]; } - (class)commonclass1:(class)classa andclass:(class)classb { nsarray *arr1 = [self superclasses:classa]; nsarray *arr2 = [self superclasses:classb]; for (nsuinteger i = 0; i < arr1.count; ++i) { class targetclass = arr1[i]; for (nsuinteger j = 0; j < arr2.count; ++j) { if (targetclass == arr2[j]) { return targetclass; } } } return nil; }
- (class)commonclass2:(class)classa andclass:(class)classb{ nsarray *arr1 = [self superclasses:classa]; nsarray *arr2 = [self superclasses:classb]; nsset *set = [nsset setwitharray:arr2]; for (nsuinteger i =0; i<arr1.count; ++i) { class targetclass = arr1[i]; if ([set containsobject:targetclass]) { return targetclass; } } return nil; }
9.无序数组中的中位数(快排思想)
10.给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。 示例:给定 nums = [2, 7, 11, 15], target = 9 --- 返回 [0, 1] 思路:
class solution { public int[] twosum(int[] nums, int target) { int len = nums.length; int[] result = new int[2]; for(int i = 0; i < len; i++){ for(int j = i+1; j < len; j++){ if(nums[i] + nums[j] == target){ result[0] = i; result[1] = j; return result; } } } return result; } }
分析:这个模块是绝大部分开发人员的软肋!这个模块是最能测试求职者思维能力的!但是我不建议面试官直接让求职者手写 在那样的面试紧张环境,手写数据结构或者一些算法代码,是非常有挑战的!思维到我觉得差不多!
1:设计模式是为了解决什么问题的?
设计模式(design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
设计模式最主要解决的问题是通过封装和隔离变化点来处理软件的各种变化问题。 隔离变化的好处在于,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。很多设计模式的意图中都明显地指出了其对问题的解决方案,学习设计模式的要点是发现其解决方案中封装的变化点。
三本经典书籍:[《gof设计模式》],[《设计模式解析》],《head first design pattern》
设计模式是软件开发领域的精髓之一。学好设计模式是目前每一个开发人员的必修课,
2:看过哪些第三方框架的源码,它们是怎么设计的?
这个题目就看你个人的感触,考量你平时的功底! 大家可以针对性一些常见的框架: rxswift 、 alamofire 、 moya 、 afnetworing 、 yykit .... 掌握会用的同时,必须要掌握底层的核心思想!
3:可以说几个重构的技巧么?你觉得重构适合什么时候来做?
在新功能增加时候,在扩展不再简单的时候。重构是一个不断的过程。
4:开发中常用架构设计模式你怎么选型?
这里也是一道开放性题目!并不是说某一种架构就是最优秀的~只有最合适的!根据公司情况,项目现状,以及开发者水平及时调整,设计!
5:你是如何组件化解耦的?
ios 解藕 、组件化最常用的是使用统跳路由的方式,目前比较常用的 ios 开源路由框架主要是 jlroutes 、 mgjrouter 、 hhrouter 等,这些路由框架各有优点和缺点,基本可以满足大部分需求。目前最常用来作路由跳转,以实现基本的组件化开发,实现各模块之间的解藕。但是,在实际中开发中会发现,无法彻底使用它们完成所有模块间通信,比如模块间的同步、异步通信等。再比如,我们在配置了相关路由跳转的 url 后,如何在上线之后动态修改相关跳转逻辑?在模块间通信时,如何在上线后动态修改相关参数?app 能否实现类似 web 的302跳转 ?[学习参考]
分析:架构设计这一层对于一个ios中高级开发人员来说。这一块那是他必须要去思考和感受总结的!如果这位求职者开发4-5年了,一直都在做应用层界面开发,那么想必他未来的职业晋升是已经落后了的!面试官不妨在这一个模块单独设计成一面,就和求职者一起交流讨论。毕竟这些思维的设计,也许能够给面试官带来一些不一样的东西!
您可能感兴趣的文章:
如对本文有疑问, 点击进行留言回复!!
出现The style on this component requires your app theme to be Theme.MaterialComponents错误
Codeforces Round #662 (Div. 2) D. Rarity and New Dress
CodeForces 1393C Pinkie Pie Eats Patty-cakes
网友评论