- 编译顺序
- App启动过程
- 视图控制生命周期
- viewWillAppear
- Preshent
- 切换rootViewController的一个bug
- 缓存池满的问题
- 计算文字高度
- 通过UIApplication修改状态栏
- http传输
- 导出ipa文件
- 证书位置
- 核心动画
- 埋点
- 沙盒
- 包大小
- 启动时间
- Xcode调试
- 内存优化
- Hybrid
编译顺序
PCH预编译导入的文件 -> 桥接文件 -> swift文件
App启动过程
main函数->UIApplicationMain(创建UIApplication和UIApplication的delegate对象)->调用didFinishLaunchingWithOption方法->创建windows->设置windows的rootViewController->显示windows
视图控制生命周期
init -view为懒加载-> loadView -加载完成之后-> viewDidLoad -将要显示,此时view还没有superview-> viewWillAppear -在这个过程中,view添加的superview上-> viewDidAppear(view已经添加到superview上)
注意: 一个 viewController(A) 有个子视图控制器 childController(B) : 如果 A 还没加载完,就又消失了,那么很可能 B 的viewDidLoad、viewWillAppear还没来得及调用,就直接调用了viewWillDisappear 因为 A 消失的时候,会调用viewWillDisappear,从而调用他的子视图的 viewWillDisappear。
viewWillAppear
应尽量不要在viewWillAppear
方法中写代码,特别是包含动画的代码。
在viewWillAppear
延迟执行(网络请求)MBProgressHUD
hidden动画,可能会导致hud不消失。
let afterTime = 0.2
// 模拟网络延迟
DispatchQueue.main.asyncAfter(deadline: .now() + afterTime) {
UIView.animate(withDuration: 1, animations: {
self.view.alpha = 0.5
}, completion: { (finish) in
// 侧滑返回的时候
// 当afterTime大于0.3秒,这个block会执行。
// 当小于0.3秒,且越接近0s,但是不等于0秒的时候,该block不会执行。
// MBProgressHUD 内部有这样的动画,从而导致hud不消失
print("animate is finish = ", finish)
})
}
Preshent
viewController A preshent B 后,b的presentingViewController
是: 如果A没有父Controller,那么就是A,否则就是A的父Controller
切换rootViewController的一个bug
A界面 present B界面, 在B界面切换rootViewController有如下问题:
1. 在B界面直接切换rootViewController
A controller 和 B controller 都不会释放,A B都被名为
_UIFullscreenPresentationController
的对象引用。
2. B dismiss后再切换rootViewController
A controller 和 B controller 都释放,但A的view依然在Window上,不会移除,即使手动将A的view从Windows移除,该view依然不会释放(被其他系统对象引用)。
缓存池满的问题
如果使用了reuseIdentifier
,那么系统会将创建过的cell加入到缓存中待用;所以,如果后续的cell没有复用已经创建过的cell,那么如果tableView的行数很多,缓存中将会存在大量的cell,导致内存过高甚至溢出。解决方法就是,循环使用or不适用缓存机制。
计算文字高度
// 注意1: iOS 9 和 iOS 10 系统字体不一样,所以,NSAttributedString 如果使用了系统字体,在iOS 10 和 iOS 9情况下,计算的rect 不一样,高度不一样。
//注意2: UITextView 有上下左右边距,所以使用下面函数计算的高度不能直接使用。
func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect
通过UIApplication修改状态栏
UIViewControllerBasedStatusBarAppearance 设置为NO
http传输
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
导出ipa文件
xcodebuild -exportArchive -archivePath /Users/userName/Library/Developer/Xcode/Archives/2016-12-31/Trailer\ 2016-12-31\ 下午2.52.xcarchive \
-exportPath ~/Desktop/MyApp2.ipa \
-exportFormat ipa \
-exportProvisioningProfile provisioningProfileName
证书位置
~/Library/MobileDevice/Provisioning Profiles
核心动画
埋点
代码埋点
在每个需要统计的事件出,增加日志上报代码。可以使用面向切面编程
Aspects
去hook所要监控的方法,减小埋点代码量。优点:精准,可自定义。
缺点:人力成本高,版本更新修改大,易出错,不方便。
可视化埋点
对APP需要埋点的页面进行截图,SDK在截图的同时,会分析视图的层级结构,然后将所有数据上传值服务器,服务器根据截图和视图层级结构、每个子试图的代码标识,从而进行埋点配置。SDK 在启动或者例行轮询时拿到这些配置信息,则会通过
addTarget:action:forControlEvents:
接口,为每个关联的控件添加的点击或者编辑行为的监听,并且在回掉函数里面调用 Sensors Analytics SDK 的接口发送相应事件的 track 信息。 第三方框架有:Heap Analytics
、TalkingData的灵动分析
。优点:不需要手写代码,后台运营人员可配置埋点。
缺点:灵活度不够,覆盖的业务范围有限,不能区分具体的用户行
沙盒
Documents 存储永久性文件,不可再生的数据,会被iTunes同步。
Documents/Inbox APP可以向系统注册可以打开的文件格式,其他APP通过
UIActivityViewController
共享文件,文件就会copy到该目录下。Library 可再生文件,除了Caches子目录外会被同步。
Library/Caches 缓存
Library/Preferences
NSUserDefaults
文件tmp 临时文件
包大小
- 图片资源:重复的图片、图片进行压缩
- 无用的SDK
- 代码的精简
启动时间
1. 动态库的加载和链接
app启动过程:
加载app可执行文件 -> 加载动态库(UIKit,runtime,私有的Framework) -> didFinish
在scheme->arguments增加配置,从而在控制台查看库的加载时间
动态库加载过程:
load dylibs image 读取库镜像文件(分析、打开、验证库)。优化方法: 减少动态库,合并动态库,使用静态库
Rebase/Bind image,修复对象、方法地址。优化方法: 较少类数量,减少selector数量,使用swift结构体
image
Objc setup 优化方法: 将不必须在+load方法中做的事情延迟到+initialize中
initializers
- 使用 Enviroment Variables 获取premain之前的数据
DYLD_PRINT_STATISTICS DYLD_PRINT_LIBRARIES_POST_LAUNCH
- 获取进程创建时间
let startTime: TimeInterval var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, ProcessInfo.processInfo.processIdentifier] var size = MemoryLayout<kinfo_proc>.size let ptr = UnsafeMutablePointer<kinfo_proc>.allocate(capacity: 1) let result = sysctl(&mib, u_int(mib.count), ptr, &size, nil, 0) if result == 0 { let info = ptr.pointee startTime = TimeInterval(info.kp_proc.p_un.__p_starttime.tv_sec) + TimeInterval(info.kp_proc.p_un.__p_starttime.tv_usec) / (1000.0 * 1000.0) ptr.deallocate() } else { ptr.deallocate() print("❌ 获取APP进程创建时间失败") return; }
2. didFinishLaunchingWithOptions
方法执行时间
不使用xib
NSUserDefaults打开数据的影响
在子线程中执行第三方SDK的初始化操作
sdk初始化推迟
参考:今日头条iOS客户端启动速度优化 , 美团外卖iOS App冷启动治理, 深入理解iOS App的启动过程, Any way to capture DYLD_PRINT_STATISTICS inside the application?, iOS App Launch time analysis and optimizations
Xcode调试
- 全局异常断点
- 条件断点
- 符号断点
- watch观察某个对象的变化
- print object po
内存优化
- 代码规范,避免内存泄漏,如block,循环引用等。
- 循环有大量临时对象,使用@autoreleasepool
- 避免单例模式
- 大量图片慎重使用
- 控件的重复使用
Hybrid
1. Web调原生
重定向 + 拦截URL: 判断调用哪个方法
在
webViewDidFinishLoad
方法中获取web的JSContext,通过JSContext的setObject(forKeyedSubscript:)
根据约定好的方法名去注入原生方法
2. 原生调Web
WebView:
stringByEvaluatingJavaScript
JavaScriptCore:
evaluateScript
获取JSContext,并根据约定的方法名获取到JSValue,调用JSValue的call(withArguments:),从而调用web的方法