随笔 编译 什么是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 回调(失败,成功, 进度条) 做了各种错误的判断,保证了成功和失败的回调 状态栏小菊花 : 并发请求操作 等等