给iOS中高级求职者的一份面试题解答,java中高级面试题
给iOS中高级求职者的一份面试题解答,java中高级面试题
前段时间更新了一篇 给iOS中高级面试官的一份招聘要求 收到很多小伙伴的点赞与关注。可能有很多小伙伴已经带着我在那篇文章给大家提供的一些面试技巧 & 其中的面试题 已经开始招聘或者应聘了!这里应大家要求,对里面的面试题提供相关答案!相信无论是面试官还是求职者都是有所收获的~~
PS:篇幅有点长,大家可以关注或者点赞收藏以备不时之需!!!
iOS基础
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 可以?
- block 本身是像对象一样可以 retain,和 release。但是,block 在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。
- 使用 retain 也可以,但是block的retain行为默认是用copy的行为实现的
- 因为 block 变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把 block 拷贝(copy)到堆,所以说为了 block 属性声明和实际的操作一致,最好声明为 copy。
- [详细参考]
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 包括了各种 Touch message 的处理,比如开始,移动,停止等等。常见的 UIResponser 有 UIView及子类 ,UIViController , APPDelegate , UIApplication 等等。
回到响应链,响应链是由 UIResponser 组成的,那么是按照哪种规则形成的。
- A: 程序启动 UIApplication 会生成一个单例,并会关联一个 APPDelegate 。 APPDelegate 作为整个响应链的根建立起来,而 UIApplication 会将自己与这个单例链接,即 UIApplication 的 nextResponser (下一个事件处理者)为 APPDelegate 。
- B:创建UIWindow 程序启动后,任何的 UIWindow 被创建时, UIWindow 内部都会把 nextResponser 设置为 UIApplication单例 。 UIWindow 初始化 rootViewController , rootViewController 的 nextResponser 会设置为 UIWindow
- C:UIViewController初始化 loadView , VC 的 view 的 nextResponser 会被设置为 VC .
- D:addSubView : addSubView 操作过程中,如果子subView不是VC的View,那么 subView 的 nextResponser 会被设置为 superView 。如果是 VC 的 View ,那就是 subView -> subView.VC ->superView 如果在中途, subView.VC 被释放,就会变成 subView.nextResponser = superView
// 先判断点是否在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
- A: 流程
- 1:先判断该层级是否能够响应`(1.alpha>0.01 2.userInteractionEnabled == YES 3.hidden = NO)`
- 2:判断改点是否在`view`内部,
- 3:如果在那么遍历子`view`继续返回可响应的view,直到没有。
- B:常见问题
- 父view设置为不可点击,子view可以点击吗
- 不可以,hit test 到父view就截止了
- 子view设置view不可点击不影响父类点击
- 同父view覆盖不影响点击
- 手势对responder方法的影响
- C:实际用法
- 点一一个圆形控件,如何实现只点击圆形区域有效,重载`pointInside`。此时可将外部的点也判断为内部的点,反之也可以。
- 事件响应链在复杂功能界面进行不同控件间的通信,简便某些场景下优于代理和`block`
6:谈谈 KVC 以及 KVO 的理解?
- [KVC和KVO的使用及原理]
- [详解键值观察(KVO)及其实现机理]
- [KVC/KVO原理详解及编程指南]
7:RunLoop 的作用是什么?它的内部工作机制了解么?
- [Runloop是什么?]()
- [深入理解*RunLoop* | Garan no dou]
字面意思是“消息循环、运行循环”,runloop 内部实际上就是一个 do-while循环 ,它在循环监听着各种事件源、消息,对他们进行管理并分发给线程来执行。
- 1.通知观察者将要进入运行循环。 线程和 RunLoop 之间是一一对应的
- 2.通知观察者将要处理计时器。
- 3.通知观察者任何非基于端口的输入源即将触发。
- 4.触发任何准备触发的基于非端口的输入源。
- 5.如果基于端口的输入源准备就绪并等待触发,请立即处理该事件。转到第9步。
- 6.通知观察者线程即将睡眠。
- 7.将线程置于睡眠状态,直到发生以下事件之一:
- 8.通知观察者线程被唤醒。
- 9.处理待处理事件。
- 10.通知观察者运行循环已退出。
8:苹果是如何实现 autoreleasepool 的?
arc下编译器会优化成
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
- 向一个结构 AutoreleasePoolPage,中写入需要自动释放的对象,类似一种标记,调用 objc_autoreleasePoolPop(context) 后,就会把这中间的对象 release 一下。
- 这里要注意的是,方法返回值是怎么做到自动释放的?
- 其使用 Thread Local Storage(TLS) 线程局部存储,每次存入线程或者从线程取出来。
- 我们没有卸载`{}`中的自动释放对象,会在每个 runloop 结束时候去释放,相当于一个大的 autoreleasepool 中。
- [参考文章]
- [苹果是如何实现autoreleasepool的]
9:谈谈你对 FRP (函数响应式) 的理解,延伸一下 RxSwift 或者 RAC !
[参考文章:RxSwift(1)— 初探] 看这一篇文章也就够了!然后结合 RxSwift 映射到 RAC !函数响应式的思想是不变的!至于内部的封装有所不同,但是最终却是殊途同归!
10:平时开发有没有玩过 Instrument ?
- [Instruments性能检测]
- [苹果官网上Instruments User Guide]
分析:这里的内容非常有意思,对于一个iOS高级开发人员,我觉得还有很有必要掌握的!尤其开发3-5年,如果没有掌握这些内容我觉得是不合格的
我个人建议在掌握面试题的同时还需要求职者更多的去分析和拓展!比如你的探索思路,你在这个知识点意外的延伸。还有你再实际开发过程的落地!而这些都是加分项!
Runtime
+ (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:有哪几种锁?各自的原理?它们之间的区别是什么?最好可以结合使用场景来说
- [iOS读写锁的几种方法]
- [iOS开发中的11种锁以及性能对比]
- [参考文章]
分析:这个模块可能是一般开发人员的盲区。对于这一块一定要有自己的理解!学习的方向就是查漏补缺,一步一个吃掉!如果你一整块去啃,你会发现很枯燥!虽然开发过程中你可能用不到,但是面试这一块是你必须要掌握的!
数据结构
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)需要重复遍历节点多次的解法
- (2)每个节点只需遍历一次的解法
- [参考文章]
算法
- (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查找)
两个思路:
- 1: hash �不同编译器对字符数据的处理不一样,所以hash之前先把字符类型转成无符号类型;
- 2,空间换时间,用`buffer数组`记录当前只找到一次的字符,避免二次遍历。
# 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;
}
- 方法一明显的是两层for循环,时间复杂度为 O(N^2) 一个改进的办法:我们将一个路径中的所有点先放进NSSet中.因为NSSet的内部实现是一个hash表,所以查询元素的时间的复杂度变成 O(1) ,我们一共有N个节点,所以总时间复杂度优化到了 O(N)
- (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] 思路:
- 第一层for循环从索引0到倒数第二个索引拿到每个数组元素,
- 第二个for循环遍历上一层for循环拿到的元素的后面的所有元素。
- [参考文章]
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年了,一直都在做应用层界面开发,那么想必他未来的职业晋升是已经落后了的!面试官不妨在这一个模块单独设计成一面,就和求职者一起交流讨论。毕竟这些思维的设计,也许能够给面试官带来一些不一样的东西!
相关文章
暂无相关文章
用户评论