本文主要是介绍给iOS中高级求职者的一份面试题解答!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前段时间更新了一篇 给iOS中高级面试官的一份招聘要求收到很多小伙伴的点赞与关注。可能有很多小伙伴已经带着我在那篇文章给大家提供的一些面试技巧 & 其中的面试题 已经开始招聘或者应聘了!这里应大家要求,对里面的面试题提供相关答案!相信无论是面试官还是求职者都是有所收获的~~
PS:篇幅有点长,大家可以关注或者点赞收藏以备不时之需!!!
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
iOS基础
1:讲讲你对
atomic&nonatomic的理解
- 1、原子操作对线程安全并无任何安全保证。被
atomic修饰的属性(不重载设置器和访问器)只保证了对数据读写的完整性,也就是原子性,但是与对象的线程安全无关。 - 2、线程安全有保障、对性能有要求的情况下可使用
nonatomic替代atomic,当然也可以一直使用atomic。 - 3:详细参考
2:被
weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
被weak修饰的对象在被释放时候会置为nil,不同于assign;
Runtime 维护了一个 weak表,用于存储指向某个对象的所有weak指针。weak表 其实是一个 hash(哈希)表,Key 是所指对象的地址,Value是 weak指针 的地址(这个地址的值是所指对象指针的地址)数组。
- 1、初始化时:
runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。 - 2、添加引用时:
objc_initWeak函数会调用objc_storeWeak() 函数,objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。 - 3、释放时,调用
clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。 - 4:详细参考
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

我们使用一个现实场景来解释这个问题:当一个用点击屏幕上的一个按钮,这个过程具体发生了什么。
-
- 用户触摸屏幕,系统硬件进程会获取到这个点击事件,将事件简单处理封装后存到系统中,由于硬件检测进程和当前App进程是两个进程,所以进程两者之间传递事件用的是端口通信。硬件检测进程会将事件放到
APP检测的那个端口。
- 用户触摸屏幕,系统硬件进程会获取到这个点击事件,将事件简单处理封装后存到系统中,由于硬件检测进程和当前App进程是两个进程,所以进程两者之间传递事件用的是端口通信。硬件检测进程会将事件放到
-
2.
APP启动主线程RunLoop会注册一个端口事件,来检测触摸事件的发生。当事件到达,系统会唤起当前APP主线程的RunLoop。来源就是App主线程事件,主线程会分析这个事件。 -
3.最后,系统判断该次触摸是否导致了一个新的事件, 也就是说是否是第一个手指开始触碰,如果是,系统会先从响应网中 寻找响应链。如果不是,说明该事件是当前正在进行中的事件产生的一个
Touch message, 也就是说已经有保存好的响应链 -
二:事件传递链
通过两种方法来做这个事情。
// 先判断点是否在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
这篇关于给iOS中高级求职者的一份面试题解答!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!
