OC面试题:总结

张建 lol

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 开发中,iOSJS 交互是每个程序猿必须掌握的技能。iOS8 以后,苹果推出了新框架 WebKit,使用 WKWebView 替代 UIWebView稳定性好、占用内存少,速度更快

说道 iOSJS 交互,就不得不提 Hybrid(Hybrid Mobile App),即通过 Web 网络技术与 Native 相结合的混合移动应用开发

WKWebView 特性:
1、在性能、稳定性、功能方面有很大提升,直观体现是内存占用变少;
2、支持高达60fps的滚动刷新率以及内置手势
3、支持了更多的HTML5特性;

OC学习21:hybrid

XML、JSON 重点

  • iOS 开发中,常见的 数据传输 格式有两种:JSON和XML

  • 服务器返回客户端的数据,一般都是 JSON格式或XML格式(文件下载除外)

  • JSON 由于 体积小、传输快等 优点,逐渐成为了 主流的数据传输格式

JSON、XML 解析 重点

  1. JSON 数据解析 性能最好 的是使用 苹果原生的框架NSJSONSerialization

  2. XML 数据解析

要想从XML中提取信息,就得解析XML,目前针对XML的解析有两种方式:

  • SAX:从根元素开始,按照顺序一个元素一个元素的往下解析,可用于 解析大、小文件
  • DOM:一次性将整个XML文档加载到内存中,适合较小的文件

iOS 中解析 XML 有两种:

  • 苹果原生:使用 NSXMLParse,SAX方式解析,使用简答

  • 第三方框架:libxml2、GDataXML

    • libxml2c 语音,默认包含在iOS SDK中,同时支持DOM和SAX方式解析
    • GDataXMLDOM 解析,由 google 基于 libxml2 开发

解析XML大文件建议用:NSXMLParse、libxml2
解析XML小文件上述三种都可以

OC学习04:JSON和XML数据解析

TCP、UDP、HTTP、HTTPS、Socket、WebSocket、Webservice 重点

必看的内容:

OC学习22:HTTP&HTTPS&Socket&WebSocket&WebService探索

事件传递链和响应链 重点

必看的内容

OC学习01:事件传递链和响应链

DNS 是什么? 重点

DNS,就是 Domain Name System 的缩写,翻译过来就是 域名系统。是互联网上作为 域名和IP地址相互映射的一个分布式数据库DNS 能够使用户更方便的访问互联网,而不用去记住能够被 机器 直接读取的 IP 数串。通过域名最终得到该域名对应的IP地址的过程 叫做 域名解析(或主机名解析)。

举例:如果你要访问域名 www.bboyzj.cn ,首先要通过 DNS 查出它的 IP 地址是 113.31.107.233

Swift学习30:DNS原理

Block? 重点

解决循环引用常见的几种方式

  • 【方式一】: weak-strong-dance 强弱共舞

  • 【方式二】: __block 修饰对象(需要注意的是在block内部需要 置空 对象,而且 block必须调用

  • 【方式三】: 传递 对象self 作为block的参数,提供给block内部使用

  • 【方式四】: 使用 NSProxy

OC底层原理30:Block底层原理

锁的种类?区别?

  • 【自旋锁】:在自旋锁中,线程会反复检查变量是否可用。由于线程这个过程中一致保持执行,所以是一种 忙等待。一旦获取了自旋锁,线程 就会一直保持该锁,直到显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于 线程只会阻塞很短时间的场合有效 的。对于iOS属性的修饰符 atomic,自带一把自旋锁

    • OSSpinLock
    • atomic
  • 【互斥锁】:互斥锁 是一种用于 多线程编程 中,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制,该目的是通过 将代码切成一个个临界区 而达成

    • @synchroized
    • NSLock
    • pthread_mutex
  • 【条件锁】:条件锁 就是 条件变量,当进程的某些 资源要求不满足 时就 进入休眠,即锁住了,当 资源被分配到了条件锁打开了,进程继续进行

    • NSCondition
    • NSConditionLock
  • 【递归锁】:递归锁就是 同一个线程可以加锁N次而不会引发死锁。递归锁是 特殊的互斥锁,即是 带有递归性质的互斥锁

    • pthread_mutex(recursive)
    • NSRecursiveLock
  • 【信号量】:信号量 是一种 更高级的同步机制互斥锁 可以说是 semaphore在取值0/1时的特例,信号量可以有更多的取值空间,用来 实现更加复杂的同步,而不是单单的线程间互斥

    • dispatch_semaphore
  • 【读写锁】:读写锁实际是一种 特殊的自旋锁。将对共享资源的访问分成 读者写者读者 只对共享资源 进行读访问写者 则需要对共享资源 进行写操作,这种锁相对于自旋锁而言,能 提高并发性

    • 一个读写锁同时只能有一个写者或者多个读者,但不能即有读者又有写者,在读写锁保持期间也是抢占失效的

    • 如果 读写锁当前没有读者,也没有写者,那么写者 可以立刻获取 读写锁,否则它必须自旋 在那里,直到没有任何写者或读者,如果读写锁没有写者,那么读者可以 立即获取读写锁

其实 基本的锁 就包括三类:自旋锁、互斥锁、读写锁,其他的比如 条件锁、递归锁、信号量 都是 上层的封装和实现

OC底层原理29:锁的原理

Git 重点

  • 回退到某个版本,只回退了 commit 信息

git reset --soft

  • 切换到指定版本

git reset --hard 版本id

  • 查看以前的提交日志

git reflog

Swift学习16:Git使用

多线程 重点

必看的内容:

OC面试题:多线程

OC底层原理25:多线程原理探索
OC底层原理26:GCD之函数与队列
OC底层原理27:GCD之 NSThread & GCD & NSOperation
OC底层原理28:GCD之底层原理分析

内存管理 重点

  1. MRC指的是 手动内存管理,在开发过程中 需要开发者手动去编写内存管理的代码

  2. ARC指的是 自动内存管理,在此内存管理模式下由 LLVM编译器和OC运行时库生成相应内存管理的代码

  3. 自动释放池:

  • 当应用程序启动,系统默认会 开启一条线程,该线程就是 主线程
  • 每条线程都包含一个与其对应的自动释放池,当某条线程被终止的时候,对应该线程的 自动释放池会被销毁。同时,处于该自动释放池的对象将会进行一次 release 操作。
  • 自动释放池 本质__AtAutoreleasePool 结构体,包含 构造函数和析构函数
  • 自动释放池释放时机,主线程通过 runloop 监听 Entry和BeforeWaiting 释放旧的,创建新的,子线程是线程退出

OC学习03:内存管理

Runloop 重点

  • 当应用程序启动,系统默认会 开启一条线程,该线程就是 主线程
  • 每一条 线程 都有一个与之对应 runloop对象主线程Runloop 系统自动获取(创建)。子线程默认没有开启runloop。runloop在线程销毁时销毁。

OC学习38.1:Runloop底层原理
OC学习38.2:Runloop线程保活

KVC 重点

  • 设置原理:查找setter方法 -> 是否可以直接访问实例变量 -> 访问实例变量查找

  • 取值原理:查找getter方法 -> 返回NSArray和NSSet代理方法查找 -> 是否可以直接访问实例变量 -> 访问实例变量查找

OC底层原理22:KVC底层原理分析

KVO 重点

OC底层原理23: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。

OC底层原理03: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

准确:创建全局计时器队列,设置延迟时间,设置间隔时间,设置开始时间,设置计时器,执行时间,取消计时器,启动计时器

OC学习32:计时器探索

对象 的底层 重点

  1. 对象
  • 对象 都是以 objc_object 为模板创建的,都有 isa属性
  1. isa指针走向
  • 实例对象isa 指针指向
  • 类对象isa 指针指向
  • 元类isa 指针指向 根元类(NSObject)
  • 根元类isa 指针指向 自己

对象 和 类 的底层 重点

  • 都是以 objc_class 为模板创建的,都有 isa属性

  • objc_class 继承自 objc_object

  1. 类的结构
  • isa 属性:继承自 objc_objectisa,占 8 字节

  • superclass 属性:Class 类型,Class 是由 objc_object 定义的,是一个 指针,占 8 字节

  • cache 属性:cache_t 类型

  • bits 属性:简单从类型 class_data_bits_t 目前无法得知,而 class_data_bits_t 是一个结构体类型,结构体的 内存大小 需要根据 内部的属性 来确定,而 结构体指针才是8字节,只有 首地址 经过上面 3个属性内存大小总和平移,才能获取到 bits

  1. 类中存储着什么?
  • 类的 属性列表、实例方法列表、协议列表等 存储在 类的bits属性中class_rw_t中,成员变量存储在 类的bits属性中class_ro_t中

  • 类的 类方法 存储 在元类bits属性 中。

OC底层原理10:类 & 类结构分析

分类和扩展 重点

  • 分类 和 扩展的区别

    • category 可以为已存在的类(自定义和系统)添加方法的声明和实现。有 .h.m 两个文件,原则上只能添加方法,不能添加属性(因为没有实现属性的seter和getter方法,可以通过runtime添加)。

    • extension 可以为自定义的类(不可以是系统的类)添加 成员变量、属性、方法,但是没有实现,只能依托 自定义的类 的实现部分,因为它只有一个 .h 文件

    • category运行时 决议。extension编译时 决议。所以扩展中的方法没有被实现编译器会报警告,分类中没有被实现编译器不会警告

OC学习05:分类和扩展

组件化方案 重点

将代码提交到 Git仓库(Gitee码云/Gitlab极狐),并支持 Cocoapods 集成

  1. 组件化方式?
  • 本地组件化
  • cocoapods组件化
  1. 组件化好处?
  • 提高代码复用性
  • 方便集成和回退
  • 方便团队写作开发
  • 方便单元测试
  • 解决编译时间过长
  1. 组件化分层

项目组件化,最难的就是 粒度 问题,需要开发者根据自己的经验把控。这里给出个人认为的层次划分:

  • 【基础组件】:宏定义/自定义工具类,如常用的自定义分类
  • 【功能组件】:项目中 常用的功能,如地图/消息推送/分享/登录等
  • 【业务组件】:项目中的 模块/业务,如文章详情/个人中心等
  • 【中间组件】:负责项目中的路由/消息通知/传参/回调等
  • 【宿主工程】:项目容器,用来集成组件,调整各个组件之间的消息传递容器

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面试题:算法

链表

链表的优缺点 面试重点

  1. 数组:

我们知道,用数组存放数据时,必须事先定义固定的长度(即元素个数)。如果事先难以确定元素个数,则必须把数组定义的足够大,以便存放,显然这样会 浪费内存

  1. 而链表可 根据需要开辟内存单元,不会浪费内存

数组 和 链表 的区别?

  • 数组:数组 静态分配 内存;数组元素在 内存上是连续 的,可以通过 下标查找元素;插入、删除需要移动大量元素,比较使用元素很少变化的情况;数组插入删除操作时间复杂度是 O(n),数组查询操作时间复杂度是 O(1)

  • 链表:链表 动态分配 内存;链表元素在 内存上是不连续的,查找慢;插入、删除只需要对元素指针重新赋值,效率高;链表插入删除操作时间复杂度是 O(1),链表查询操作时间复杂度是 O(n)

如何检测单链表中是否有环? 面试重点

  • 穷举遍历

首先从头节点开始,依次遍历每个节点,每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,当 新节点的ID此节点之前的所有节点ID 依次比较,如果发现 ID相同,则证明链表有环。

1
2
外层循环:保证一个新节点ID
内层循环:从头节点遍历到新节点ID之前,如果发现 ID相同,证明链表有环
  • 哈希表缓存

首先创建一个以 节点ID为键HashSet集合,用来 存储曾经遍历过的节点。然后同样是从头节点开始,依次遍历每一个节点,当新节点和HashSet集合当中存储的节点ID相同,则说明链表右环。

1
2
外层循环:HashSet集合存储曾经遍历的节点ID
内层循环:从头节点开始,如果遍历到最新的节点,ID与存储在HashSet集合中存储的节点ID相同,则证 明有环
  • 快慢指针

首先创建两个指针,指针1和指针2,同时指向这个头节点,指针1每次向下移动一个节点,指针2向下移动2个节点,比较节点是否相同,如果相同则说明链表有环。如果不同执行下一次循环。

OC学习:链表

二叉树

OC学习:二叉树

  • 先序遍历:对任一子树,先访问 ,然后遍历 左子树,最后遍历其 右子树
  • 中序遍历:对任一子树,先遍历 左子树,然后访问 ,最后遍历其 右子树
  • 后序遍历:对任一子树,先遍历其 左子树,然后遍历其 右子树,最后访问

二叉树算法题

  1. 已知某二叉树的前序遍历为 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

OC学习24:多环境配置

启动优化

我们这里所说的启动优化,一般是指 冷启动 情况下的启动优化,是指用户唤起App开始到AppDelegate 中的 didFinishLaunchingWithOptions 方法执行完毕为止,并以 main() 函数为分界点,分为 pre-mainmain() 两个阶段:

  1. pre-main 阶段:是指从用户唤起App到 main() 函数执行之前的过程

针对这几部,有以下几点优化建议:重点面试题

  • 尽量 少用外部动态库,苹果官方建议自定义的动态库最好 不要超过6个,如果超过6个,需要 合并 动态库
  • 减少 OC 类,因为类越多,越耗时
  • 将不必须在 +load 方法中做的事情延迟到 +initialize 中,尽量不要用 C++ 虚函数
  • 如果是Swift,尽量使用 struct
  1. 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
2
3
4
5
6
7
8
9
10
11
12
static id _instance = nil; // 定义static全局变量,保证只分配一次内存
+ (id)shareInstance{
return [[self alloc] init];
}
+ (id)copyWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken; // 声明一个静态的GcD单次任务
// 保证只执行一次
dispatch_once(&onceToken,^{ //
_instance = [super allocWithZone:zone];
});
return p;
}

这么写可以保证下面两种方式返回同一个实例:

1
2
3
Single * p1 = [Single shareInstance];
Single * p2 = [[Single alloc] init];
NSLog(@"%d",p1==p2); // 1

成员变量和实例变量区别?

  • 成员变量:在文件中@interface下{}内的均统称为 成员变量

  • 实例变量:实例变量是 类定义 的变量

  • 区别:

    • 去除基本数据类型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
2
3
4
5
6
7
// 字符串 变 类
Class class = NSClassFromString(@"student");
Student * stu = [[class alloc] init];

// 类名 变 字符串
Class class =[Student class];
NSString * className = NSStringFromClass(class);
  • SEL的反射:字符串方法 相互转换
1
2
3
4
5
6
// 字符串 变 方法
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@“ZJ"];

// ⽅法 变 字符串
NSString * methodStr = NSStringFromSelector(@selector*(setName:)

@synthesize 和 @dynamic 分别有什么作用?

  • @synthesize(sɪnθəsaɪz) 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

  • @dynamic(daɪˈnæmɪk) 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成

NSSet和NSArray的区别?

  • NSSet 无序集合,存储方式 不连续。NSArray有序集合,存储方式 连续
  • NSSethash 算法,查询效果高
  • 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
2
3
4
5
- (nullable id)valueForKey:(NSString *)key;     
- (nullable id)valueForKeyPath:(NSString *)keyPath;

- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

KVC底层原理:

KVO是什么?重点

KVO 全程 Key Value Observing,中文 键值观察,它 用于监听实例对象属性的变化

KVO的实现原理?(KVO的本质是什么?) 重点

当一个 实例对象属性注册了KVO,实例对象 isa指针 的指向在注册KVO观察者之后,由 原有类 改为 中间类(NSKVONotifing_类名)中间类 重写了 属性setter方法、class、dealloc、_isKVOA 方法;dealloc 方法中,移除 KVO 观察者之后,实例对象isa 指向由 中间类 更改为 原有类;中间类 从创建后就 一直存在内存中,不会被销毁。

KVO实际应用 重点

  • 观察 实例对象属性 变化

  • 观察 实例对象容器 变化

观察容器用: mutableArrayValueForKey

KVO底层原理:

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
2
[_timer invalidate];
_timer = nil;

OC 如何进行内存管理的? 重点

  • 手动内存管理 MRC

  • 自动内存管理 ARC

LLVM + Runtime 会为我们代码自动插入 retainrelease 以及 autorelease等代码,不需要我们手动管理

  • 自动释放池

自动释放池原理 重点

  • 自动释放池的本质是 __AtAutoreleasePool 结构体,包含 构造函数和析构函数

  • 结构体声明,触发构造函数,调用 objc_autoreleasePoolPush() 函数,对象压栈

  • 结构体出作用域,触发析构函数,调用 objc_autoreleasePoolPop() 函数,对象出栈

内存优化 重点

  • cell复用
  • 绘制的话:用CAShaperLayer,渲染快,内存使用高效
  • 按需加载:懒加载
  • 合理利用缓存:比如三方图片压缩缓存
  • 尽量使用透明View:控件有背景色增大内存消耗

卡顿监听 重点

主要是用 displayLink + Runloop 进行FPS监测

主要从减轻 CPUGPU 消耗入手,保证写一个 VSync 到来时,CPUGPU 能够写作完成下一帧的渲染并缓存到帧缓冲区

  • 卡顿优化在 CPU 层面:

1)尽量用轻量级的对象,比如 用不到事件处理 的地方,可以考虑使用 CALayer 取代 UIView
2)不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
3)尽量 提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
4)图片的 size 最好刚好跟 UIImageViewsize 保持一致
5)控制一下线程的最大并发数量
6)尽量把 耗时的操作放到子线程:如text宽高获取等

  • 卡顿优化在 GPU层面:

1)GPU能处理的 最大纹理 尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
2)尽量 减少视图数量和层次
3)减少透明的视图(alpha<1),不透明的就设置 opaqueYES
4)尽量 避免出现离屏渲染:圆角、阴影、遮罩等

tableView卡顿优化 重点

  • 最常用的就是cell的复用, 注册复用标识符

  • 避免cell的重新布局,初始化时就布局好

  • 提前计算并缓存cell的高度

  • 减少cell中控件的数量,少动态添加 view

  • 避免背景透明

  • 使用局部更新 的就使用 局部更新

  • 加载网络数据,下载图片,使用异步加载,并缓存

  • 按需加载cell:cell滚动很快时,只加载范围内的cell

  • 不要实现无用的代理方法,tableView只遵守两个协议

网络优化 重点

  • DNS优化:即域名解析优化,缓存 ip
  • 资源优化:
    • 图片webp,比png/jpg小
    • 数据传输用ProtocolBuffer代替json,数据小,序列化和反序列化也简单
  • 请求压缩、请求合并
  • 数据缓存
  • 网络环境监测
    • 针对性请求重试

SDWebImage流程

  1. 入口 setImageWithURL:placeholderImage:options:
    会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
  2. 进入 SDWebImageManagerdownloadWithURL:delegate:options:userInfo:
    交给 SDImageCache 从缓存查找图片是否已经下载
    queryDiskCacheForKey:delegate:userInfo:
  3. 先从内存图片 缓存查找 是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 会调用 imageCache:didFindImage:forKey:userInfo:SDWebImageManager
  4. SDWebImageManagerDelegate 会调 webImageManager:didFinishWithImage:UIImageView+WebCache 等前端展示图片。
  5. 如果 内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从 硬盘查找 图片是否已经缓存。
  6. 根据 URLKey 在硬盘缓存目录下尝试 读取图片文件,这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate
  7. 如果上一操作 从硬盘读取到了图片,将 图片添加到内存缓存 中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调imageCache:didFindImage:forKey:userInfo:,进而回调展示图片。
  8. 如果从 硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要 下载图片
    回调 imageCache:didNotFindImageForKey:userInfo:
  9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  10. 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  11. connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
  12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  13. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader
  15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  16. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  17. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  18. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
    19)SDWebImage 也提供了 UIButton+WebCacheMKAnnotationView+WebCache,方便使用。
    20)SDWebImagePrefetcher 可以预先下载图片,

缓存策略:

  • 它的底层是用 NSCache 在实现的,NSCacheFoundation框架 提供的缓存类的实现,它是线程安全的。在内存不足时NSCache会自动释放存储的对象。key是不会重复的。

  • SDWebImage的缓存策略:缓存有关的一共有四个文件 SDImageCacheConfig和SDImageCache,SDImageCacheConfig配置类,保存一些缓存策略的信息(压缩图片-YES、iCloud备份-关闭、内存做缓存-YES、最长时间默认1周、缓存占用最大的空间-字节),

  • SDImageCache通过 url作为keyUIImage作为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保留在屏幕上。上拉同理。

面试官问还有什么想问的? 重点

技术面不问薪资待遇,人事面问薪资待遇

  • 问岗位:
  1. 在这个岗位上,会直接 接触到哪些类型的项目?
  2. 这个岗位的主要职责是什么?主要的KPI是什么?
  3. 这个岗位如何评估绩效,试用期需要达到什么指标?
  4. 这个岗位的最大挑战是什么?
  • 问团队:
  1. 团队的基本情况?
  2. 这个团队在公司的角色是什么?
  3. 可以跟我介绍一下我的领导吗?
  • 问公司
  1. 公司的文化氛围是什么样的?
  2. 员工的晋升机制是什么样的?我这个岗位的晋升机会如何?
  3. 这个岗位所在的团队如何支持公司目标的实现?
  • 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.
On this page
OC面试题:总结