1.捏造内存 & ASLR
在早期盘算机中数据是直接通过物理地址访问的,这就造成了下面两个题目
1.内存不够用
2.数据安全题目
内存不够 ---> 捏造内存
捏造内存就是通过创建一张物理地址和捏造地址的映射表来管理内存,进步了CPU利用率,使多个历程可以同时/按需加载。
- 在iOS中,每个历程都有独立的捏造内存,存放物理内存中,其地址是从0开始的,大小固定4G,每个捏造内存又会按页划分,每页16K,以页为单元加载,每个历程是相互独立的,包管历程间的数据安全
- 当一个历程只有部门功能使用时,系统会主动将使用部门加载到物理内存中
- CPU在进行数据访问时,会先访问捏造内存,通过捏造内存和物理内存的映射关系,去对应的物理内存中查找,在CPU上有专门处理处罚映射的硬件
- 当CPU访问数据时,如果捏造内存中的数据没有物理内存绑定,会发生缺页非常(pagefault),会阻塞当进步程,直到数据加载到物理内存中,捏造内存和物理内存绑定
- 当内存条中的内存全部用完后,系统会将新的数据覆盖到很久没使用的内存上
数据安全 ---> ASLR
由于捏造内存的起始地址(0x000000)和大小(4G)是固定的,这就意味着我们的数据地址也是固定的,所以在iOS4.3引进了ASLR。
ASLR:英文全称Address Space Layout Randomization,也叫地址空间配置随机加载,是一种针对缓冲区溢出的安全掩护技能。通过对堆、栈、共享库映射等线性区布局的随机化,增强了攻击者找到目的物理内存的难度,防止攻击者直接定位攻击代码位置,到达克制溢出攻击的目的的一种技能。
由于ASLR的存在,导致可实验文件和动态链接库在捏造内存中的地址每次都是不固定的,所以需要在编译时来修复镜像中的资源指针,来指向精确的地址。即精确的内存地址 = ASLR地址 + 偏移值
2.iOS 系统架构
Mac系统是基于Unix内核的图形化操纵系统。Mac OS 和 iOS 系统架构的对比分析发现,Mac OS和iOS的系统架构层次只有最上面一层不同,Mac是Cocoa框架,而iOS是Cocoa Touch框架,其余的架构层次都是一样的。
Core OS是用FreeBSD和Mach所改写的一个名叫Darwin的开放原始码操纵系统, 是开源、符合POSIX尺度的一个Unix核心。这一层包含并提供了整个iPhone OS的一些底子功能,比如:硬件驱动, 内存管理,步伐管理,线程管理(POSIX),文件系统,网络(BSD Socket),以及尺度输入输出等,所有这些功能都会通过C语言的API来提供。
核心OS层的驱动提供了硬件和系统框架之间的接口。然而,由于安全的考虑,只有有限的系统框架类能访问内核和驱动。iPhone OS提供了许多访问操纵系统低层功能的接口集,iPhone 应用通过LibSystem库来访问这些功能,这些接口集有线程(POSIX线程)、网络(BSD sockets)、文件系统访问、尺度I/O、Bonjour和DNS服务、现场信息(Locale Information)、内存分配和数学盘算等。
Core Services在Core OS底子上提供了更为丰富的功能, 它包含了Foundation.Framework和Core Foundation.Framework。之所以叫Foundation,就是由于它提供了一系列处理处罚字符串,排列、组合、日历、时间等等的基本功能。
Foundation是属于Objective-C的API,Core Fundation是属于C的API。另外Core servieces还提供了如Security(用来处理处罚认证,暗码管理,安全性管理等), Core Location, SQLite和Address Book等功能。
核心底子框架(CoreFoundation.framework)是基于C语言的接口集,提供iPhone应用的基本数据管理和服务功能。该框架支持Collection数据类型(Arrays、 Sets等)、Bundles、字符串管理、日期和时间管理、原始数据块管理、首选项管理、URL和Stream操纵、线程和运行循环(Run Loops)、端口和Socket通信。
核心底子框架与底子框架是紧密相关的,它们为雷同的基本功能提供了Objective-C接口。如果开发者混合使用Foundation Objects 和Core Foundation类型,就能充分利用存在两个框架中的"toll-free bridging"技能(桥接)。toll-free bridging使开发者能使用这两个框架中的任何一个的核心底子和底子类型。
步伐启动之前
从应用图标被用户点击开始,直到应用可以开始响应发生了许多事情。
许多文章中大家都提到说dyld加载了主步伐和动态库,这个理解明显是错误的,在XNU加载Mach-O和dyld过程中,是内核加载了主步伐,dyld只会负责动态库的加载。固然主步伐也会作为镜像形式被dyld来管理起来。当一个App启动时,dyld把App需要的dylib加载进App的内存空间。App运行所需要的信息,一样平常都存放在其MachO头部44中,此中dylib的信息是由Load Commands指定的,App得到实验时,dyld会查看其MachO头部中的Load Commands,并把里面LC_LOAD_DYLIB相关的dylib给加载到历程的内存空间。
一样平常来说,逆向工程会在dyld阶段入手,之前版本的dyld中确实存在一些漏洞,使App可以或许绕过代码签名,例如dyld-353.2.1版本,漏洞编号CVE-2015-5876,漏洞存在于Mach-O头的处理处罚过程中,一个畸形的Mach-O文件可以导致内存段被替换,从而导致任意代码实验,当然现在dyld的已知漏洞都已修复。监控启动崩溃会在Objc阶段之后,详细方法详细分析。
总结来说,大体分为如下步调:
(1) 系统为步伐启动做好预备。
(2) 系统将控制权交给 Dyld,Dyld 会负责后续的工作。
(3) Dyld 加载步伐所需的动态库。
(3) Dyld 对步伐进行 rebase 以及 bind 操纵。
(4) Objc SetUp。
(5) 运行初始化函数。
(6) 实验步伐的 main 函数。
需要注意的是,dyld2和dyld3的加载方式略有不同。dyld2是纯粹的in-process,也就是在步伐历程内实验的,也就意味着只有当应用步伐被启动的时间,dyld2才气开始实验任务。dyld3则是部门out-of-process,部门in-process。
dyld2的过程是:加载dyld到App历程,加载动态库(包罗所依靠的所有动态库),Rebase,Bind,初始化Objective C Runtime和其它的初始化代码。
dyld3的out-of-process会做如下事情:分析Mach-O Headers,分析依靠的动态库,查找需要Rebase & Bind之类的符号,把上述结果写入缓存。如许,在应用启动的时间,就可以直接从缓存中读取数据,加快加载速度。
从exec()开始
Mach-O是 OS X 系统的可实验文件,Mach-O有多种文件类型,比如MH_DYLIB文件、MH_BUNDLE文件、MH_EXECUTE文件,MH_OBJECT(内核加载)等。可实验文件离不开历程,在 Linux 中,我们会通过 Fork()来新创建子历程,然后实验镜像通过exec()来替换为另一个可实验步伐。在用户态会通过exec*系列函数来加载一个可实验文件。
main()函数是整个步伐的入口,在步伐启动之前,系统会调用exec()函数。在Unix中exec和system的不同在于,system是用shell来调用步伐,相当于fork+exec+waitpid,fork 函数创建子历程后通常都会调用 exec 函数来实验一个新步伐;而exec是直接让你的步伐代替原来的步伐运行。
system 是在单独的历程中实验命令,完了还会回到你的步伐中。而exec函数是直接在你的历程中实验新的步伐,新的步伐会把你的步伐覆盖,除非调用堕落,否则你再也回不到exec后面的代码,也就是当前的步伐变成了exec调用的那个步伐了。
UNIX 提供了 6 种不同的 exec 函数供我们使用。
- #include <unistd.h>
- int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
- int execv(const char *pathname, char *const argv[]);
- int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
- int execve(const char *pathname, char *const argv[], char *const envp[]);
- int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
- int execvp(cosnt char *filename, char *const argv[]);
复制代码 通过分析我们发现,含有 l 和 v 的 exec 函数的参数表通报方式是不同的。含有 e 结尾的 exec 函数会通报一个情况变量列表。含有 p 结尾的 exec 函数取的是新步伐的文件名作为参数,而其他exec 函数取的是新步伐的路径。
如果函数堕落则返回-1,若成功则没有返回值。此中只有execve是真正意义上的系统调用,其它都是在此底子上经过包装的库函数。
exec函数族的作用是根据指定的文件名找到可实验文件,并用它来代替调用历程的内容,换句话说,就是在调用历程内部实验一个可实验文件。这里的可实验文件既可以是二进制文件,也可以是任何Unix下可实验的脚本文件。
Dyld
Dyld 是 iOS 系统的动态链接器, 在dyldStartup.s 文件中有个名为 __dyld_start 的方法,它会去调用 dyldbootstrap::start() 方法,然后进一步调用 dyld::_main() 方法,里面包含 App 的整个启动流程,该函数最终返回应用步伐 main 函数的地址,最后 Dyld 会去调用它。
之后会去加载可实验文件,二进制文件常被称为 image,包罗可实验文件、动态库等,ImageLoader 的作用就是将二进制文件加载进内存。dyld::_main() 方法在设置好运行情况后,会调用instantiateFromLoadedImage 函数将可实验文件加载进内存中,加载过程分为三步:
1.合法性检查。重要是检查可实验文件是否合法,是否能在当前的 CPU 架构下运行。
2.选择 ImageLoader 加载可实验文件。系统会去判断可实验文件的类型,选择相应的 ImageLoader 将其加载进内存空间中。
3.注册 image 信息。可实验文件加载完成后,系统会调用 addImage 函数将其管理起来,并更新内存分布信息。
以上三步完成后,Dyld 会调用 link 函数开始之后的处理处罚流程。
3.静态链接库与动态链接库
iOS中的相关文件有如下几种:Dylib,动态链接库(又称 DSO 或 DLL);Bundle,不能被链接的 Dylib,只能在运行时使用 dlopen() 加载,可当做 macOS 的插件。Framework,包含 Dylib 以及资源文件和头文件的文件夹。
动态链接库是一组源代码的模块,每个模块包含一些可供应用步伐或者其他动态链接库调用的函数,在应用步伐调用一个动态链接库里面的函数的时间,操纵系统会将动态链接库的文件映像映射到历程的地址空间中,如许历程中所有的线程就可以调用动态链接库中的函数了。动态链接库加载完成后,这个时间动态链接库对于历程中的线程来说只是一些被放在地址历程空间附加的代码和数据,操纵系统为了节流内存空间,同一个动态链接库在内存中只有一个,操纵系统也只会加载一次到内存中。
由于代码段在内存中的权限都是为只读的,所以当多个应用步伐加载同一个动态链接库的时间,不用担心应用步伐会修改动态链接库的代码段。当线程调用动态链接库的一个函数,函数会在线程栈中取得通报给他的参数,并使用线程栈来存放他需要的变量,动态链接库函数创建的任何对象都为调用线程或者调用历程拥有,动态链接库不会拥有任何对象。如果动态链接库中的一个函数调用了VirtualAlloc,系统会从调用历程的地址空间预定地址,纵然撤销了对动态链接库的映射,调用历程的预定地址依然会存在,直到用户取消预定或者历程结束。
静态链接库与动态链接库都是共享代码的方式,如果接纳静态链接库,则无论你愿不乐意,lib 中的指令都全部被直接包含在最终天生的包文件中了。但是若使用动态链接库,该动态链接库不必被包含在最终包里,包文件实验时可以“动态”地引用和卸载这个与安装包独立的动态链接库文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
Linux中静态函数库的名字一样平常是libxxx.a,利用静态函数库编译成的文件比力大,由于整个函数库的所有数据都会被整合进目的代码中。编译后的实验步伐不需要外部的函数库支持,由于所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,由于如果静态函数库改变了,那么你的步伐必须重新编译。
动态函数库的名字一样平常是libxxx.so,相对于静态函数库,动态函数库在编译的时间并没有被编译进目的代码中,你的步伐实验到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可实验文件比力小。由于函数库没有被整合进你的步伐,而是步伐运行时动态的申请并调用,所以步伐的运行情况中必须提供相应的库。动态函数库的改变并不影响你的步伐,所以动态函数库的升级比力方便。
iOS开发中静态库和动态库是相对编译期和运行期的。静态库在步伐编译时会被链接到目的代码中,步伐运行时将不再需要载入静态库。而动态库在步伐编译时并不会被链接到目的代码中,只是在步伐运行时才被载入,由于在步伐运行期间还需要动态库的存在。
iOS中静态库可以用.a或.Framework文件体现,动态库的形式有.dylib和.framework。系统的.framework是动态库,一样平常自己创建的.framework是静态库。
.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。.a文件不能直接使用,至少要有.h文件配合。.framework文件可以直接使用,.a + .h + sourceFile = .framework。
动态库的一个重要特性就是即插即用性,我们可以选择在需要的时间再加载动态库。如果不希望在软件一启动就加载动态库,需要将
- Targets-->Build Phases-->Link Binary With Libraries
复制代码 中 *.framework 对应的Status由默认的 Required 改成 Optional ;或者将 xx.framework 从 Link Binary With Libraries 列表中删除。
可以使用dlopen加载动态库,动态库中真正的可实验代码为 xx.framework/xx 文件。
- - (IBAction)useDlopenLoad:(id)sender
- {
- NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/xx.framework/xx",NSHomeDirectory()];
- [self dlopenLoadlib:documentsPath];
- }
- - (void)dlopenLoadlib:(NSString *)path
- {
- libHandle = NULL;
- libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
- if (libHandle == NULL) {
- char *error = dlerror();
- NSLog(@"dlopen error: %s", error);
- } else {
- NSLog(@"dlopen load framework success.");
- }
- }
复制代码 也可以使用NSBundle来加载动态库,实现代码如下:
- - (IBAction)useBundleLoad:(id)sender
- {
- NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/xx.framework",NSHomeDirectory()];
- [self bundleLoadlib:documentsPath];
- }
- - (void)bundleLoadlib:(NSString *)path
- {
- _libPath = path;
- NSError *err = nil;
- NSBundle *bundle = [NSBundle bundleWithPath:path];
- if ([bundle loadAndReturnError:&err]) {
- NSLog(@"bundle load framework success.");
- } else {
- NSLog(@"bundle load framework err:%@",err);
- }
- }
复制代码 可以为动态库的加载和移除添加监听回调,ImageLogger上有一个完整的示例代码,从中可以发现,一个工程软件启动的时间会加载多达一百二十多个动态库,纵然是一个空缺的项目。
但是,需要注意的一点是,不要在初始化方法中调用 dlopen(),对性能有影响。由于 dyld 在 App 开始前运行,由于此时是单线程运行所以系统会取消加锁,但 dlopen() 开启了多线程,系统不得不加锁,这就严重影响了性能,还可能会造成死锁以及产生未知的后果。所以也不要在初始化器中创建线程。
听说,iOS现在可以使用自定义的动态库,低版本的需要手动的使用dlopen()加载。动态库上架会有一些考核的规则,如不要把x86/i386的包和arm架构的包lipo在一起使用。如:
- lipo –create Release-iphoneos/libiphone.a Debig-iphonesimulator/libiphone.a –output libiphone.a
复制代码 如此便将模仿器和设备的静态库文件合并成一个文件输出了。
dylib加载调用
基于上面的分析,在exec()时,系统内核把应用映射到新的地址空间,每次起始位置都是随机的。然后使用dyld 加载 dylib 文件(动态链接库),dyld 在应用历程中运行的工作就是加载应用依靠的所有动态链接库,预备好运行所需的统统,它拥有和应用一样的权限。
加载 Dylib时,先从主实验文件的 header 中获取需要加载的所依靠动态库的列表,从中找到每个 dylib,然后打开文件读取文件起始位置,确保它是 Mach-O 文件(针对不同运行时可实验文件的文件类型)。然后找到代码签名并将其注册到内核。
应用所依靠的 dylib 文件可能会再依靠其他 dylib,因此动态库列表是一个递归依靠的集合。一样平常应用会加载 100 到 400 个 dylib 文件,但大部门都是系统 dylib,它们会被预先盘算和缓存起来,加载速度很快。但加载内嵌(embedded)的 dylib 文件很占时间,所以尽可能把多个内嵌 dylib 合并成一个来加载,或者使用 static archive。
在加载所有的动态链接库之后,它们只是处在相互独立的状态,代码签名使得我们不能修改指令,那样就不能让一个 dylib 调用另一个 dylib。通过fix-up可以将它们团结起来,dyld 所做的事情就是修正(fix-up)指针和数据。Fix-up 有两种类型,rebasing(在镜像内部调整指针的指向) 和 binding(将指针指向镜像外部的内容)。
由于地址空间加载随机化的缘故,二进制文件最终的加载地址与预期地址之间会存在偏移,所以需要进行 rebase 操纵,对那些指向文件内部符号的指针进行修正。rebase 完成之后,就会进行 bind 操纵,修正那些指向其他二进制文件所包含的符号的指针。由于 dylib 之间有依靠关系,所以 动态库中的许多多少操纵都是沿着依靠链递归操纵的,Rebasing 和 Binding 分别对应着 recursiveRebase() 和 recursiveBind() 这两个方法。由于是递归,所以会自底向上地分别调用 doRebase() 和 doBind() 方法,如许被依靠的 dylib 总是先于依靠它的 dylib 实验 Rebasing 和 Binding。
Rebaing 消耗了大量时间在 I/O 上,在 Rebasing 和 Binding 前会判断是否已经 预绑定。如果已经进行过预绑定(Prebinding),那就不需要 Rebasing 和 Binding 这些 Fix-up 流程了,由于已经在预先绑定的地址加载好了。
Binding 处理处罚那些指向 dylib 外部的指针,它们现实上被符号(symbol)名称绑定,是一个字符串。dyld 需要找到 symbol 对应的实现,在符号表里查找时需要许多盘算,找到后会将内容存储起来。Binding 看起来盘算量比 Rebasing 更大,但实在需要的 I/O 操纵很少,由于之前 Rebasing 已经替 Binding 做过了。Objective-C 中有许多数据结构都是靠 Rebasing 和 Binding 来修正(fix-up)的,比如 Class 中指向超类的指针和指向方法的指针。
Objc
C++ 会为静态创建的对象天生初始化器,与静态语言不同,OC基于Runtime机制可以用类的名字来实例化一个类的对象。Runtime 维护了一张映射类名与类的全局表,当加载一个 dylib 时,其定义的所有的类都需要被注册到这个全局表中。ObjC 在加载时可以通过 fix-up 在动态类中改变实例变量的偏移量,利用这个技能可以在不改变dylib的情况下添加另一个 dylib 中类的方法,而非常见的通过定义种别(Category)的方式改变一个类的方法。
Dyld 在 bind 操纵结束之后,会发出 dyld_image_state_bound 通知,然后与之绑定的回调函数 map_2_images 就会被调用,它重要做以下几件事来完成 Objc Setup:
1.读取二进制文件的 DATA 段内容,找到与 objc 相关的信息。
2.注册 Objc 类。
3.确保 selector 的唯一性。
4.读取 protocol 以及 category 的信息。
除了 map_2_images,我们注意到 _objc_init 还注册了 load_images 函数,它的作用就是调用 Objc 的 + load 方法,它监听 dyld_image_state_dependents_initialized 通知。
dyld 是运行在用户态的, 这里由内核态切到了用户态。每当有新的镜像加载之后,都会实验 load-images 方法进行回调,这里的回调是在整个Objc runtime 初始化时 -objc-init 注册的。有新的镜像被 map 到 runtime 时,调用 load-images 方法,并传入最新镜像的信息列表 infoList。调用 prepare-load-methods 对 load 方法的调用进行预备(将需要调用 load 方法的类添加到一个列表中),调用 -getObjc2NonlazyClassList 获取所有的类的列表之后,会通过 remapClass 获取类对应的指针,然后调用 schedule-class-load 递归地 将当前类和没有调用 + load 父类进入列表。在实验 add-class-to-loadable-list(cls) 将当前类加入加载列表之前,会先把父类加入待加载的列表,包管父类在子类前调用 load 方法。在实验 add-class-to-loadable-list(cls) 将当前类加入加载列表之前,会先把父类加入待加载的列表,包管父类在子类前调用 load 方法。在将镜像加载到运行时、对 load 方法的预备就绪,实验 call-load-methods,开始调用 load 方法。
由于iOS开发时基于Cocoa Touch的,所以绝大多数的类起始都是系统类,大多数的Runtime初始化起始在Rebase和Bind中已经完成。
Initializers
Objc SetUp 结束后,Dyld 便开始运行步伐的初始化函数,该任务由 initializeMainExecutable 函数实验。整个初始化过程是一个递归的过程,次序是先将依靠的动态库初始化,然后在对自己初始化。初始化需要做的事情包罗:
1.调用 Objc 类的 + load 函数。
2.调用 C++ 中带有 constructor 标记的函数。
3.非基本类型的 C++ 静态全局变量的创建。
所谓实验监控启动crash的思绪都是在这里构建的。下面是一些方法的实验次序,initialize的次序可能在更早,但总是会在load和launch之间。
4.步伐启动逻辑
主实验文件和相关的 dylib的依靠关系构成了一张巨大的有向图,实验初始化器先加载叶子节点,然后逐步向上加载中间节点,直至最后加载根节点。这种加载次序确保了安全性,加载某个 dylib 前,其所依靠的其余 dylib 文件肯定已经被预先加载。最后 dyld 会调用 main() 函数。main() 会调用 UIApplicationMain(),步伐启动。
使用Xcode打开一个项目,很容易会发现一个文件-main.m文件,此处就是应用的入口了。步伐启动时,先实验main函数,main函数是ios步伐的入口点,内部会调用UIApplicationMain函数,UIApplicationMain里会创建一个UIApplication对象 ,然后创建UIApplication的delegate对象 —–(您的)AppDelegate ,开启一个消息循环(main runloop),每当监听到对应的系统变乱时,就会通知AppDelegate。
- int main(int argc, char * argv[]) {
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil,
- NSStringFromClass([AppDelegate class]));
- }
- }
复制代码 UIApplication对象是应用步伐的象征,每一个应用都有自己的UIApplication对象,而且是单例的。通过[UIApplication sharedApplication]可以获得这个单例对象,一个iOS步伐启动后创建的第一个对象就是UIApplication对象,利用UIApplication对象,能进行一些应用级别的操纵。
UIApplicationMain函数实现如下:
- int UIApplicationMain{
- int argc,
- char *argv[],
- NSString *principalClassName,
- NSString *delegateClassName
- }
复制代码 第一个参数体现参数的个数,第二个参数体现装载函数的数组,第三个参数,是UIApplication类名或其子类名,若是nil,则默认使用UIApplication类名。第四个参数是协议UIApplicationDelegate的实例化对象名,这个对象就是UIApplication对象监听到系统变化的时间通知其实验的相应方法。
启动完毕会调用 didFinishLaunching方法,并在这个方法中创建UIWindow,设置AppDelegate的window属性,并设置UIWindow的根控制器。如果有storyboard,会根据info.plist中找到应用步伐的入口storyboard并加载箭头所指的控制器,体现窗口。storyboard和xib最大的不同在于storyboard是基于试图控制器的,而非视图或窗口。展示之前会将添加rootViewController的view到UIWindow上面(在这一步才会创建控制器的view)
- [window addSubview: window.rootViewControler.view];
复制代码 每个应用步伐至少有一个UIWindow,这window负责管理和协调应用步伐的屏幕体现,rootViewController的view将会作为UIWindow的首视图。
步伐启动的完整过程如下:
1.main 函数。
2.UIApplicationMain
a.创建UIApplication对象。
b.创建UIApplication的delegate对象。
- delegate对象开始处理处罚(监听)系统变乱(没有storyboard)
a.步伐启动完毕的时间, 就会调用代理的application:didFinishLaunchingWithOptions:方法。
b.在application:didFinishLaunchingWithOptions:中创建UIWindow。
c.创建和设置UIWindow的rootViewController。
d.体现窗口。
4.根据Info.plist获得最重要storyboard的文件名,加载最重要storyboard(有storyboard)。
a.创建UIWindow。
b.创建和设置UIWindow的rootViewController。
c.体现窗口。
AppDelegate的代理方法
- //app启动完毕后就会调用
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- }
- //app程序失去焦点就会调用
- - (void)applicationWillResignActive:(UIApplication *)application
- {
- }
- //app进入后台的时候调用, 一般在这里保存应用的数据(游戏数据,比如暂停游戏)
- - (void)applicationDidEnterBackground:(UIApplication *)application
- {
- }
- //app程序程序从后台回到前台就会调用
- - (void)applicationWillEnterForeground:(UIApplication *)application
- {
- }
- //app程序获取焦点就会调用
- - (void)applicationDidBecomeActive:(UIApplication *)application
- {
- }
- // 内存警告,可能要终止程序,清除不需要再使用的内存
- - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
- {
- }
- // 程序即将退出调用
- - (void)applicationWillTerminate:(UIApplication *)application
- {
- }
复制代码 AppDelegate加载次序
1.application:didFinishLaunchingWithOptions:
2.applicationDidBecomeActive:
ViewController中的加载次序
1.loadView
2.viewDidLoad
3.viewWillAppear
4.viewWillLayoutSubviews
5.viewDidLayoutSubviews
6.viewDidAppear
View中的加载次序
1.initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)
2.awakeFromNib
3.layoutSubviews
4.drawRect
一些方法的使用机遇
应用步伐启动就会调用的方法,在这个方法里写的代码开始调用。
用到本类时才调用,这个方法里一样平常设置导航控制器的主题等,如果在后面的方法设置导航栏主题就太迟了!
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
复制代码 这个方法里面会创建UIWindow,设置根控制器并显现,比如某些应用步伐要加载授权页面也是在这加,也可以设置观察者,监听到通知切换根控制器等。
在使用IB的时间才会涉及到此方法的使用,当.nib文件被加载的时间,会发送一个awakeFromNib的消息到.nib文件中的每个对象,每个对象都可以定义自己的awakeFromNib函数来响应这个消息,实验一些必要的操纵。在这个方法里设置view的背景等一系列普通操纵。
创建视图的层次结构,在没有创建控制器的view的情况下不能直接写 self.view 由于self.view的底层是:
- if(_view == nil){
- _view = [self loadView]
- }
复制代码 这么写会直接造成死循环。
如果重写这个loadView方法里面什么都不写,会体现黑屏。
- - (void)viewWillLayoutSubviews;
复制代码 视图将要布局子视图,苹果建议的设置界面布局属性的方法,这个方法和viewWillAppear里,系统的底层都是没有写任何代码的,也就是说这里面不写super 也是可以的。
在这个方法里一样平常设置子控件的frame。
- - (void)drawRect:(CGRect)rect;
复制代码 UI控件都是画上去的,在这一步就是把所有的东西画上去。drawRect方法只能在加载时调用一次,如果后面还需要调用,比如下载进度的圆弧,需要一直刷帧,就要使用setNeedsDisplay来定时多次调用本方法。
- - (void)applicationDidBecomeActive:(UIApplication *)application;
复制代码 这是AppDelegate的应用步伐获取核心方法,真正到了这里,才是所有东西全部加载完毕。
启动分析
应用启动时,会播放一个启动动画。iPhone上是400ms,iPad上是500ms。如果应用启动过慢,用户就会放弃使用,甚至永久都不再回来。为了防止一个应用占用过多的系统资源,开发iOS的苹果工程师门筹划了一个“看门狗”的机制。在不同的场景下,“看门狗”会监测应用的性能。如果超出了该场景所规定的运行间,“看门狗”就会强制终结这个应用的历程。
iOS App启动时会链接并加载Framework和Static lib,实验UIKit初始化,然后进入应用步伐回调,实验Core Animation transaction等。每个Framework都会增加启动时间和占用的内存,不要链接不必要的Framework,必要的Framework不要标记为Optional。克制创建全局的C++对象。
初始化UIKit时字体、状态栏、user defaults、Main.storyboard会被初始化。User defaults本质上是一个plist文件,生存的数据是同时被反序列化的,不要在user defaults里面生存图片等大数据。
对于 OC 来说应尽量减少 class,selector 和 category 这些元数据的数量。编码原则和筹划模式之类的理论会鼓励大家多写精致短小的类和方法,并将每部门方法独立出一个种别,但这会增加启动时间。在调用的地方使用初始化器,不要使用atribute((constructor)) 将方法显式标记为初始化器,而是让初始化方法调用时才实验。比如使用 dispatch_once(),pthread_once() 或 std:nce()。也就是在第一次使用时才初始化,推迟了一部门工作耗时。
创建网络毗连前需要做域名解析,如果网关出现题目,dns解析不正常时,dns的超时时间是应用控制不了的。在步伐筹划时要考虑这些题目,如果步伐启动时有网络毗连,应尽快的结束启动过程,网络访问通过线程解决,而不阻塞主线程的运行。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |