APP基本知识

Aug 25, 2014


编译顺序

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延迟执行(网络请求)MBProgressHUDhidden动画,可能会导致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

核心动画

资料文章

Demo

埋点

代码埋点

在每个需要统计的事件出,增加日志上报代码。可以使用面向切面编程Aspects去hook所要监控的方法,减小埋点代码量。

优点:精准,可自定义。

缺点:人力成本高,版本更新修改大,易出错,不方便。

可视化埋点

对APP需要埋点的页面进行截图,SDK在截图的同时,会分析视图的层级结构,然后将所有数据上传值服务器,服务器根据截图和视图层级结构、每个子试图的代码标识,从而进行埋点配置。SDK 在启动或者例行轮询时拿到这些配置信息,则会通过addTarget:action:forControlEvents:接口,为每个关联的控件添加的点击或者编辑行为的监听,并且在回掉函数里面调用 Sensors Analytics SDK 的接口发送相应事件的 track 信息。 第三方框架有:Heap AnalyticsTalkingData的灵动分析

优点:不需要手写代码,后台运营人员可配置埋点。

缺点:灵活度不够,覆盖的业务范围有限,不能区分具体的用户行

沙盒

Documents 存储永久性文件,不可再生的数据,会被iTunes同步。

Documents/Inbox APP可以向系统注册可以打开的文件格式,其他APP通过UIActivityViewController共享文件,文件就会copy到该目录下。

Library 可再生文件,除了Caches子目录外会被同步。

Library/Caches 缓存

Library/Preferences NSUserDefaults文件

tmp 临时文件

包大小

  1. 图片资源:重复的图片、图片进行压缩
  2. 无用的SDK
  3. 代码的精简

启动时间

1. 动态库的加载和链接

app启动过程:

加载app可执行文件 -> 加载动态库(UIKit,runtime,私有的Framework) -> didFinish

在scheme->arguments增加配置,从而在控制台查看库的加载时间

动态库加载过程:

  1. load dylibs image 读取库镜像文件(分析、打开、验证库)。优化方法: 减少动态库,合并动态库,使用静态库

  2. Rebase/Bind image,修复对象、方法地址。优化方法: 较少类数量,减少selector数量,使用swift结构体

  3. image

  4. Objc setup 优化方法: 将不必须在+load方法中做的事情延迟到+initialize中

  5. initializers

  6. 使用 Enviroment Variables 获取premain之前的数据
    DYLD_PRINT_STATISTICS
    DYLD_PRINT_LIBRARIES_POST_LAUNCH
    
  7. 获取进程创建时间
    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调试

  1. 全局异常断点
  2. 条件断点
  3. 符号断点
  4. watch观察某个对象的变化
  5. print object po

内存优化

  1. 代码规范,避免内存泄漏,如block,循环引用等。
  2. 循环有大量临时对象,使用@autoreleasepool
  3. 避免单例模式
  4. 大量图片慎重使用
  5. 控件的重复使用

Hybrid

1. Web调原生

重定向 + 拦截URL: 判断调用哪个方法

webViewDidFinishLoad方法中获取web的JSContext,通过JSContext的setObject(forKeyedSubscript:)根据约定好的方法名去注入原生方法

2. 原生调Web

WebView: stringByEvaluatingJavaScript

JavaScriptCore: evaluateScript

获取JSContext,并根据约定的方法名获取到JSValue,调用JSValue的call(withArguments:),从而调用web的方法