OC面试题:总结
iOS 面试自我介绍
面试官您好:
我叫张建,来自辽宁省大石桥市,毕业于辽宁工业大学,有7年iOS开发工作经验,先后经历了3家互联网公司:
第一家公司是 “北京文字度量科技有限公司” 是一家互联网创业公司,做的项目叫 “榜样” 的 App,是一款直播、交友类的App,面向全国大学生的应用软件
第二家公司是 “同方知网(北京)技术有限公司” 是同方股份有限公司的子公司,主要经营数字出版、知识服务、数字图书馆 领域的企业,做的项目是 “知网阅读App” ,是一个阅读类的App,为用户提供全国精品 “期刊、图书、论文、工具书、有声书、课程等” 资源;
第三家公司是 “中图数字科技(北京)有限公司” 是中国图书进出口(集团有限公司的上市子公司,主要为了实施 “新零售” 发展战略而创建的上市公司,做的项目是 “中图云书房App”,是一款电商类的App,主要为用户提供海量 “英文原版书” 资源,利用按需印刷技术,满足读者个性化定制的需求;
项目面试题
中图云书房
商品详情
采用了 WKWebView
实现,通过OC调JS方法evaluateJavaScript,获取网页的高度,刷新Cell的高度。通过OC调JS方法获取所有图片,保存图片到数组中,自己实现图片预览传入数组进行大图的显示。
发现卡片
通过继承 UICollectionFlowLayout
,重写 layoutAttributesForElementsInRect
获取所有cell的属性数组,计算距离滚动中心点的距离,进行比例缩放
Bang 项目
直播功能怎么实现的?
直播间是通过集成金山云SDK实现的
- 推流
通过吊起 摄像头和麦克风
获取 音视频流
,音频
编码采用 AAC
编码,视频
编码采用 H.265
编码,设置 推流参数
,主要是 视频编码器和音频编码率
的设置,设置完 推流
参数之后,将流媒体推送到 RTMP协议
服务器,平均延迟 2秒
。RTMP协议是一个专门为高效传输视频,音频和数据而设计的协议。它通过建立一个二进制 TCP连接或者连接HTTP隧道
实现实时的视频和声音传输。
- 拉流
金山云 播放器内核
提供 播放器SDK
,在系统播放器 MPMoviePlayerController
基础上,新增了一些功能,采用高性能 H.265
解码器,支持 rmvb/flv/avi
等格式,支持 HLS/rtmp
协议,直播流的播放,实现了低于2s的延迟。
直播间的聊天IM实现?
聊天功能是使用的融云SDK,通过加入聊天室实现的。
融云SDK实现了私聊的功能
自己写的聊天页面,通过
融云接口
传入参数会话类型:单聊、群组。聊天室等
,目标会话ID),消息数量,获取某个会话的内容。发送文本消息、图片消息,监听消息,刷新列表
iOS和JS交互 重点
在 iOS
开发中,iOS
和 JS
交互是每个程序猿必须掌握的技能。iOS8
以后,苹果推出了新框架 WebKit
,使用 WKWebView
替代 UIWebView
。稳定性好、占用内存少,速度更快
。
说道 iOS
和 JS
交互,就不得不提 Hybrid(Hybrid Mobile App)
,即通过 Web
网络技术与 Native
相结合的混合移动应用开发
WKWebView
特性:
1、在性能、稳定性、功能方面有很大提升,直观体现是内存占用变少;
2、支持高达60fps的滚动刷新率以及内置手势
3、支持了更多的HTML5特性;
XML、JSON 重点
在
iOS
开发中,常见的数据传输
格式有两种:JSON和XML
。服务器返回客户端的数据,一般都是
JSON格式或XML格式(文件下载除外)
JSON
由于体积小、传输快等
优点,逐渐成为了主流的数据传输格式
。
JSON、XML 解析 重点
JSON 数据解析
性能最好
的是使用苹果原生的框架
:NSJSONSerialization
XML 数据解析
要想从XML中提取信息,就得解析XML,目前针对XML的解析有两种方式:
- SAX:从根元素开始,按照顺序一个元素一个元素的往下解析,可用于
解析大、小文件
- DOM:一次性将整个XML文档加载到内存中,适合较小的文件
iOS
中解析 XML
有两种:
苹果原生:使用
NSXMLParse
,SAX方式解析,使用简答第三方框架:
libxml2、GDataXML
libxml2
纯c
语音,默认包含在iOS SDK中,同时支持DOM和SAX方式解析GDataXMLDOM
解析,由google
基于libxml2
开发
解析XML大文件建议用:NSXMLParse、libxml2
解析XML小文件上述三种都可以
TCP、UDP、HTTP、HTTPS、Socket、WebSocket、Webservice 重点
必看的内容:
OC学习22:HTTP&HTTPS&Socket&WebSocket&WebService探索
事件传递链和响应链 重点
必看的内容
DNS 是什么? 重点
DNS
,就是 Domain Name System
的缩写,翻译过来就是 域名系统
。是互联网上作为 域名和IP地址相互映射的一个分布式数据库
。DNS
能够使用户更方便的访问互联网,而不用去记住能够被 机器
直接读取的 IP
数串。通过域名最终得到该域名对应的IP地址的过程
叫做 域名解析
(或主机名解析)。
举例:如果你要访问域名 www.bboyzj.cn
,首先要通过 DNS
查出它的 IP
地址是 113.31.107.233
。
Block? 重点
解决循环引用常见的几种方式
【方式一】:
weak-strong-dance
强弱共舞【方式二】:
__block
修饰对象(需要注意的是在block内部需要置空
对象,而且block必须调用
)【方式三】: 传递
对象self
作为block的参数,提供给block内部使用【方式四】: 使用
NSProxy
锁的种类?区别?
【自旋锁】:在自旋锁中,
线程会反复检查变量是否可用
。由于线程这个过程中一致保持执行,所以是一种忙等待
。一旦获取了自旋锁,线程就会一直保持该锁
,直到显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合
是有效
的。对于iOS属性的修饰符atomic
,自带一把自旋锁- OSSpinLock
- atomic
【互斥锁】:
互斥锁
是一种用于多线程编程
中,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制
,该目的是通过将代码切成一个个临界区
而达成- @synchroized
- NSLock
- pthread_mutex
【条件锁】:
条件锁
就是条件变量
,当进程的某些资源要求不满足
时就进入休眠
,即锁住了,当资源被分配到了
,条件锁打开了
,进程继续进行- NSCondition
- NSConditionLock
【递归锁】:递归锁就是
同一个线程可以加锁N次而不会引发死锁
。递归锁是特殊的互斥锁
,即是带有递归性质的互斥锁
- pthread_mutex(recursive)
- NSRecursiveLock
【信号量】:
信号量
是一种更高级的同步机制
,互斥锁
可以说是semaphore在取值0/1时的特例
,信号量可以有更多的取值空间,用来实现更加复杂的同步
,而不是单单的线程间互斥- dispatch_semaphore
【读写锁】:读写锁实际是一种
特殊的自旋锁
。将对共享资源的访问分成读者
和写者
,读者
只对共享资源进行读访问
,写者
则需要对共享资源进行写操作
,这种锁相对于自旋锁而言,能提高并发性
一个读写锁同时只能有一个写者或者多个读者
,但不能即有读者又有写者,在读写锁保持期间也是抢占失效的如果
读写锁当前没有读者,也没有写者
,那么写者可以立刻获取
读写锁,否则它必须自旋
在那里,直到没有任何写者或读者,如果读写锁没有写者,那么读者可以 立即获取读写锁
其实 基本的锁
就包括三类:自旋锁、互斥锁、读写锁
,其他的比如 条件锁、递归锁、信号量
都是 上层的封装和实现
Git 重点
- 回退到某个版本,只回退了
commit
信息
git reset --soft
- 切换到指定版本
git reset --hard 版本id
- 查看以前的提交日志
git reflog
多线程 重点
必看的内容:
OC底层原理25:多线程原理探索
OC底层原理26:GCD之函数与队列
OC底层原理27:GCD之 NSThread & GCD & NSOperation
OC底层原理28:GCD之底层原理分析
内存管理 重点
MRC指的是
手动内存管理
,在开发过程中需要开发者手动去编写内存管理的代码
;ARC指的是
自动内存管理
,在此内存管理模式下由LLVM编译器和OC运行时库生成相应内存管理的代码
。自动释放池:
- 当应用程序启动,系统默认会
开启一条线程
,该线程就是主线程
。 每条线程都包含一个与其对应的自动释放池
,当某条线程被终止的时候,对应该线程的自动释放池会被销毁
。同时,处于该自动释放池的对象将会进行一次release
操作。- 自动释放池
本质
是__AtAutoreleasePool
结构体,包含构造函数和析构函数
- 自动释放池释放时机,主线程通过
runloop
监听Entry和BeforeWaiting
释放旧的,创建新的,子线程是线程退出
Runloop 重点
- 当应用程序启动,系统默认会
开启一条线程
,该线程就是主线程
。 - 每一条
线程
都有一个与之对应runloop对象
。主线程
的Runloop
系统自动获取(创建)。子线程默认没有开启runloop。runloop在线程销毁时销毁。
OC学习38.1:Runloop底层原理
OC学习38.2:Runloop线程保活
KVC 重点
设置原理:查找setter方法 -> 是否可以直接访问实例变量 -> 访问实例变量查找
取值原理:查找getter方法 -> 返回NSArray和NSSet代理方法查找 -> 是否可以直接访问实例变量 -> 访问实例变量查找
KVO 重点
alloc、init、new?
alloc
开辟了内存空间
objc_rootAlloc -> callAlloc -> objc_rootAllocWithZone -> class_createInstanceFromZone -> (cls->instanceSize、calloc、obj->initInstanceIsa)
init
没有开辟内存空间
init什么都没做,只是调用了
_objc_rootInit
重写init
,即构造方法,给用户提供入口去实现工厂设计。new
发现和
alloc
调用callAlloc
是一个流程,即等同于new = alloc + init
,但是有一点区别没有自定义的构造方法
,所以一般自定义的类会用alloc init
,系统的类用new。
Runtime 重点
1、runtime
如何实现 weak
属性? weak
属性如何自动置nil的?
Runtime会 对weak属性进行内存布局
,构建hash表
,用于 存储weak指针变量
,表中是用 weak
指向的 对象内存地址
作为 key
,用 指向这个内存地址的所有weak指针
作为 value
,实际上是一个数组。当对象引用计数为 0 dealloc
时,以 key
获取所有 weak指针地址的数组
,然后遍历这个数组把其中的数据 置nil
,最后把这个 对象
从weak表中删除。
OC学习39.1:runtime初探
OC学习39.2:Runtime实际应用
计时器 重点
- NSTimer
时间不准确,因为NSTimer需要加入Runloop中,如果Runloop正在执行一个耗时的计算,timer就会被延时触发
- CADisplayLink
屏幕刷新率 60次/秒
- dispatch_source_t
准确:创建全局计时器队列,设置延迟时间,设置间隔时间,设置开始时间,设置计时器,执行时间,取消计时器,启动计时器
对象 的底层 重点
- 对象
对象
都是以objc_object
为模板创建的,都有isa属性
- isa指针走向
实例对象
的isa
指针指向类
类对象
的isa
指针指向元
类元类
的isa
指针指向根元类(NSObject)
根元类
的isa
指针指向自己
对象 和 类 的底层 重点
- 类
类
都是以objc_class
为模板创建的,都有isa属性
objc_class
继承自objc_object
- 类的结构
isa
属性:继承自objc_object
的isa
,占8
字节superclass
属性:Class
类型,Class
是由objc_object
定义的,是一个指针
,占8
字节cache
属性:cache_t
类型bits
属性:简单从类型class_data_bits_t
目前无法得知,而class_data_bits_t
是一个结构体类型,结构体的内存大小
需要根据内部的属性
来确定,而结构体指针才是8字节
,只有首地址
经过上面3个属性
的内存大小总和
的平移
,才能获取到bits
- 类中存储着什么?
类的
属性列表、实例方法列表、协议列表等
存储在类的bits属性中class_rw_t中
,成员变量存储在类的bits属性中class_ro_t中
。类的
类方法
存储在元类
的bits属性
中。
分类和扩展 重点
分类 和 扩展的区别
category
可以为已存在的类(自定义和系统)添加方法的声明和实现。有.h
和.m
两个文件,原则上只能添加方法,不能添加属性(因为没有实现属性的seter和getter方法,可以通过runtime添加)。extension
可以为自定义的类(不可以是系统的类)添加成员变量、属性、方法
,但是没有实现,只能依托自定义的类
的实现部分,因为它只有一个.h
文件category
在运行时
决议。extension
在编译时
决议。所以扩展中的方法没有被实现编译器会报警告,分类中没有被实现编译器不会警告
组件化方案 重点
将代码提交到 Git仓库(Gitee码云/Gitlab极狐)
,并支持 Cocoapods
集成
- 组件化方式?
- 本地组件化
- cocoapods组件化
- 组件化好处?
- 提高代码复用性
- 方便集成和回退
- 方便团队写作开发
- 方便单元测试
- 解决编译时间过长
- 组件化分层
项目组件化,最难的就是 粒度
问题,需要开发者根据自己的经验把控。这里给出个人认为的层次划分:
- 【基础组件】:
宏定义/自定义工具类
,如常用的自定义分类 - 【功能组件】:项目中
常用的功能
,如地图/消息推送/分享/登录等 - 【业务组件】:项目中的
模块/业务
,如文章详情/个人中心等 - 【中间组件】:负责项目中的路由/消息通知/传参/回调等
- 【宿主工程】:项目容器,用来集成组件,调整各个组件之间的消息传递容器
OC组件化01:组件化介绍
OC组件化02:基于URL-Scheme的使用
OC组件化03:基于RuntTime的target-action的使用
OC组件化04:面向协议Protocol-Class的使用
OC组件化05:【方案一】本地组件化
OC组件化06:【方案二】cocoapods组件化
OC组件化07:Github-Desktop管理工具使用
算法
[C]
OC面试题:算法
链表
链表的优缺点 面试重点
- 数组:
我们知道,用数组存放数据时,必须事先定义固定的长度(即元素个数)
。如果事先难以确定元素个数,则必须把数组定义的足够大,以便存放,显然这样会 浪费内存
。
- 而链表可
根据需要开辟内存单元,不会浪费内存
。
数组 和 链表 的区别?
数组:数组
静态分配 内存
;数组元素在内存上是连续
的,可以通过下标查找元素
;插入、删除需要移动大量元素,比较使用元素很少变化的情况;数组插入删除操作时间复杂度是 O(n),数组查询操作时间复杂度是 O(1)链表:链表
动态分配 内存
;链表元素在内存上是不连续的
,查找慢;插入、删除只需要对元素指针重新赋值,效率高;链表插入删除操作时间复杂度是 O(1),链表查询操作时间复杂度是 O(n)
如何检测单链表中是否有环? 面试重点
- 穷举遍历
首先从头节点开始,依次遍历每个节点,每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,当 新节点的ID
和 此节点之前的所有节点ID
依次比较,如果发现 ID相同
,则证明链表有环。
1 | 外层循环:保证一个新节点ID |
- 哈希表缓存
首先创建一个以 节点ID为键
的 HashSet集合
,用来 存储曾经遍历过的节点
。然后同样是从头节点开始,依次遍历每一个节点,当新节点和HashSet集合当中存储的节点ID相同,则说明链表右环。
1 | 外层循环:HashSet集合存储曾经遍历的节点ID |
- 快慢指针
首先创建两个指针,指针1和指针2,同时指向这个头节点,指针1每次向下移动一个节点,指针2向下移动2个节点,比较节点是否相同,如果相同则说明链表有环。如果不同执行下一次循环。
二叉树
- 先序遍历:对任一子树,先访问
根
,然后遍历左子树
,最后遍历其右子树
- 中序遍历:对任一子树,先遍历
左子树
,然后访问根
,最后遍历其右子树
- 后序遍历:对任一子树,先遍历其
左子树
,然后遍历其右子树
,最后访问根
二叉树算法题
- 已知某二叉树的前序遍历为
A-B-D-F-G-H-I-E-C
,中序遍历为F-D-H-G-I-B-E-A-C
,请还原这颗二叉树。
解题思路:
从前序遍历中,我们确定了根节点为A,在从中序遍历中得出 F-D-H-G-I-B-E
在根节点的左边,C 在根节点的右边,那么我们就可以构建我们的二叉树雏形
那么剩下的前序遍历为 B-D-F-G-H-I-E,中序遍历为 F-D-H-G-I-B-E,B就是我们新的 根节点,从中序遍历中得出 F-D-H-G-I 在B的左边,E在B的右边,继续构建
那么剩下的前序遍历为 D-F-G-H-I,中序遍历为 F-D-H-G-I,D就是我们新的 根结点
,从中序遍历中得出F在D的左边,H-G-I 在D的右边,继续构建
那么剩下的前序遍历为 G-H-I,中序遍历为 H-G-I,G 就是我们新的 根结点
,从中序遍历中得出H在G的左边,I在G的右边,继续构建
结论:
前序(根左右):A-B-D-F-G-H-I-E-C
中序(左根右):F-D-H-I-G-B-E-A-C
后序(左右根):F-H-I-G-D-EB-C-A
注:光有前序和中序是无法还原二叉树的
Dealloc 调用流程 重点
- 首先调用
_objc_rootDealloc()
- 接下来调用
rootDealloc()
- 这时候会判断是否可以被释放,判断的依据主要有5个,判断是否有以上五种情况
NONPointer_ISA
weakly_reference
has_assoc
has_cxx_dtor
has_sidetable_rc
- 如果有以上五中任意一种,将会调用
object_dispose()
方法,做下一步的处理。
如果没有之前五种情况的任意一种,则可以执行释放操作,C函数的free()
。 - 执行完毕。
多环境配置三种方式
- Target
- Url Scheme
- xcconfig
启动优化
我们这里所说的启动优化,一般是指 冷启动
情况下的启动优化,是指用户唤起App开始到AppDelegate
中的 didFinishLaunchingWithOptions
方法执行完毕为止,并以 main()
函数为分界点,分为 pre-main
和 main()
两个阶段:
pre-main
阶段:是指从用户唤起App到main()
函数执行之前的过程
针对这几部,有以下几点优化建议:重点面试题
- 尽量
少用外部动态库
,苹果官方建议自定义的动态库最好不要超过6个
,如果超过6个,需要合并
动态库 - 减少
OC
类,因为类越多,越耗时 - 将不必须在
+load
方法中做的事情延迟到+initialize
中,尽量不要用C++
虚函数 - 如果是Swift,尽量使用
struct
main()
阶段:是指main()
函数开始执行到didFinishLaunchingWithOptions
方法执行结束的过程
- 减少启动初始化的流程,
能懒加载的懒加载,能延迟的延迟,能放后台初始化的放后台,尽量少占用主线程的启动时间
- 优化代码逻辑,去除非必须的代码逻辑,减少每个流程的消耗时间
- 启动阶段能
使用多线程
来初始化的,就使用多线程 - 尽量
使用纯代码
来进行UI框架的搭建,尤其是主UI框架,例如UITabBarController
,尽量避免使用Xib或者StoryBoard,相比纯代码而言,这种更耗时 - 删除废弃类、方法
启动优化具体的方案:二进制重排
二进制重排的本质就是
对启动加载的符号进行重新排列
启动加载符号的获取用:clang插桩
build 编译过程
其中 编译过程
如下图所示,主要分为以下几步:
源文件
:载入.h、.m、.cpp
等文件预处理
:替换宏,删除注释,展开头文件,产生.i
文件编译
:将.i
文件转换为汇编语言
,产生.s
文件汇编
:将汇编文件转换为机器码文件
,产生.o
文件链接
:对.o
文件中引用其他库的地方进行引用,生成最后的可执行文件 MachO
基础知识面试题
什么是僵尸对象?
已经被销毁的对象(xcode 中默认不实时检查僵尸对象)
什么是野指针?
指向僵尸对象(不可用的内存)的指针,尝试使用它会导致 应用程序崩溃
什么是空指针?
没有指向存储空间的的指针(里面存的是nil, 也就是0),给空指针发消息不会有任何反应。
什么是内存泄漏?
一个 对象
没有被释放,会 内存泄漏
,内存泄漏会导致应用程序占用的内存会越来越大,直到应用程序无内存可用时,会导致 应用程序崩溃
iOS 关键词有哪些?各有什么作用? 重点
- readwrite
可读可写
特性,同时生成get方法和set方法的声明和实现
- readonly
只读
特性,只会生成get方法的声明和实现,不希望属性在类外改变
- retain
持有
特性,retaincount 会 +1,用于 MRC
- nonatomic
非原子
特性
- atomic
原子
特性,默认属性
atomic不是绝对线程安全的,只是对 setter/getter
方法使用了 自旋锁(spinlock_t)
,内部使用 互斥锁(os_unfair_lock)
,保证了 读/写
安全。
atomic并不能保证 整个对象
是线程安全的,需要对 整个对象
进行 加锁
来保证线程安全:
* NSLock(互斥锁)
* dispathch_semaphore(信号量)
* @synchronized(互斥递归锁)
- assign
可以修饰 基本数据类型和对象
。
通常用于修饰 基本数据类型
,如Int、CGFloat、Double等,这是因为 基本数据类型放在栈区
,栈由编译器自动分配和释放内存,栈先进后出,基本数据类型出栈后,assign修饰的基本数据类型就不存在了,不会出现 野指针
修饰对象,如NSString、实例对象等,引用计数不会增加,但 assign
修饰对象释放后,指针
不会被系统置为nil,会产生 野指针
或 EXC_BAD_ACCESS
错误。
- strong
强引用,只修饰对象
,属性默认修饰符
指向并持有该对象
,其修饰的对象引用计数会 +1
,引用计数不为 0
则不会被销毁,需要将其置为 nil
可以销毁。否则会出现 内存泄漏
。
- weak
弱引用,只修饰对象
。
指向但并不拥有该对象
,引用计数不增加。该对象自动在内存中销毁。
- copy
用于修饰 不可变的对象
。
比如NSString、NSDictionary、NSArray等。
浅拷贝和深拷贝 重点
- 浅拷贝
浅拷贝,拷贝了一个新的指针,与原对象指针指向同一块内存,引用计数 + 1
;(A,CopyA = [A copy],A
变化,CopyA
的值也变化)
- 深拷贝
深拷贝,拷贝了一个新的对象,指向新的内存,新对象的 引用计数为 1
,源对象引用计数不变;(A,MutableA = [A mutbleCopy],A
变化,MutableA
的值不变)。
NSString/NSArray/NSDictionary和NSMutableString/NSMutableArray/NSMutableDictionary 进行copy和mutableCopy区别?重点
对于不可变NSString/NSArray/NSDictionary,copy浅拷贝(元素:浅拷贝),mutableCopy深拷贝(元素:浅拷贝)
对于可变NSMutableString/NSMutableArray/NSMutableDictionary,copy深拷贝(元素:浅拷贝),mutableCopy深拷贝(元素:浅拷贝)
自定义对象的copy和mutableCopy?
- copy和mutableCopy都是深拷贝(属性:浅拷贝)
属性用copy还是strong? 重点面试题
对于
不可变属性
,推荐用copy
,能防止不可变对象变成可变对象,从而防止值发生不可控变化。- 用不可变A赋值,A改变,copy和strong修饰的属性不会改变
- 用可变的A赋值,A改变,strong修饰的属性会随着改变,copy的话分_copy还是self.copy
_copy赋值给实例变量,值随着A改变;self.copy调用了setter方法进行了深拷贝
,值不随着A改变
对于
可变属性
,推荐用strong
,strong只会增加引用计数;copy修饰后,相当于深拷贝
,会生成一个不可变对象,再调用可变对象的函数时会crash
一个APP是如何唤醒另一个APP的?
URL Scheme
:iOS有一个特性就是将 自身绑定
到一个自定义的 URL Scheme
上,该 scheme
用于从 浏览器或其他应用中启动本应用
。
UIView和CALayer
- UIView和CALayer的关系?
1)UIView继承UIReponder,具有响应事件的能力。CALayer继承NSObject,没有响应事件的能力。
2)UIView 主要负责视图与用户的交互。CALayer 绘制视图显示的内容。
3)UIView是CALayer的代理。
- UIWindow和UIView的关系?
1)UIWindow继承自UIView。
2)UIWindow提供一个区域用来显示UIView。
单例的写法和作用?
单例模式的作用:可以保证程序运行过程中,一个类只返回一个实例,供外界访问;static保证类只分配一次内存;dispatch_once 方法保证调用一次,并自动加锁,线程安全
1 | static id _instance = nil; // 定义static全局变量,保证只分配一次内存 |
这么写可以保证下面两种方式返回同一个实例:
1 | Single * p1 = [Single shareInstance]; |
成员变量和实例变量区别?
成员变量:在文件中@interface下{}内的均统称为
成员变量
实例变量:实例变量是
类定义
的变量区别:
- 去除基本数据类型int,float…等,其他类型的变量均叫做
实例变量
成员变量 = 实例变量 + 基本数据类型
- 去除基本数据类型int,float…等,其他类型的变量均叫做
@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?
- @property = _ivar + getter + setter;
property(属性)= _ivar(实例变量)+ setter(存方法)+ getter(取方法)
属性引用self.name与_name的区别?
self.name是对属性的访问,= 左侧是调用setter方法,= 右侧是调用getter方法,可以在类外使用,_name不能在类外使用。
_name是对
局部变量
的访问,直接调用变量,不通过getter方法
frame 和 bounds 区别?
frame:参考系是父视图坐标
bounds:参考系是自身坐标
为什么只在主线程刷新UI?
所有的用户事件都是在
主线程上
进行传递(如点击、拖动),所以只能在主线程上才能对事件进行响应。而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上
同时
更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新
你是否接触过OC中的反射机制?简单聊⼀下概念和使⽤?
- class反射:
类名
与字符串
相互转化
1 | // 字符串 变 类 |
- SEL的反射:
字符串
和方法
相互转换
1 | // 字符串 变 方法 |
@synthesize 和 @dynamic 分别有什么作用?
@synthesize(sɪnθəsaɪz)
的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。@dynamic(daɪˈnæmɪk)
告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成
NSSet和NSArray的区别?
NSSet
无序集合,存储方式不连续
。NSArray有序集合,存储方式连续
。NSSet
用hash
算法,查询效果高
。NSSet
集合里的元素只能遍历,NSArray通过下标访问。
常见的状态码?
2xx
成功
:200表示请求正常。3xx
重定向
:302是请求重定向。解决方法 NSURLConnetion 和 NSURLSession 进行拦截4xx
客户端
错误:400客户端请求的语法错误,404Not Found 找不到/请求失败5xx
服务器
错误:500 Internal Server Error 服务器的内部错误
staitc 和 const 的区别? 重点
const:表示
只读
的意思const
放在类型
前:可以改变指针的指向,可以改变指针指向的内容const
放在变量
前:不可以改变指针的指向,不可以改变指针指向的内容
static:
静态变量
,可修饰局部变量和全局变量
,可修饰方法static
可修饰局部/全局变量
,称为局部静态变量和全局静态变量
,只初始化一次,只分配一次内存,生命周期被改变,不再是作用域内static
修饰的方法
,可以在不同文件下重名,互不影响运行
通用链接(Universal Links)重点
服务端配置
HTTPS
证书 和添加apple-app-site-association
地址关联文件移动端在plist添加
Associated Domains
关联域权限
iOS中几种常见的设计模式? 重点
代理模式
一对一的依赖关系,当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
观察者模式
KVO机制 和 Notification通知机制
单例模式
保证程序运行过程中,一个类只返回一个实例,供外界访问
工厂模式
通过一个类方法,根据已有模板批量生产对象
。
MVC模式
Model即数据模型
view即视图
controller即控制器
怎么理解Objective-C是动态运行时语言。 重点
主要是 将数据类型的确定和函数的调用由编译时推迟到了运行时
。这个问题其实浅涉及到两个概念,运行时和多态
。
运行时:简单来说,运行时机制使我们
直到运行时才去确定数据类型和要调用的函数
。多态:
不同对象以自己的方式响应相同的消息的能力
叫做多态。
runtime项目中具体应用? 重点
方法交换
。- 给
分类添加属性
。 - 动态添加方法。
- 字典转模型。
- 数组越界。
- 动态获取成员属性、成员变量、实例方法
KVC是什么?重点
KVC
全程 Key Value Coding
,中文 键值编码
,是由 NSKeyValueCoding
非正式协议启动的一种机制,对象
采用该协议来 间接访问对象的属性
。
1 | - (nullable id)valueForKey:(NSString *)key; |
KVO是什么?重点
KVO
全程 Key Value Observing
,中文 键值观察
,它 用于监听实例对象属性的变化
。
KVO的实现原理?(KVO的本质是什么?) 重点
当一个 实例对象
的 属性注册了KVO
,实例对象 isa指针
的指向在注册KVO观察者之后,由 原有类
改为 中间类(NSKVONotifing_类名)
;中间类
重写了 属性setter方法、class、dealloc、_isKVOA
方法;dealloc
方法中,移除 KVO
观察者之后,实例对象isa 指向由 中间类
更改为 原有类
;中间类 从创建后就 一直存在内存中
,不会被销毁。
KVO实际应用 重点
观察
实例对象
的属性
变化观察
实例对象
的容器
变化
观察容器用: mutableArrayValueForKey
super的本质?
- [self class] : 返回实例对象的类
- [self superclass] : 返回实例对象的父类
- [super class] : 编译指示器(标识符),底层会被编译成
objc_msgSendSuper()
方法,返回实例对象的类 - [super superclass] : 返回父类
Method、SEL、IMP关系?
Method = SEL + IMP + method_types,相当于在SEL和IMP之间建立了一个映射
Method:是一个objc_method的结构体,objc_method是类的一个方法描述
SEL:
方法编号
(方法名)IMP:实际上是一个
函数指针
,指向方法实现的地址
。method_types:
参数和返回类型的描述字串
简述下Objective-C中调用方法的过程?重点面试题
Objective-C是动态语言,每个方法在 运行时
会被动态转为 消息发送
,即:objc_msgSend(receiver, selector)
,整个过程介绍如下:
person实例对象发送一条test消息:
消息发送阶段
:
负责从 类及父类的缓存列表及方法列表查找方法
。每当调用方法的时候,会先去cache中查找是否有缓存的方法,如果没有缓存,在去类对象方法列表中遍历查找,如果方法不在列表里面;就会通过superclass找到父类的类对象,在去父类cache中查找是否有缓存的方法,如果没有缓存,去父类对象方法列表里面遍历查找,以此类推直到找到方法之后,就会将方法直接存储在cache中,下一次在调用这个方法的时候,就会在类对象的cache里面找到这个方法,直接调用了。
动态方法解析
如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现。
动态解析对象方法时,会调用:+(BOOL)resolveInstanceMethod
动态解析类方法时,会调用:+(BOOL)resolveClassMethod:(SEL)sel。
快速转发阶段:将消息转发给可以实现此方法的对象
如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理。以处理消息的接受者来处理。- (id)forwardingTargetForSelector:
由上述代码可以看出,当本类没有实现方法,并且没有动态解析方法,就会调用forwardingTargetForSelector函数,进行消息转发
,我们可以实现forwardingTargetForSelector函数,在其内部 将消息转发给可以实现此方法的对象
。
慢速转发阶段:修改方法调用对象
方法签名
:返回值类型、参数类型- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
如果forwardingTargetForSelector函数返回为nil或者没有实现的话,就会调用methodSignatureForSelector方法,用来返回一个 方法签名
,这也是我们正确跳转方法的最后机会。
NSInvocation 封装了一个方法调用,包括:方法调用者,方法,方法的参数- (void)forwardInvocation:(NSInvocation *)anInvocation
如果methodSignatureForSelector方法返回正确的方法签名就会调用forwardInvocation方法,forwardInvocation方法内提供一个NSInvocation类型的参数,NSInvocation封装了一个方法的调用,包括方法的调用者,方法名,以及方法的参数。在forwardInvocation函数内 修改方法调用对象
即可。
- 如果
methodSignatureForSelector
返回的为nil,就会来到doseNotRecognizeSelector:
方法内部,程序crash提示无法识别选择器unrecognized selector sent to instance。
如果消息转发也没有实现,就会报方法找不到的错误,无法识别消息
,unrecognzied selector sent to instance
循环引用的几种情况和解决方式? 重点
- Block
原因: self
强引用了 block
,而 block
内部又调用了 self
解决: 使用 Weak-Strong Dance
- Delegate
原因:委托者和被委托人之间的相互强引用问题 strong
解决:用 weak
进行弱引用 或者
通过中间对象(代理对象)的方式来解决(效率更加高的中间对象NSProxy:不需要进行发送消息和动态解析,直接进行消息转发)
- NSTimer
原因:self → timer → self(target) 的循环持有链
解决:在适当的时机销毁
1 | [_timer invalidate]; |
OC 如何进行内存管理的? 重点
手动内存管理 MRC
自动内存管理 ARC
LLVM + Runtime 会为我们代码自动插入 retain
和 release
以及 autorelease
等代码,不需要我们手动管理
- 自动释放池
自动释放池原理 重点
自动释放池的本质是
__AtAutoreleasePool
结构体,包含构造函数和析构函数
结构体声明,触发构造函数
,调用objc_autoreleasePoolPush()
函数,对象压栈结构体出作用域,触发析构函数
,调用objc_autoreleasePoolPop()
函数,对象出栈
内存优化 重点
- cell复用
- 绘制的话:用CAShaperLayer,渲染快,内存使用高效
- 按需加载:懒加载
- 合理利用缓存:比如三方图片压缩缓存
- 尽量使用透明View:控件有背景色增大内存消耗
卡顿监听 重点
主要是用 displayLink + Runloop 进行FPS监测
主要从减轻 CPU
和 GPU
消耗入手,保证写一个 VSync
到来时,CPU
和 GPU
能够写作完成下一帧的渲染并缓存到帧缓冲区
- 卡顿优化在
CPU
层面:
1)尽量用轻量级的对象,比如 用不到事件处理
的地方,可以考虑使用 CALayer
取代 UIView
2)不要频繁地调用 UIView
的相关属性,比如 frame、bounds、transform
等属性,尽量减少不必要的修改
3)尽量 提前计算好布局
,在有需要时一次性调整对应的属性,不要多次修改属性
4)图片的 size
最好刚好跟 UIImageView
的 size
保持一致
5)控制
一下线程的最大并发数量
6)尽量把 耗时的操作放到子线程
:如text宽高获取等
- 卡顿优化在 GPU层面:
1)GPU能处理的 最大纹理
尺寸是 4096x4096
,一旦超过这个尺寸,就会占用 CPU
资源进行处理,所以纹理尽量不要超过这个尺寸
2)尽量 减少视图数量和层次
3)减少透明的视图
(alpha<1),不透明的就设置 opaque
为 YES
4)尽量 避免出现离屏渲染
:圆角、阴影、遮罩等
tableView卡顿优化 重点
最常用的就是
cell的复用
, 注册复用标识符避免cell的重新布局
,初始化时就布局好提前计算并缓存cell的高度
减少cell中控件的数量
,少动态添加 view避免背景透明
能
使用局部更新
的就使用局部更新
加载网络数据,
下载图片,使用异步加载
,并缓存按需加载cell
:cell滚动很快时,只加载范围内的cell不要实现无用的代理方法
,tableView只遵守两个协议
网络优化 重点
- DNS优化:即域名解析优化,缓存
ip
- 资源优化:
- 图片webp,比png/jpg小
- 数据传输用ProtocolBuffer代替json,数据小,序列化和反序列化也简单
- 请求压缩、请求合并
- 数据缓存
- 网络环境监测
- 针对性请求重试
SDWebImage流程
- 入口
setImageWithURL:placeholderImage:options:
会先把placeholderImage
显示,然后SDWebImageManager
根据URL
开始处理图片。 - 进入
SDWebImageManagerdownloadWithURL:delegate:options:userInfo:
,
交给SDImageCache
从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:
- 先从内存图片
缓存查找
是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate
会调用imageCache:didFindImage:forKey:userInfo:
到SDWebImageManager
。 SDWebImageManagerDelegate
会调webImageManager:didFinishWithImage:
到UIImageView+WebCache
等前端展示图片。- 如果
内存缓存中没有
,生成NSInvocationOperation
添加到队列开始从硬盘查找
图片是否已经缓存。 - 根据
URLKey
在硬盘缓存目录下尝试读取图片文件
,这一步是在NSOperation
进行的操作,所以回主线程进行结果回调notifyDelegate
。 - 如果上一操作
从硬盘读取到了图片
,将图片添加到内存缓存
中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate
回调imageCache:didFindImage:forKey:userInfo:
,进而回调展示图片。 - 如果从
硬盘缓存目录读取不到图片
,说明所有缓存都不存在该图片,需要下载图片
,
回调imageCache:didNotFindImageForKey:userInfo:
。 - 共享或重新生成一个下载器
SDWebImageDownloader
开始下载图片。 - 图片下载由
NSURLConnection
来做,实现相关delegate
来判断图片下载中、下载完成和下载失败。 connection:didReceiveData:
中利用ImageIO
做了按图片下载进度加载效果。connectionDidFinishLoading:
数据下载完成后交给SDWebImageDecoder
做图片解码处理。- 图片解码处理在一个
NSOperationQueue
完成,不会拖慢主线程UI
。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。 - 在主线程
notifyDelegateOnMainThreadWithInfo:
宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:
回调给SDWebImageDownloader
。 imageDownloader:didFinishWithImage:
回调给SDWebImageManager
告知图片下载完成。- 通知所有的
downloadDelegates
下载完成,回调给需要的地方展示图片。 - 将图片保存到
SDImageCache
中,内存缓存和硬盘缓存同时保存
。写文件到硬盘也在以单独NSInvocationOperation
完成,避免拖慢主线程。 SDImageCache
在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
19)SDWebImage 也提供了UIButton+WebCache
和MKAnnotationView+WebCache
,方便使用。
20)SDWebImagePrefetcher
可以预先下载图片,
缓存策略:
它的底层是用
NSCache
在实现的,NSCache
是Foundation框架
提供的缓存类的实现,它是线程安全的。在内存不足时NSCache会自动释放存储的对象。key是不会重复的。SDWebImage的缓存策略:缓存有关的一共有四个文件
SDImageCacheConfig和SDImageCache,SDImageCacheConfig配置类
,保存一些缓存策略的信息(压缩图片-YES、iCloud备份-关闭、内存做缓存-YES、最长时间默认1周
、缓存占用最大的空间-字节),SDImageCache通过
url作为key
,UIImage作为value存到内存
。并开启异步串行队列将图片编码存储到磁盘中defalut文件下,key为url做了MD5加密并拼接defalut路径为绝对路径。
AFNetworking
AFNetworking 底层原理分析:
AFNetworking主要是对 NSURLSession
的封装,
其中主要有以下类:
AFHTTPSessionManager
:内部封装是NSURLSession
,负责发送⽹络请求
,使
⽤最多的⼀个类。AFNetworkReachabilityManager
:实时监测⽹络状态的⼯具类。当前的⽹络环
境发⽣改变之后,这个⼯具类就可以检测到。AFSecurityPolicy
:⽹络安全的⼯具类, 主要是针对HTTPS
服务。AFURLRequestSerialization
:序列化⼯具类AFURLResponseSerialization
:反序列化⼯具类AFJSONResponseSerializer
:JSON解析器,默认的解析器.AFHTTPResponseSerializer
:万能解析器, JSON和XML之外的数据类型,直接返
回⼆进制数据,对服务器返回的数据不做任何处理.
MJEetension
主要作用是将 json -> Model
,主要分为三步
- 创建模型
- 为模型中的属性赋值
- 返回模型
原理:
- 使用
Runtime
动态获取模型的类和其所有父类的所有的属性名
(包括继承链的所有属性,MJExtension对模型的属性做了缓存
,下次转换时自己使用,空间换时间) - 将服务器返回的
json数据转为字典
,并根据属性名在数据字典中获取对应的值
- 将取出的值使用
KVC
(setValue:forKey)设置给Model
即可
主要是用runtime API是实现的数据解析,将字典转模型
MJRefresh
下拉刷新的基本原理:
大部分的下拉刷新控件,都是用 contentInset
实现的。大部分的下拉刷新控件,都是将自己放在 UIScrollView
的上方,起始y设置成负数,所以平时不会显示出来,只有下拉的时候才会出现,放开又会弹回去。然后在loading的时候,临时把contentInset增大,相当于把UIScrollView往下挤,于是下拉刷新的控件就会显示出来,然后刷新完成之后,再把contentInset改回原来的值,实现回弹的效果
- 通过
UIScrollView+MJRefresh
里的一个category,为UIScrollView增加了属性header和footer。 - 通过
KVO
监听UIScrollView的contentOffset和contentSize实现状态监听 - 下拉的时候临时增大contentInset,令header保留在屏幕上。上拉同理。
面试官问还有什么想问的? 重点
技术面不问薪资待遇,人事面问薪资待遇
- 问岗位:
- 在这个岗位上,会直接
接触到哪些类型的项目?
- 这个岗位的主要职责是什么?主要的KPI是什么?
- 这个岗位如何评估绩效,试用期需要达到什么指标?
- 这个岗位的最大挑战是什么?
- 问团队:
团队的基本情况?
- 这个团队在公司的角色是什么?
- 可以跟我介绍一下我的领导吗?
- 问公司
公司的文化氛围是什么样的?
- 员工的晋升机制是什么样的?我这个岗位的晋升机会如何?
- 这个岗位所在的团队如何支持公司目标的实现?
- Post title:OC面试题:总结
- Post author:张建
- Create time:2023-03-01 16:57:33
- Post link:https://redefine.ohevan.com/2023/03/01/面试题/OC面试题:总结/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.