app ios 直接启动 魔窗
2019-02-22 11:33:34 huanglinxiao 阅读数 67

一. iOS程序的启动执行顺序

程序启动顺序图

具体执行流程

  1. 程序入口
    进入main函数,设置AppDelegate称为函数的代理

  2. main函数中执行了一个UIApplicationMain这个函数.

    1

    2

    3

    4

    5

    6

    7

    8

    int main(int argc, char * argv[])

    {

        @autoreleasepool {

            return UIApplicationMain(argc, argv, nil, NSStringFromClass([JNAppDelegate class]));

        }

    }

     

    int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName)

        delegateClassName:指定应用程序的代理类,该类必须遵守UIApplicationDelegate协议.

        UIApplicationMain函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性.

        接着会建立应用程序的Main Runloop(事件循环),进行事件的处理(首先会在程序启动完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法)

        程序正常退出时UIApplicationMain函数才返回.

     

  3. 程序完成加载
    [AppDelegate application:didFinishLaunchingWithOptions:]

  4. 创建window窗口

  5. 程序被激活
    [AppDelegate applicationDidBecomeActive:]

  6. 当点击command+H时(针对模拟器,手机是当点击home键)
    程序取消激活状态
    [AppDelegate applicationWillResignActive:];
    程序进入后台
    [AppDelegate applicationDidEnterBackground:];

  7. 点击进入工程
    程序进入前台
    [AppDelegate applicationWillEnterForeground:]
    程序被激活
    [AppDelegate applicationDidBecomeActive:];

分析

1. 对于applicationWillResignActive(非活动)applicationDidEnterBackground(后台)这两个的区别

  • applicationWillResignActive(非活动):
    比如当有电话进来或短信进来或锁屏等情况下,这时应用程序挂起进入非活动状态,也就是手机界面还是显示着你当前的应用程序的窗口,只不过被别的任务强制占用了,也可能是即将进入后台状态(因为要先进入非活动状态然后进入后台状态)

  • applicationDidEnterBackground(后台):
    指当前窗口不是你的App,大多数程序进入这个后台会在这个状态上停留一会,时间到之后会进入挂起状态(Suspended)。如果你程序特殊处理后可以长期处于后台状态也可以运行。
    Suspended (挂起): 程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

如下图所示:

2.UIApplicationMain函数解释:

int main(int argc, char * argv[]) {  
      @autoreleasepool {  
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    
    } 
}

 UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
  • argcargv参数是为了与C语言保持一致。

  • principalClassName (主要类名)和 ** delegateClassName (委托类名)**。
    (1) 如果principalClassName是nil,那么它的值将从Info.plist去获取,如果Info.plist没有,则默认为UIApplicationprincipalClass这个类除了管理整个程序的生命周期之外什么都不做,它只负责监听事件然后交给delegateClass去做。
    (2) delegateClass将在工程新建时实例化一个对象。NSStringFromClass([AppDelegate class])

  • AppDelegate类实现文件

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"--- %s ---",__func__);//__func__打印方法名
    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
     NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
   NSLog(@"--- %s ---",__func__);
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
   NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
  NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
     NSLog(@"--- %s ---",__func__);
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"--- %s ---",__func__);
}

打印调用顺序
启动程序

 --- -[AppDelegate application:didFinishLaunchingWithOptions:] ---
 --- -[AppDelegate applicationDidBecomeActive:] ---

按下 Command + H + SHIFT

--- -[AppDelegate applicationWillResignActive:] ---
--- -[AppDelegate applicationDidEnterBackground:] ---

重新点击 进入程序

--- -[AppDelegate applicationWillEnterForeground:] ---
--- -[AppDelegate applicationDidBecomeActive:] ---

选择 模拟器的Simulate Memory Warning

--- -[AppDelegate applicationDidReceiveMemoryWarning:] ---

分析:

1.application:didFinishLaunchingWithOptions:
程序首次已经完成启动时执行,一般在这个函数里创建window对象,将程序内容通过window呈现给用户。

  1. applicationWillResignActive(非活动)
    程序将要失去Active状态时调用,比如有电话进来或者按下Home键,之后程序进入后台状态,对应的applicationWillEnterForeground(即将进入前台)方法。

该函数里面主要执行操作:
a . 暂停正在执行的任务
b. 禁止计时器
c. 减少OpenGL ES帧率
d. 若为游戏应暂停游戏

3.applicationDidEnterBackground(已经进入后台)
对应applicationDidBecomeActive(已经变成前台)

该方法用来:
a. 释放共享资源
b. 保存用户数据(写到硬盘)
c. 作废计时器
d. 保存足够的程序状态以便下次修复;

  1. applicationWillEnterForeground(即将进入前台)
    程序即将进入前台时调用,对应applicationWillResignActive(即将进入后台)
    这个方法用来: 撤销applicationWillResignActive中做的改变。

  2. applicationDidBecomeActive(已经进入前台)
    程序已经变为Active(前台)时调用。对应applicationDidEnterBackground(已经进入后台)
    注意: 若程序之前在后台,在此方法内刷新用户界面

  3. applicationWillTerminate
    程序即将退出时调用。记得保存数据,如applicationDidEnterBackground方法一样。

2016-08-09 18:49:07 gx_wqm 阅读数 237

在一个app中启动另外的app,这个应用很常见,实现也是很简单的,只需要注册一个URL,然后通过UIApplication的 openURL就可以实现了:

比如 A 启动 B

一、在 B(被启动的app) 中注册URL:

1、找到info.plist并打开,点击Information property list 右边的加号新建一个项,新项的名字改选择URL types,它是一个数组类型,点击其下面的item 0,将下面的URL identifer的值命名为com.company.appName 也就是反域名。

2、点击上面URL identifier右边的加号,新建一个项,名字选择URL Schedules,里面的item 0, 命名一般为appName

至此,在B中的设置完成了, 付个简图:



二、在A(启动别的app),通过openURL启动其他的app

这个就比较简单了,只需要:

    NSURL *appUrl = [NSURLURLWithString:@"FRC://"]; //这里的参数(FRC则这里是FRC://)对应URL Schemes item0的值

    if ([[UIApplicationsharedApplication] canOpenURL:appUrl]) {

        [[UIApplicationsharedApplication] openURL:appUrl];

    }

这样就可以了。

另外如果你需要向B,也就是被启动的app专递参数的话也是可以的,只需要在URL的后面添加所需的参数即可(eg:FRC://123

然后在A的APPDelegate添加的:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL*)url {

    returnYES;

}

就可以获得参数了。
2017-03-09 14:09:00 weixin_34228662 阅读数 3

iOS编译

当一个xcode工程build之后一般会执行如下几个步骤:

  • 预处理
  • 语法和语义分析
  • 生成代码和优化
  • 汇编
  • 链接

iOS编译采用Clang作为编译器前端,LLVM作为编译器后端。流程如下

793918-f581e0c54e46fd92.png
编译过程.png

Clang的任务:预处理、词法分析、语法分析、语义分析、静态分析、生成中间代码。
预处理:以#开头的代码预处理。包括引入的头文件和自定义宏。
词法分析:每一个.m源文件的声明和定义从string转化为特殊的标记流。
语法分析:将标记流解析成一颗抽象语法树( abstract syntax tree-AST)。
静态分析:包含类型检查和其他检查。
中间代码生成:生成LLVM代码。

793918-f6f8713f27c1e351.png
Clang过程.png

LLVM的任务:将代码进行优化并产生汇编代码。
汇编器:将可读的汇编代码转换为机器代码,最终创建一个目标对象.o文件。
链接器的任务:把目标文件和库相连,最终输出可运行文件:a.out。

Mach-O

Mach-O是针对不同运行时可执行文件的类型。
文件类型:

  • Exectuable: 应用的主要二进制
  • Dylib: 动态链接库
  • Bundle: 不能被链接的Dylib,只能在运行时使用dlopen()加载

Mach-O镜像文件
所有Mach-O(可使用MachOView工具查看)都包含:__TEXT, __DATA, __LINKEDIT:

  • __TEXT 包含Mach header, 被执行的代码和只读常量。
  • __DATA 包含全局变量,静态变量。可读写。
  • __LINKEDIT 包含加载程序的元数据,比如函数的名称和地址。

app启动

当用户点击一个app,app从启动到打开第一个页面的时间 t = t1 + t2,其中t1 = 系统dylib和自身app可执行文件(app中所有.o文件的集合)的加载,t2为main函数到appdelegate中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法执行结束这段时间。

app启动后,先加载可执行文件,再使用dyld(动态链接器)动态递归加载dylib(系统的framework,oc runtime的libobjc,系统级别的libSystem)。由于dylib是共享的,所以可以减少app包的体积。

dylib加载过程

  1. load dylibs image
  2. Rebase image
  3. Bind image
  4. Objc setup
  5. initializers

Rebase/Bind是用来修复image中的资源指针,使其指向正确的地址(因为iamge是加载在虚拟内存中(ASLR))

Objc setup工作

  • 注册Objc类
  • 把category定义插入方法列表
  • 保证每一个selector唯一

initializers
前面三步是静态调整,修改__DATA segment内容。这里开始动态调整,开始在堆栈中写入内容。

main()之前的加载时间

它可以通过以下方式来显示。

793918-a3499b494fd6c598.png
launch time.png

结果如下:


793918-1883d0d4d1ab9565.png
pre-main time.png

main()调用之后的加载时间

  1. 准备阶段,主要是图片的解码
  2. 布局阶段,-(void)layoutSubViews()
  3. 绘制阶段,-(void)drawRect:(CGRect)rect
  4. 启动阶段必要服务的启动、必要数据的创建和读取。

优化启动时间

  • 内嵌的dylib尽可能少,或者合并起来。
  • Rebase/Binding减少__DATA中需要修正的指针。 对于oc来说减少 class, selector, category 这些元数据的数量,对与c++来说,减少虚函数数量。swift结构体需要修正的比较少。
  • 将不必须在+load中做的事延迟到+ initialize中。
  • 不使用xib,直接用代码加载首页视图。
  • release版不要用NSLog输出。
  • 启动时的网络请求尽可能异步。

参考链接

趣探 Mach-O:文件格式分析
优化 App 的启动时间
Mach-O 可执行文件
编译器

转载于:https://www.jianshu.com/p/65901441903e

2017-03-09 14:09:00 weixin_34133829 阅读数 19

iOS编译

当一个xcode工程build之后一般会执行如下几个步骤:

  • 预处理
  • 语法和语义分析
  • 生成代码和优化
  • 汇编
  • 链接

iOS编译采用Clang作为编译器前端,LLVM作为编译器后端。流程如下

793918-f581e0c54e46fd92.png
编译过程.png

Clang的任务:预处理、词法分析、语法分析、语义分析、静态分析、生成中间代码。
预处理:以#开头的代码预处理。包括引入的头文件和自定义宏。
词法分析:每一个.m源文件的声明和定义从string转化为特殊的标记流。
语法分析:将标记流解析成一颗抽象语法树( abstract syntax tree-AST)。
静态分析:包含类型检查和其他检查。
中间代码生成:生成LLVM代码。

793918-f6f8713f27c1e351.png
Clang过程.png

LLVM的任务:将代码进行优化并产生汇编代码。
汇编器:将可读的汇编代码转换为机器代码,最终创建一个目标对象.o文件。
链接器的任务:把目标文件和库相连,最终输出可运行文件:a.out。

Mach-O

Mach-O是针对不同运行时可执行文件的类型。
文件类型:

  • Exectuable: 应用的主要二进制
  • Dylib: 动态链接库
  • Bundle: 不能被链接的Dylib,只能在运行时使用dlopen()加载

Mach-O镜像文件
所有Mach-O(可使用MachOView工具查看)都包含:__TEXT, __DATA, __LINKEDIT:

  • __TEXT 包含Mach header, 被执行的代码和只读常量。
  • __DATA 包含全局变量,静态变量。可读写。
  • __LINKEDIT 包含加载程序的元数据,比如函数的名称和地址。

app启动

当用户点击一个app,app从启动到打开第一个页面的时间 t = t1 + t2,其中t1 = 系统dylib和自身app可执行文件(app中所有.o文件的集合)的加载,t2为main函数到appdelegate中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法执行结束这段时间。

app启动后,先加载可执行文件,再使用dyld(动态链接器)动态递归加载dylib(系统的framework,oc runtime的libobjc,系统级别的libSystem)。由于dylib是共享的,所以可以减少app包的体积。

dylib加载过程

  1. load dylibs image
  2. Rebase image
  3. Bind image
  4. Objc setup
  5. initializers

Rebase/Bind是用来修复image中的资源指针,使其指向正确的地址(因为iamge是加载在虚拟内存中(ASLR))

Objc setup工作

  • 注册Objc类
  • 把category定义插入方法列表
  • 保证每一个selector唯一

initializers
前面三步是静态调整,修改__DATA segment内容。这里开始动态调整,开始在堆栈中写入内容。

main()之前的加载时间

它可以通过以下方式来显示。

793918-a3499b494fd6c598.png
launch time.png

结果如下:


793918-1883d0d4d1ab9565.png
pre-main time.png

main()调用之后的加载时间

  1. 准备阶段,主要是图片的解码
  2. 布局阶段,-(void)layoutSubViews()
  3. 绘制阶段,-(void)drawRect:(CGRect)rect
  4. 启动阶段必要服务的启动、必要数据的创建和读取。

优化启动时间

  • 内嵌的dylib尽可能少,或者合并起来。
  • Rebase/Binding减少__DATA中需要修正的指针。 对于oc来说减少 class, selector, category 这些元数据的数量,对与c++来说,减少虚函数数量。swift结构体需要修正的比较少。
  • 将不必须在+load中做的事延迟到+ initialize中。
  • 不使用xib,直接用代码加载首页视图。
  • release版不要用NSLog输出。
  • 启动时的网络请求尽可能异步。

参考链接

趣探 Mach-O:文件格式分析
优化 App 的启动时间
Mach-O 可执行文件
编译器

2017-10-19 11:52:00 weixin_34095889 阅读数 18

本文首发个人博客:iOS App 启动性能优化

应用启动时间,直接影响用户对一款应用的判断和使用体验。ZAKER新闻本身就包含非常多并且复杂度高的业务模块(如新闻、视频等),也接入了很多第三方的插件,这势必会拖慢应用的启动时间,本着精益求精的态度和对用户体验的追求,我们希望在业务扩张的同时最大程度的优化启动时间。

启动时间

总时间 = T1 + T2

T1

加载系统dylib可执行文件的时间。

T2

mainapplicationWillFinishLaunching结束的时间。

App启动过程

1492739-1c395aef0e19208d.png
App启动过程

1)解析Info.plist

  • 加载相关信息,例如如闪屏
  • 沙箱建立、权限检查

2)Mach-O加载

  • 如果是胖二进制文件,寻找合适当前CPU类别的部分
  • 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
  • 定位内部、外部指针引用,例如字符串、函数等
  • 执行声明为__attribute__((constructor))的C函数
  • 加载类扩展(Category)中的方法
  • C++静态对象加载、调用ObjC的+load函数

3)程序执行

  • 调用main()
  • 调用UIApplicationMain()
  • 调用applicationWillFinishLaunching

Mach-O

Mach-O 是针对不同运行时可执行文件的文件类型。

1492739-ab3beb338ec35f7b.png
Mach-O

文件类型:

Executable: 应用的主要二进制

Dylib: 动态链接库(又称 DSO 或 DLL)

Bundle: 不能被链接的 Dylib,只能在运行时使用 dlopen() 加载,可当做 macOS 的插件。

Image: executable,dylib 或 bundle

Framework: 包含 Dylib 以及资源文件和头文件的文件夹

Mach-O 镜像文件

Mach-O 被划分成一些 segement,每个 segement 又被划分成一些 section。

segment 的名字都是大写的,且空间大小为页的整数。页的大小跟硬件有关,在 arm64 架构一页是 16KB,其余为 4KB。

section 虽然没有整数倍页大小的限制,但是 section 之间不会有重叠。

几乎所有 Mach-O 都包含这三个段(segment): __TEXT,__DATA__LINKEDIT

  • __TEXT 包含 Mach header,被执行的代码和只读常量(如C 字符串)。只读可执行(r-x)。

  • __DATA 包含全局变量,静态变量等。可读写(rw-)。

  • __LINKEDIT 包含了加载程序的『元数据』,比如函数的名称和地址。只读(r–)。

Mach-O Universal 文件

FAT 二进制文件,将多种架构的 Mach-O 文件合并而成。它通过 Fat Header 来记录不同架构在文件中的偏移量,Fat Header 占一页的空间。

按分页来存储这些 segement 和 header 会浪费空间,但这有利于虚拟内存的实现。

什么是image

1.executable可执行文件 比如.o文件。

2.dylib 动态链接库 framework就是动态链接库和相应资源包含在一起的一个文件夹结构。

3.bundle 资源文件 只能用dlopen加载,不推荐使用这种方式加载。

除了我们App本身的可行性文件,系统中所有的framework比如UIKit、Foundation等都是以动态链接库的方式集成进App中的。

什么是ImageLoader

image 表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等,所以 ImageLoader 作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。

两步走:在程序运行时它先将动态链接的 image 递归加载 (也就是上面测试栈中一串的递归调用的时刻)。 再从可执行文件 image 递归加载所有符号。

冷启动和热启动

冷启动

应用首次启动。即后台线程中未有当前打开的应用,所有的资源都需要加载并初始化。

热启动

应用非首次启动。即后台线程中保留有当前应用,应用的资源在内存中有保存。

启动时间分析

1)开启时间分析功能

在Xcode的菜单中选择ProjectSchemeEdit Scheme...,然后找到RunEnvironment Variables+,添加nameDYLD_PRINT_STATISTICSvalue1的环境变量。

1492739-0fbe62df3450c8fb.png
开启时间分析功能
1492739-44e9e60c9745778c.png
启动时间

load dylibs image

在每个动态库的加载过程中, dyld需要:

1.分析所依赖的动态库

2.找到动态库的mach-o文件

3.打开文件

4.验证文件

5.在系统核心注册文件签名

6.对动态库的每一个segment调用mmap()

通常的,一个App需要加载100到400个dylibs, 但是其中的系统库被优化,可以很快的加载。 针对这一步骤的优化有:

1.减少非系统库的依赖

2.合并非系统库

3.使用静态资源,比如把代码加入主程序

rebase/bind

由于ASLR(address space layout randomization)的存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定,所以需要这2步来修复镜像中的资源指针,来指向正确的地址。 rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。

rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。

优化该阶段的关键在于减少__DATA segment中的指针数量。我们可以优化的点有:

1.减少Objc类数量, 减少selector数量

2.减少C++虚函数数量

3.转而使用swift stuct(其实本质上就是为了减少符号的数量)

解读

  • main()函数之前总共使用了506.48ms
  • 在506.48ms中,加载动态库用了46.35ms,指针重定位使用了137.72ms,ObjC类初始化使用了95.39ms,各种初始化使用了226.92ms。
  • 在初始化耗费的226.92ms中,用时最多的几个初始化是libSystem.B.dyliblibBacktraceRecording.dyliblibglInterpose.dylib以及libMTLInterpose.dylib

2)使用instruments工作分析具体时间消耗点

耗时的影响因素

1) main()函数之前耗时的影响因素

  • 动态库加载越多,启动越慢。
  • ObjC类越多,启动越慢
  • C的constructor函数越多,启动越慢
  • C++静态对象越多,启动越慢
  • ObjC的+load越多,启动越慢

实验证明,在ObjC类的数目一样多的情况下,需要加载的动态库越多,App启动就越慢。同样的,在动态库一样多的情况下,ObjC的类越多,App的启动也越慢。需要加载的动态库从1个上升到10个的时候,用户几乎感知不到任何分别,但从10个上升到100个的时候就会变得十分明显。同理,100个类和1000个类,可能也很难查察觉得出,但1000个类和10000个类的分别就开始明显起来。

同样的,尽量不要写__attribute__((constructor))的C函数,也尽量不要用到C++的静态对象;至于ObjC的+load方法,似乎大家已经习惯不用它了。任何情况下,能用dispatch_once()来完成的,就尽量不要用到以上的方法。

2) main()函数之后耗时的影响因素

main()函数开始至applicationWillFinishLaunching结束,我们统一称为main()函数之后的部分。

  • 执行main()函数的耗时
  • 执行applicationWillFinishLaunching的耗时
  • rootViewController及其childViewController的加载、view及其subviews的加载

实践

移除不需要用到的类

为了解决这个历史问题,我使用了一个叫做fui(Find Unused Imports)的开源项目,它能很好的分析出不再使用的类,准确率非常高,唯一的问题是它处理不了动态库和静态库里提供的类,也处理不了C++的类模板。

使用方法是在Terminalcd到项目所在的目录,然后执行fui find,然后等上那么几分钟(是的你没有看错,真的需要好几分钟甚至需要更长的时间),就可以得到一个列表了。由于这个工具还不是100%靠谱,可根据这个列表,在Xcode中手动检查并删除不再用到的类。

合并功能类似的类和扩展(Category)

优化application:didFinishLaunchingWithOptions:方法

优化rootViewController加载

问题

1)NSUserDefaults是否是瓶颈

2)还有其他哪些点可以做优化

参考文档:《优化 App 的启动时间》

IOS App 启动优化

阅读数 7

iOS App启动优化

阅读数 65

iOS app启动优化

阅读数 4

没有更多推荐了,返回首页