3~5年开发经历的 iOS工程师 应该知道的内容~本文总结以下内容
- 视图烘托和离屏烘托
- 事情传递和呼应链
- crash处理和功能优化
- 编译流程和发动流程
导航
iOS 进阶常识总结(一)
- 目标
- 类目标
- 分类
- runtime
- 音讯与音讯转发
iOS 进阶常识总结(二)
- KVO
- KVC
- 多线程
- 锁
- runloop
- 计时器
iOS 进阶常识总结(三)
- 视图烘托和离屏烘托
- 事情传递和呼应链
- crash处理和功能优化
- 编译流程和发动流程
iOS 进阶常识总结(四)
- 内存办理
- 野指针处理
- autoreleasePool
- weak
- 单例、通知、block、继承和集合
iOS 进阶常识总结(五)
- 网络根底
- AFNetWorking
- SDWebImage
烘托
屏幕撕裂的原因?
- 单一缓存形式下,帧缓冲区只要一个缓存空间
- 图片需求经过
CPU -> 内存 -> GPU -> 展现
的烘托进程 - CPU和GPU的协作进程中呈现了偏差,GPU应该完整的制作图片,但是工作慢了只制作出图片上半部分。
- 此时CPU又把新数据存储到缓冲区,GPU持续制作的时分下半部分就变成了新数据。
- 形成了两帧一起呈现了一部分在屏幕上,看起来就撕裂了。
怎样处理屏幕撕裂?
- 处理上一帧和下一帧的覆盖问题,需求运用不同的缓冲区,经过两个图形缓冲区的替换来处理。
- 呈现速度差的时分,就把下一帧存储在后备缓冲区,制作完结后再切换帧。
- 当制作完终究一个像素点就会发出这个笔直同步信号通知展现完结。
- 所以屏幕撕裂问题需求经过 双缓冲区 + 笔直同步信号 处理。
掉帧是怎样产生的?
- 屏幕正在展现A帧的时分,CPU和GPU会处理B帧。
- A帧展现完结该切换展现B帧的时分B帧的数据未预备好。
- 没办法切换就只能重复展现A帧,感官上便是卡了,这便是掉帧的问题
怎样处理掉帧?
- 掉帧底子原因是CPU和GPU烘托核算耗时过长
- 1、下降视图层级
- 2、提前或削减在烘托期的核算
CPU
烘托功能
- 布局核算:假如视图层级过于杂乱,呈现或修正的时分需求耗费许多时刻核算
-
视图懒加载:当显现的时分才会加载视图,这对内存运用和程序发动时刻很有优点。但展现前的操作都不会被及时呼应,当视图从
xib
加载或许涉及图片显现,懒加载都会比正常加载慢。 -
解压图片:
PNG
或许JPEG
紧缩之后的图片会比位图小得多。在制作到屏幕之前,必须把它扩展成完整的尺寸(通常等同于图片宽 x 长 x 4个字节)。为了节省内存,真实制作的时分(榜首次赋值UIImageView
或许把它制作到Core Graphics
)才会解码图片,大图会占用必定的时刻。 -
Core Graphics
制作:完成了drawRect:
、drawLayer:inContext:
、CALayerDelegate
的办法,在制作前都会产生一个巨大的功能开支以拓荒绘画上下文。 -
图层打包:由于
GPU
不知道图层结构,CPU
需求经过OpenGL
把烘托树中的每个可见图层转换成纹路三角板。CPU
的工作量和图层量成正比,假如层级联系太杂乱就会烘托速度。
GPU
烘托功能
-
GPU
会依据生成的前后帧缓存数据,依据实际情况进行组成 - 形成
GPU
烘托担负:离屏烘托,图层混合,延迟加载
一个UIImageView
添加到视图上今后,内部怎样烘托到手机上的?
- 图片显现分为三个进程: 加载、解码、烘托
- 通常,咱们程序员的操作只是加载,至于解码和烘托是由
UIKit
内部进行的。 - 例如:
UIImageView
显现在屏幕上的时分需求赋值iamge
。UIImage
持有的数据是未解码的紧缩数据,赋值的时分图画数据会被解码变成RGB
色彩数据,终究烘托到屏幕上。
说说烘托流程
- 1、承认极点方位:
CPU
承认制作图形的方位,从iOS
坐标系换算成屏幕坐标系。 - 2、图元安装:承认极点间的连线联系。
- 3、光栅化:便是把展现用到的像素点摘出来。
- 4、着色器处理:
GPU
核算像素点展现的色彩,并存入缓冲区。 - 5、屏幕展现
什么是离屏烘托
- 普通烘托流程:APP – 帧缓冲区 – 展现
- 离屏烘托流程:APP – 离屏烘托缓冲区 – 帧缓冲区 – 展现
- 离屏烘托,是无法一次性处理烘托,需求分部处理并存储中心成果引起的。
- 所以判别是否呈现离屏烘托的底子条件便是判别烘托是否需求分部处理~
- 需求分步处理,会产生离屏烘托
- 一次性烘托,不产生离屏烘托
离屏烘托的影响
- 需求分几步就需求拓荒出几个离屏烘托缓冲区存储中心成果,形成空间浪费。
- 终究合并多个离屏烘托缓冲区才干展现成果,会影响功能。
什么操作会触发离屏烘托?
- 光栅化
layer.shouldRasterize = YES
- 遮罩
layer.mask
- 圆角
layer.maskToBounds = Yes
,Layer.cornerRadis
- 阴影
layer.shadowXXX
视图
AutoLayout
的原理,功能怎样
-
Auto Layout
只关注视图之间的联系,经过布局引擎和已有的束缚核算出各个视图的frame
- 每逢束缚改动时会从头核算各个视图的
frame
- 取得
frame
的进程,便是依据各个视图已有的束缚条件解方程式的进程。 - 功能会随着视图数量的添加呈指数级添加
- 到达必定数量的视图时,布局所需求的时刻就会大于16.67ms,超过屏幕的改写频率时会呈现卡顿。
你认为主动布局怎样完成的
- 原理是线性公式,运用了体系提供的
NSLayoutConstraint
-
Masonry
依据它封装
ViewController
生命周期
-
initWithCoder
:经过nib文件初始化时触发。 -
awakeFromNib
:nib文件被加载的时分,会产生一个awakeFromNib
的音讯到nib
文件中的每个目标。 -
loadView
:开端加载视图控制器自带的view
。 -
viewDidLoad
:视图控制器的view
被加载完结。 -
viewWillAppear
:视图控制器的view
即将显现在window
上。 -
updateViewConstrains
:视图控制器的view
开端更新AutoLayout
束缚。 -
viewWillLayoutSubviews
:视图控制器的view
即将更新内容视图的方位。 -
viewDidLayoutSubviews
:视图控制器的view
现已更新视图的方位。 -
viewDidAppear
:视图控制器的view
现已展现到window
上。 -
viewWillDisappear
:视图控制器的view
即将从window
上消失。 -
viewDidDisappear
:视图控制器的view
现已从window
上消失。
LayoutSubviews
调用时机
-
init
初始化不会调用LayoutSubviews
办法 -
addsubView
时分会调用 - 改动一个
view
的frame
的时分调用 - 翻滚
UIScrollView
导致UIView
从头布局的时分会调用 - 手动调用
setNeedsLayout
或许layoutIfNeeded
setNeedsLayout
和layoutIfNeeded
的差异
-
setNeedsLayout
符号为需求从头布局- 异步调用
layoutIfNeeded
改写布局,不当即改写,鄙人一轮runloop
结束前改写。 - 关于这一轮
runloop
之内的一切布局和UI更新
只会改写一次,layoutSubviews
必定会被调用。
- 异步调用
-
layoutIfNeeded
- 假如有需求改写的符号,当即调用
layoutSubviews
进行布局 - 假如没有符号,不会调用
layoutSubviews
- 假如想在当时
runloop
中当即改写,调用次序应该是
[self setNeedsLayout]; [self layoutIfNeeded];
- 假如有需求改写的符号,当即调用
drawRect
调用时机
-
drawRect
在loadView
和ViewDidLoad
之后调用
UIView
和CALayer
是什么联系?
-
View
能够呼应并处理用户事情,CALayer
不能够。 - 每个
UIView
内部都有一个CALayer
提供尺寸款式(模型树),进行制作和显现。 - 两者都有树状层级结构,
layer
内部有subLayers
,view
内部有subViews
。 -
CALayer
是支持隐式动画的,View
作为Layer
的署理,经过actionForLayer:forKey:
向Layer
提交相应的动画 - layer 内部维护着三分
layer tree
- 动画树
presentLayer Tree
,修正动画的特点改的是动画树的特点值 - 模型树
modeLayer Tree
,终究展现在界面上的其实是提供视图的模型树 - 烘托树
render Tree
。
- 动画树
UIView
显现原理
-
UIView
能够显现是由于内部有一个layer
作为根图层,根图层上能够放其他子图层。 -
UIView
中一切能够看到的内容都包含在layer
中 - 当
UIView
需求显现到屏幕上会调用drawRect:
办法进行绘图,而且会将一切内容制作在自己的layer
上 - 绘图结束后,体系将图层展现到屏幕上,完结了UIView的显现。
UIView
显现进程
-
view.layer
创立一个图层类型的上下文(Layer Graphics Contex
) - 触发署理办法
drawLayer:inContext:
,传入刚才预备好的上下文 -
drawLayer:inContext:
内部会让view
调用drawRect:
办法绘图 - 开发者在
drawRect:
办法中完成绘图代码,内容终究制作到view.layer
- 体系将
view.layer
展现到屏幕,完结了view
的显现
UITableView
的重用机制?
-
UITableView
经过重用单元格来节省内存 - 为每个单元格指定一个重用标识符,即指定了单元格的种类
- 当屏幕上的单元格滑出屏幕时,体系会把这个单元格添加到重用队列中,等候被重用。
- 当有新单元格从屏幕外滑入屏幕内时,从重用队列查找可重用的单元格,假如有就拿来用,假如没有就创立一个运用。
UITableView
卡顿的的原因有哪些?
- 隐式制作
CGContext
- 制作
Core Graphics
- 文本
CATextLayer
和UILabel
- 截图
-renderInContext:
- 可弹性图片
- 混合图层
- 目标回收
- 离屏烘托
- 光栅化
shouldRasterize
- 阴影作用
shadowPath
- 圆角裁切
cornerRadius
- 遮罩
- 光栅化
UITableVIew
优化
- 重用机制(缓存池)
- 少用有透明度的
View
- 尽量防止运用
xib
- 尽量防止过多的层级结构
- iOS8今后出的预估高度
- 削减离屏烘托操作(圆角、阴影)
- 缓存
cell
的高度(提前核算好cell
的高度,缓存进当时的模型里边) - 异步制作
- 滑动的时分,按需加载
- 尽量少
add、remove
子控件,最好经过hidden
控制显现
imageName
与imageWithContentsOfFile
差异?
-
imageWithContentsOfFile
:加载本地目录图片,不能加载image.xcassets
里边的图片资源。不缓存占用内存小,相同的图片会被重复加载到内存中。不需求重复读取的时分运用。 -
imageNamed
:能够读取image.xcassets
的图片资源,加载到内存缓存起来,占用内存较大,相同的图片不会被重复加载。直到收到内存正告的时分才会开释不运用的UIImage
。需求重复读取同一个图片的时分用。
IBOutlet
连出来的视图特点为什么能够被设置成weak
?
- 由于
Xcode
内部把链接的控件放进了一个_topLevelObjectsToKeepAliveFromStoryboard
的私有数组中,这个数组强引用这一切topLevel
的目标,所以用weak
也无伤大雅。
UIScrollerView
完成原理
- 翻滚其实是在修正原点坐标。当手指接触后,
scrollview
阻拦接触事情创立一个计时器。 - 假如计时器到点后没有产生手指移动事情,
scrollview
发送tracking events
到被点击的subview
。 - 假如计时器到点前产生了移动事情,
scrollview
撤销tracking
自己翻滚。
怎样完成视图的变形?
- 修正
view
的transform
。
事情呼应链和事情传递
什么是呼应链
- 由链接在一起的呼应者(
UIResponse
及其子类)组成的链式联系。 - 最先的呼应者称为榜首呼应者
- 最底层的呼应者是
UIApplication
写出一个呼应链
subView -> view -> superView -> viewController -> window -> application
什么是事情传递
- 触发事情后,事情从榜首呼应者到
application
的传递进程
事情传递的进程
- 当程序中产生接触事情之后,体系会将事情添加到
UIApplication
办理的一个队列当中 -
application
将使命队列的首个使命向下分发 application -> window -> viewController -> view
-
view
需求满意条件才干够处理使命,透明度>0.01、接触在view
的区域内、userInteractionEnabled=YES
、hidden=NO
。 - 满意条件的
view
遍历本身的subViews
,判别是否满意上述条件 - 假如一切
subView
都无法满意条件,那么最佳呼应者便是自己。 - 假如没有任何一个
view
能处理事情,事情会被抛弃。
找出接触的View
// 回来的View是本次点击的最佳呼应者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// 判别点是否落在某区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
重写hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判别当时控件能否接纳事情
if (self.userInteractionEnabled == NO ||
self.hidden == YES ||
self.alpha <= 0.01) {
return nil;
}
// 2. 判别点在不在当时控件
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[I];
// 把当时控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束 没有比自己更合适的view
return self;
}
Crash
常见Crash的原因有哪些?
- 1、找不到办法的完成
unrecognized selector sent to instance
- 2、
KVC
形成的crash - 3、
KVO
形成的crash - 4、
EXC_BAD_ACCESS
- 5、集合类相关溃散,越界等
- 6、多线程中的溃散,运用了被开释的目标
- 7、后台回来错误的数据结构
BAD_ACCESS
在什么情况下呈现?
- 拜访现已开释目标的成员变量
- 给现已开释目标发音讯
- 死循环
不运用第三方,排查闪退问题?
- 1、运用
NSSetUncaughtExceptionHandler
计算闪退的信息 - 2、将计算到的信息发给后台
- 3、在后台搜集信息,进行排查
static void my_uncaught_exception_handler (NSException *exception) { //获取NSException 信息 NSLog(@"***********************************************"); NSLog(@"%@",exception); NSLog(@"%@",exception.callStackReturnAddresses); NSLog(@"%@",exception.callStackSymbols); NSLog(@"***********************************************"); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler); return YES; }
功能优化
优化发动时刻
- 发动时刻是用户点击App图标,到榜首个界面展现的时刻。
- 发动时刻在小于400ms是最佳的,由于从点击图标到显现
Launch Screen
,到Launch Screen
消失这段时刻是400ms。 - 发动时刻不能够大于20s,不然会被体系杀掉。
以main
函数作为分水岭,发动时刻其实包含了两部分:
mian
函数之前的发动优化
- 削减或合并动态库(这是目前为止最耗时的了, 基本上占了95%以上的时刻)
- 承认动态库是
optional or required
。假如该Framework
在支持的一切iOS体系版本都存在,那么就设为required
,不然就设为optional
,由于optional
会有些额定的查看
mian
函数之后的发动优化
- 1、合并和删减不必要的类或许分类
- 2、将不必需在
+load
办法中做的事情,延时放到+initialize
。 - 3、非必要的 SDK 和装备事情能够放在榜首个界面处理
- 4、削减创立线程,线程创立和运转需求多次的上下文切换所带来的开支,均匀耗费大约在 29 毫秒。这是很大的时刻开支,应该防止乱用。
- 5、编译器插桩获取办法符号,生成
order file
设置到xcode
。削减页中断带来的耗时。
网络优化
-
IP
直连,将咱们原本的恳求链接www.baidu.com
修正为180.149.132.47
- 运营商在拿到恳求后发现是
IP
地址会直接放行,而不会去走DNS解析 - 不走他的
DNS
解析也就不会存在DNS被劫持的问题 - 完成办法1:接运用
HTTPDNS
等sdk - 完成办法2:服务端下发
发域名-IP
对应列表,客户端缓存,经过缓存IP来进行事务拜访。
包体积优化
- 1、删去陈腐代码、删去陈腐
xib/sb
,删去无用的资源文件(检测未运用图片的工具LSUnusedResources
) - 2、图片、音视频资源紧缩后运用。
- 3、动图能够运用
webP
格式,加载速度比较慢,但体积小 - 4、能用动态库的尽量用动态库,一般情况静态库的体积会比动态库大
- 5、主题类的资源文件提供按需下载功能,不直接打包在运用包里边
- 6、
App Slicing
,运用程序切片,只打包下载机型所需的资源文件,不需求开发者处理 - 7、
Bitcode
,中心代码, 苹果会对可执行文件进行优化,不需求开发者处理 - 8、
ODR,On Demand Resources
,按需加载资源,需求开发者处理
电量优化
- 1.定位,尽量不要实时更新,能够恰当下降精度
- 2.网络恳求,能用缓存的时分尽量运用缓存,下降恳求的频率,削减恳求的次数,优化传输结构
- 3.CPU处理,需求复用的数据能缓存的数据尽量缓存,优化算法和数据结构
- 4.GPU处理,削减离屏烘托
短视频/直播优化
- 多播映器,像
tableview
那样维护缓存池。多播映器,才干做预加载。 - 边下边播,要完成下一个视频或许几个视频能快速的播映起来,首先应该保证正在播映的短视频能顺畅播映,边下边播使命优先级应该高于预加载使命。没有边下边播时才干执行预加载,边下边播使命进行时应当中止预加载。
- 预加载,预加载的下载应该和边下边播区分开,手势滑动到差不多呈现的时分,开端播映预加载的那
1m
数据。滑动下一个的时分,就撤销preload
,播映那1m
下载的数据,再持续load
。一起调起下一个preload
预备下载。
短视频缓存办理
- 缓存首要创立了三个目录办理,分别为
temp、media、trash
目录 - 缓存分为暂时缓存和终究缓存,当短视频资源未下载完时是放在
temp
、当视频资源缓存完时移动到media
,这样分别存放便能方便读取和办理两种状态的缓存, - 一切需求删去的缓存文件都移入
trash
,随后再删去以此来保证较高的删去效率。一切文件命名运用视频url的md5值
保证唯一性。 - 缓存应该具有主动办理功能,默认装备下
ShortMediaCache
答应暂时缓存最多保存1天,最大100Mb
,而终究缓存则答应最多保存2天最大200Mb
,假如事务需求能够自定义ShortMediaCacheConfig
装备完成。
一般是怎样用Instruments
的?
-
Instruments
里边工具许多,常用: -
Time Profiler
: 功能剖析 -
Zombies
:查看是否拜访了僵尸目标,但是这个工具只能从上往下查看,不智能。 -
Allocations
:用来查看内存。 -
Leaks
:查看内存,看是否有内存泄露。
编译 & 发动
编译链接流程
- 0、输入文件:找到源文件
- 1、预处理:包含替换宏和导入头文件
- 2、编译阶段:词法剖析、语法剖析、语义剖析,终究生成IR
- 2-1、预处理后会进行词法剖析,词法剖析会把代码切片成一个个
token
。 - 2-2、语法剖析会验证语法是否正确,在词法剖析的根底上把单词组组成各种语法短语,然后把节点组成抽象的语法树
- 2-3、代码生成器依据语法树自顶向下生成
LLVM IR
。OC
会在这儿进行runtime
的桥接:property
的组成、ARC
处理等
- 2-1、预处理后会进行词法剖析,词法剖析会把代码切片成一个个
- 3、编译器后端:经过一个个
Pass
去优化,每个Pass
完结一部分功能,终究生成汇编代码- 3-1、苹果对代码做了进一步优化,而且经过
.ll
文件生成.bc
文件。 - 3-2、能够经过
.bc
或.ll
文件生成汇编代码
- 3-1、苹果对代码做了进一步优化,而且经过
- 4、生成,
.o
格式的目标文件 - 5、链接动态库和静态库
- 6、经过不同架构,生成对应的可执行文件
MachO
APP发动进程
- 1、加载可执行文件(读取
Mach-O
) - 2、加载动态库(
Dylib
) - 3、
Rebase & Bind
- 3-1、
Rebase
的作用是修正ASLR
的偏移,把当时MachO
的指针指向正确的内存 - 3-2、
Bind
的作用是从头修正外部办法指针的指向,fishhook
原理
- 3-1、
- 4、
objc_init
,加载类和分类 - 5、
Initializers
,调用load
办法,初始化C & C++
的目标等 - 6、
main()
函数 - 7、执行
AppDelegate
的署理办法(如:didFinishLaunchingWithOptions
)。 - 8、初始化
Windows
,初始化ViewController
。
dyld
做了什么
- 1、
dyld
读取Mach-O
的Header
和Load Commands
- 2、找可执行文件的依靠动态库,将依靠的动态库加载到内存中。这是一个递归的进程,依靠的动态库可能还会依靠别的动态库,所以
dyld
会递归每个动态库,直至一切的依靠库都被加载结束。