// 供外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
// 调用CFRunLoopRunSpecific
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 其内部会调用 __CFRunLoopRun 函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果没找到 || 如果mode里没有source/timer/observer, 直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 取上一次运行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
// 如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
// 初始化一个result为kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 通知observer:即将进入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop逻辑
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//记录最后runloop状态,用于return
int32_t retVal = 0;
do {
//初始化一个存放内核消息的缓冲池
uint8_t msg_buffer[3 * 1024];
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
//取所有需要监听的port
__CFPortSet waitSet = rlm->_portSet;
//设置RunLoop为可以被唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知observer,即将触发timer回调,处理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer,即将触发Source0回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.处理source0事件
//有事件处理返回true,没有事件返回false
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果没有Sources0事件处理 并且 没有超时,poll为false
//如果有Sources0事件处理 或者 超时,poll都为true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do-while循环不会走该分支,因为didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//从缓冲区读取消息
msg = (mach_msg_header_t *)msg_buffer;
//5.接收dispatchPort端口的消息,(接收source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的话,前往第9步开始处理msg
goto handle_msg;
}
}
didDispatchPortLastTime = false;
//6.通知观察者RunLoop即将进入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//设置RunLoop为休眠状态
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//这里有个内循环,用于接收等待端口的消息
//进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
do {
//7.接收waitSet端口的消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//收到消息之后,livePort的值为msg->msgh_local_port,
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 醒来
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
//取消runloop的休眠状态
__CFRunLoopUnsetSleeping(rl);
//8.通知观察者runloop被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.处理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通过CFRunloopWake唤醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
//什么都不干,跳回2重新循环
// do nothing on Mac OS
}
//如果是定时器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1 处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是定时器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是dispatch到main queue的block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
//9.2执行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有source1事件待处理
if (rls) {
mach_msg_header_t *reply = NULL;
//9.2 处理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
}
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
__CFRunLoopDoBlocks(rl, rlm);
// 根据之前的执行结果来决定怎么做,为retVal赋相应的值
if (sourceHandledThisLoop && stopAfterHandle) {
//进入RunLoop时传入的参数,处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
}else if (timeout_context->termTSR < mach_absolute_time()) {
//RunLoop超时
retVal = kCFRunLoopRunTimedOut;
}else if (__CFRunLoopIsStopped(rl)) {
//RunLoop被手动终止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}else if (rlm->_stopped) {
//mode被终止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//mode中没有要处理的事件
retVal = kCFRunLoopRunFinished;
}
//除了上面这几种情况,都继续循环
} while (0 == retVal);
return retVal;
}
- 实际上 RunLoop 内部就是一个 do-while 循环
- 当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回
- 同时RunLoop有很多个mode,但是RunLoop在run的时候必须只能指定其中一个mode,运行起来之后,被指定的mode即为currentMode
LLDB常用的调试命令?
po:print object的缩写,表示显示对象的文本描述,如果对象不存在则打印nil。
p:可以用来打印基本数据类型。
call:执行一段代码 如:call NSLog(@"%@", @"yang")
expr:动态执行指定表达式
bt:打印当前线程堆栈信息 (bt all 打印所有线程堆栈信息)
image:常用来寻找栈地址对应代码位置 如:image lookup --address 0xxxx
断点调试?
条件断点
打上断点之后,对断点进行编辑,设置相应过滤条件。下面简单的介绍一下条件设置:
- Condition:返回一个布尔值,当布尔值为真触发断点,一般里面我们可以写一个表达式。
- Ignore:忽略前N次断点,到N+1次再触发断点。
- Action:断点触发事件,分为六种:
- AppleScript:执行脚本。
- Capture GPU Frame:用于OpenGL ES调试,捕获断点处GPU当前绘制帧。
- Debugger Command:和控制台中输入LLDB调试命令一致。
- Log Message:输出自定义格式信息至控制台。
- Shell Command:接收命令文件及相应参数列表,Shell Command是异步执行的,只有勾选“Wait until done”才会等待Shell命令执行完在执行调试。
- Sound:断点触发时播放声音。
- Options(Automatically continue after evaluating actions选项):选中后,表示断点不会终止程序的运行。
异常断点
异常断点可以快速定位不满足特定条件的异常,比如常见的数组越界,这时候很难通过异常信息定位到错误所在位置。这个时候异常断点就可以发挥作用了。
Exception:可以选择抛出异常对象类型:OC或C++。
Break:选择断点接收的抛出异常来源是Throw还是Catch语句。
符号断点
符号断点的创建方式和异常断点一样一样的,在符号断点中可以指定要中断执行的方法:
Symbol:[类名 方法名]可以执行到指定类的指定方法中开始断点。
iOS 常见的崩溃类型有哪些?
- unrecognized selector crash
- KVO crash
- NSNotification crash
- NSTimer crash
- Container crash
- NSString crash
- Bad Access crash (野指针)
- UI not on Main Thread Crash
造成tableView卡顿的原因有哪些?
- 最常用的就是cell的重用, 注册重用标识符
- 如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
- 如果有很多数据的时候,就会堆积很多cell。
- 如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
- 避免cell的重新布局
- cell的布局填充等操作比较耗时,一般创建时就布局好
- 如可以将cell单独放到一个自定义类,初始化时就布局好
- 提前计算并缓存cell的属性及内容
- 当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
- 而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
- 减少cell中控件的数量
- 尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
- 不适用的可以先隐藏
- 不要使用ClearColor,无背景色,透明度也不要设置为0
- 渲染耗时比较长
- 使用局部更新
- 如果只是更新某组的话,使用reloadSection进行局部更
- 加载网络数据,下载图片,使用异步加载,并缓存
- 少使用addView 给cell动态添加view
- 按需加载cell,cell滚动很快时,只加载范围内的cell
- 不要实现无用的代理方法,tableView只遵守两个协议
- 缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
- 不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
- 预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
- 使用正确的数据结构来存储数据。
如何提升 tableview 的流畅度?
本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
GPU:纹理的渲染
- 卡顿优化在 CPU 层面
- 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
- 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
- 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
- Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
- 图片的 size 最好刚好跟 UIImageView 的 size 保持一致
- 控制一下线程的最大并发数量
- 尽量把耗时的操作放到子线程
- 文本处理(尺寸计算、绘制)
- 图片处理(解码、绘制)
- 卡顿优化在 GPU层面
- 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
- GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
- 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
- 尽量避免出现离屏渲染
iOS 保持界面流畅的技巧?
- 预排版,提前计算
- 在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。
- 尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方式
- 预渲染,提前绘制
- 例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,回到主线程后直接调用就可以了
- 避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。
- 异步绘制
- 全局并发线程
- 高效的图片异步加载
APP启动时间应从哪些方面优化?
App启动时间可以通过xcode提供的工具来度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,将环境变量DYLD_PRINT_STATISTICS设为YES,优化需以下方面入手
- dylib loading time
- 核心思想是减少dylibs的引用
- 合并现有的dylibs(最好是6个以内)
- 使用静态库
- rebase/binding time
- 核心思想是减少DATA块内的指针
- 减少Object C元数据量,减少Objc类数量,减少实例变量和函数(与面向对象设计思想冲突)
- 减少c++虚函数
- 多使用Swift结构体(推荐使用swift)
- ObjC setup time
- 核心思想同上,这部分内容基本上在上一阶段优化过后就不会太过耗时
- initializer time
- 使用initialize替代load方法
- 减少使用c/c++的attribute((constructor));推荐使用dispatch_once() pthread_once() std:once()等方法
- 推荐使用swift
- 不要在初始化中调用dlopen()方法,因为加载过程是单线程,无锁,如果调用dlopen则会变成多线程,会开启锁的消耗,同时有可能死锁
- 不要在初始化中创建线程
Runtime方法调用流程?
1、当调用对象方法的时候,会通过obj_object的isa指针找对对应的归属类。
2、从归属类(obj_class)类中的obj_cache中寻找对应的相等的sel方法编号。
3、如果没有找到,继续obj_class中的obj_method_list中查找,如果找到写入obj_cache中。
4、如果没有到找到,会一直找到它的元类上。
5、如果元类也没有的话,会调用消息动态解析方法+resovleInstanceMethod:
和+resloveClassMethod:
的方法,查看是否存在绑定的方法。
6、如果没有绑定方法,会调用消息转发方法-forwardingTargetForSelector:
的方法。查看是否存在转发对象。
7、如果没有存在消息转发对象,会调用-methodSignatureForSelector:
的方法,查看是否有方法签名返回类型和参数类型。
8、不存在签名方法和类型,就会来到-doseNotRecognizeSelector:
方法内部程序crash提示无法识别选择器unrecognized selector sent to instance。
9、存在签名的方法,就是继续执行-forwardInvocation:
寻找IMP,没有找到IMP,就会来到-doseNotRecognizeSelector:
方法内部程序crash提示无法识别选择器unrecognized selector sent to instance。
@implementation Person
- (BOOL)respondsToSelector:(SEL)aSelector {
bool a= [super respondsToSelector:aSelector];
return a;
}
//如果方法没有实现,默认返回false
//如果返回false,就会走消息转发
+ (BOOL)resolveInstanceMethod:(SEL)sel {
bool a = [super resolveInstanceMethod:sel];
return a;
}
//默认返回空
//又被称为快速消息转发。
// 如果为空,走慢速消息转发,继续转发消息
- (id)forwardingTargetForSelector:(SEL)aSelector {
id a = [super forwardingTargetForSelector:aSelector];
return a;
}
// 默认一般普通方法是返回空的。
// 如果是协议方法,没有实现,不会反回空。
//反回空,到这里就会崩溃了
//如果这里返回了签名,会再次调用resolveInstanceMethod:(SEL)sel判断是否实现
//如果仍然没有实现,就会走到fowardInvocation:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *a = [super methodSignatureForSelector:aSelector];
return a;
}
//默认实现是崩溃
//并且不能用try-catch捕获
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[super forwardInvocation:anInvocation];
NSLog(@"");
}
@end