随笔
编译
什么是LLVM和Clang?
LLVM 全称 Low Level Virtual Machine ,底层虚拟机,
宏观:LLVM不仅是一个编译器或者虚拟机,它是一个众多编译器工具及低级工
具技术的统称,包含一个前端、优化器、后端以及众多函数库和模板。
微观:可以看做后端编译器,前端编译器为Clang。
Clang(LLVM 是一部分)是一个有C++编写的编译器前端,能够编译C、C++、
Objc等高级语言,属于LLVM的一部分。主要用于词法分析、语法分析、语义分
析、生成中间代码。
Clang与GCC区别?
(1)Clang比Gcc编译用时更短
(2)Clang比Gcc占用内存更小
(3)Clang生成的中间产物比Gcc更小
(4)Clang错误提示比Gcc更友好
(5)Clang有静态分析,Gcc没有
(6)Clang从开始就被设计为API,允许代码分析工具和IDE集成
(7)Gcc比Clang支持更多语言和平台
Clang如何使用?
Clang功能非常强大,可以通过Clang观察到编译器运行的几个阶段:
(1)词法分析
(2)语法分析
(3)生成抽象语法树(AST)
(4)生成中间代码(IR)
(5)编译器优化
(6)生成目标文件
(7)运行目标文件
比如使用Clang将Objc代码转化成C++代码:
clang -rewrite-objc main.m //将oc->c++
CocoaPods的工作原理
(1)通过建立podfile文件,指定第三方库
(2)使用pod install安装框架
(3)第三方框架被编译为libpods.a的静态库,主项目依赖这个静态库
动态库和静态库的区别
(1)存在形式上:静态库已.a或者.framework为文件后缀,动态库以.dylib或
者.framework为文件后缀
(2)使用区别上:静态库链接时,被完整的复制到可执行文件中,动态库链接
时,不复制,程序运行时由系统动态加载到内存,供程序调用,当多个程序共同
调用时,只加载一次,以节省内存开销。
iOS中动态库不是真正动态库,打包时会随着代码混在一起,启动时自动加载,
自定义动态库并不能在系统中共享
RunLoop运行机制
RunLoop实现
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval
seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName,
seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds,
stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop,
modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds,
returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode,
kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode,
kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop,
currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个
Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort,
&msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode,
kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休
眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer),
&livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for
receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode,
kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode,
mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(
msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 =
__CFRunLoopModeFindSourceForMachPort(runloop, currentMode,
livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop,
currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
性能
内存泄漏检测
内存大图片检测
图片主线程解压缩检测
卡顿检测
帧率检测
网络性能监控
Crash 监控
Abort 监控 (指由于 jetsam 机制杀死 APP,或者主线程长时间未反应被
watchdog 杀死等情况,这些情况不具有堆栈,定位困难)
基于页面级别的内存消耗监控
卡顿监控
DNS 解析情况监控
启动时间监控
精准 iOS 内存泄露检测工具
MLeaksFinder
PLeakSniffer
图片加载速度优化
FastImageCache
卡顿检测
1.iOS应用UI线程卡顿监控 :是通过 ping 主线程,看主线程一定时间呢,是否
及时回复
2.iOS 实时卡顿监控:是通过 Runloop,监控主线程处理事件一次 loop 的时间
3.FPS :当帧率过低时,也可以表明卡顿,帧率主要是通过 CADisplayLink 来做
的
4.比较堆栈:主要是比较主线程一定时间内堆栈是否一致,假如一分钟获取一
次,相邻两次获取结果相同,则代表被卡住了
日志
微信跨平台组件mars-xlog架构分析及迁移思路
UI+性能
UIView和CALayer是什么关系?
UIView继承自UIResponder , 负责用户事件响应
CALayer继承自NSObject ,负责绘制内容
UIView当中有layer的一个属性,并且实现了CALayerDelegate的相关协议。这
样UIView就具有了响应用户时间并且能够绘制内容的能力。两者为互相依赖的关
系
UIView的drawRect方法
UIView的绘图操作是drawRect方法实现的
需要重写drawRect方法,实现绘图操作
触发drawRect方法
调用setNeedsDisplay方法
设置frame
更改bounds
View的contentModel为UIViewContentModeRedraw时
UIImageView加圆角
离屏渲染
(1)需要额外创建缓冲区来存放渲染结果
(2)需要多次切换上下文环境,先从当前屏幕切换到离屏,然后进行渲染,渲
染完毕后,再将缓冲区的渲染结果显示到屏幕上,这需要将上下文切换到当前屏
幕上
修改cornerRadius和masksToBounds属性 导致离屏渲染
(1)开启一个子线程,使用贝萨尔曲线和CoreGraphics绘制圆角,绘制完成后
切换到主线程刷新
code
- (void)drawCircle:(UIImage *)image{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *imageCircle = [image drawImageCircle];
dispatch_async(dispatch_get_main_queue(), ^{
[self.imageView setImage: imageCircle];
});
});
}
"UIImage+Circle.h"
- (instancetype)drawImageCircle{
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
UIBezierPath *path = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0, 0, self.size.width,
self.size.height)];
[path addClip];
[self drawAtPoint:CGPointZero];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
(2)将CALayer的shouldRasteriz设置为YES
CALayer光栅化为bitmap,保存离屏渲染后的缓冲区,减少重复渲染的过程,适
用于复用圆角UIImageView的地方,例如cell上
self.imageView.layer.shouldRasterize = YES;
self.imageView.layer.rasterizationScale = [UIScreen mainScreen].scale;
(3)使用CASharpLayer和UIBezierPath设置圆角
UIBezierPath *bPath = [UIBezierPath
bezierPathWithRoundedRect:self.imageView.bounds
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:self.imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.imageView.bounds;
maskLayer.path = bPath.CGPath;
self.imageView.layer.mask = maskLayer;
(4) 切图使用圆角图
另外制作纯色背景圆角图(删除alfa通道)进行叠加
(5) 删除alfa通道, opaque不透明度
能删除的删掉
(6) 尽量保证图片显示不再进行裁剪
contentMode 根据类型进行不同裁剪 (图片比例和Frame尽量一致 )
UILabel
使用UILabel时设置背景不透明
显示大量文本时采用异步文本绘制
大量文本绘制时,CPU 的压力会非常大,
用 TextKit 或最底层的 CoreText 对文本异步绘制
block
非逃逸:声明的block的生命周期就是声明所在的函数体的生命周期。我们在函
数体中声明一个block,这个block会在函数体结束时释放
逃逸:声明的block生命周期和声明所在的函数体无关了。我们在函数A中声明的
block,在B中也可以调用
NSGlobalBlock
不捕获自动变量的类型或者捕获的是静态局部变量(全局变量不需要捕获)
值捕获:
捕获的变量为其指针指向的值,或基础数据类型的值
地址捕获:
捕获的变量为其指针本身,或指向基础数据类型的指针
MRC 环境 Copy 修饰 Block
会将栈区的 Block 拷贝到堆区
ARC 环境 使用 Strong、Copy 修饰 Block
都会将栈区的 Block 拷贝到堆区
循环引用
block会把写在block里的变量copy一份,如果直接在block里使用self,block就
会对self持有,而self对block持有(self对变量默认是强引用), 导致循环引用,
所以这里需要声明一个弱引用weakSelf,让block引用weakSelf,打破循环引用
strongSelf
如果这个block是异步调用而且调用的时候self已经释放了,这个时候weakSelf已
就变成了nil, 此时用strongSelf对weakSelf强引用,,当block执行完毕后,
strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放
深拷贝和浅拷贝
1.浅拷贝 :
不可变对象copy操作
比如NSArray、NSString 、NSSet、NSDictionary 等等
2.深拷贝 :
可变(Mutable)对象copy操作
可变(Mutable)对象mutableCopy操作
集合拷贝
NSArray + copy :
指向同一个内存地址(创建一个内存地址引用指针而已)
集合内部对象地址不变
浅拷贝
NSArray + mutableCopy :
内存地址发生了变化
集合内部对象地址不变
深拷贝
NSMutableArray + copy :
内存地址发生了变化
集合内部对象地址不变
深拷贝
NSMutableArray + mutableCopy :
内存地址发生了变化
集合内部对象地址不变
深拷贝
3.单层深拷贝 :
对于不可变的容器类对象(如NSArray、NSSet、NSDictionary)进 mutableCopy
操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化,属于单
层深拷贝
动画
显式动画 :
比如:用户自己通过beginAnimations:context:和commitAnimations创建的动
画
开启需要手动创建并开启
隐式动画 :
系统偷偷执行的动画, 默认就有, 不需要手动创建
比如:通过UIView的animateWithDuration:animations:方法创建的动画
默认自动执行, 关闭需要手动设置
NSAutoreleasePool
每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个
AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)自动释放池
中的 AutoreleasePoolPage 是以双向链表的形式连接起来
写法1:
NSAutoreleasePool*pool = [[NSAutoreleasePoolalloc]init ];//创建一个自动
释放池
Person *person = [[Person alloc]init];
person.age = 10;
[person autorelease];//释放
[pool release];//释放
写法2:
@autoreleasepool {
Person *person = [[Person alloc]init];
person.age = 10;
}
最终执行 :
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
最终 :
autoreleasepool 本质就是调用Push开始, 调用Pop时释放
而这两个函数都是对AutoreleasePoolPage的简单封装
销毁 :
调用 objc_autorelease
AFNetworking 做了啥 ?
提前证书认证 :
AF可以让你在系统验证证书之前,就去自主验证。然后如果自己验证不正确,直
接取消网络请求。否则验证通过则继续进行系统验证
系统验证的流程:系统的验证,首先是去系统的根证书找,看是否有能匹配服务
端的证书,如果匹配,则验证成功,返回https的安全数据
如果不匹配则去判断ATS是否关闭,如果关闭,则返回https不安全连接的数据。
如果开启ATS,则拒绝这个请求,请求失败
2.x : 开辟一条常驻线程 :
多个异步请求同时返回数据, 如果回到主线程,会堵塞. 如果开辟多条子线程, 会导
致线程数太多, 因此,开辟一条常驻子线程,数据回调处理均在此线程处理
1. 创建全局线程NSThread *
2. [runLoop addPort:[NSMachPort port]
forMode:NSRunLoopCommonModes];
启动 : [runLoop run];
保持runLoop需要source和mode , 不然会立马退出
3.x 不再需要常驻线程, 因为苹果使用NSURLSession 替换NSURLConnection ,
弥补了其缺陷
但是需要设置self.operationQueue.maxConcurrentOperationCount = 1;
让并发的请求串行(xxCount = 1)的进行回调
operationQueue是用来接收NSURLSessionDelegate回调
做了各种请求方式request的参数拼接
Block 回调(失败,成功, 进度条)
做了各种错误的判断,保证了成功和失败的回调
状态栏小菊花 : 并发请求操作
等等