iOS APP 从编译到运行

这篇文章讨论两个问题:
我们开发一个APP,从新建项目,写UI,写业务逻辑,之后模拟器或真机运行。
1、这一套过程Xcode做了哪些主要事情呢
2、iPhone在启动一个APP之前都做了哪些事情呢?

0

OC是静态语言,写好的代码被编译链接生成可执行文件才可以在平台运行。

下面分别从代码到打app包,再从app包启动运行这两个过程说明一下主要过程。

1 OC语言开发的APP编译过程:

1、把源文件(.c, .h, .m, .mm, .cpp)先进行预编译(宏定义的预处理)

2、把OC高级语言代码编译成汇编语言底层代码

3、链接 .a, .lib等静态库文件,依赖UIKit等cocoa库,写入可窒息那个文件

4、生成可执行文件

2 APP运行之前iOS系统做了哪些工作?

对程序员来说,我们学编程的时候就知道,程序要运行,所有的一切都要从main函数说起,我们的代码都会写在appDelegate之后的源文件中。
但是iOS系统在调用应用程序的main()函数之前已经做了好多工作。比如我们在某个ViewController中写的+load()方法,+initialize()方法。一般也会把方法交换写在这里。也就是main函数之前,load,initialze方法都已经执行了。

2.1 先直观的看一下动态链接的时哪些东西

应用程序在运行时会先进行动态库链接。链接一些苹果提供的动态库如 UIKit.
我们真机运行一个APP,然后在produc里找到app源文件,进入finder,在进入包里,看到可执行文件。
利用otool工具的命令: $ otool -L app.app/app
可以看到运行时应用都动态链接里哪些framework

macnaem:Debug-iphoneos username$ otool -L app.app/app
app.app/APP:
	/usr/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.5)
	/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
	/usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 64.2.0)
	/System/Library/Frameworks/CFNetwork.framework/CFNetwork (compatibility version 1.0.0, current version 0.0.0)
	/System/Library/Frameworks/QuickLook.framework/QuickLook (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/CoreMedia.framework/CoreMedia (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/Photos.framework/Photos (compatibility version 1.0.0, current version 3612.27.220)
	/System/Library/Frameworks/AssetsLibrary.framework/AssetsLibrary (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/AVFoundation.framework/AVFoundation (compatibility version 1.0.0, current version 2.0.0)
	/System/Library/Frameworks/AdSupport.framework/AdSupport (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/CoreLocation.framework/CoreLocation (compatibility version 1.0.0, current version 2388.0.21)
	/System/Library/Frameworks/CoreMotion.framework/CoreMotion (compatibility version 1.0.0, current version 2388.0.21)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 800.7.0)
	/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony (compatibility version 1.0.0, current version 0.0.0)
	/usr/lib/libsqlite3.dylib (compatibility version 9.0.0, current version 308.4.0)
	/System/Library/Frameworks/Security.framework/Security (compatibility version 1.0.0, current version 59306.42.2)
	/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 1348.12.1)
	/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration (compatibility version 1.0.0, current version 1061.40.2)
	/System/Library/Frameworks/OpenGLES.framework/OpenGLES (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/QuartzCore.framework/QuartzCore (compatibility version 1.2.0, current version 1.11.0)
	/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices (compatibility version 1.0.0, current version 1069.12.0)
	/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1673.126.0)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
	/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1673.126.0)
	/System/Library/Frameworks/ImageIO.framework/ImageIO (compatibility version 1.0.0, current version 0.0.0)
	/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)
	/System/Library/Frameworks/UserNotifications.framework/UserNotifications (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/WebKit.framework/WebKit (compatibility version 1.0.0, current version 608.3.10)

可以直观的看到动态链接里好多系统提供的库文件。
其中
CoreGraohics被UIKit依赖。有两个默认添加的lib
libobjc库包含了 objc, 和 runtime
libSystem中包含了很多系统提供的lib如:libdisptch(GCD),libsystem_c(C语言库),libsystem_blocks(Block),libcommonCrypto(加密库)
这些动态库类似windows中的dll,程序运行时才会动态链接。
使用动态链接有很多好处:
-代码公用: 很多程序都动态链接了这些lib,但他们在内存和磁盘中只有一份
-易于维护:由于被依赖的lib时程序执行时才link的,所以这些lib很容易做更新,例如libSystem.dylib是libSystem.B.dylib的替身,哪天想升级,直接换成libSystem.C.dylib然后再替换替身就行了。
-减少可执行文件的体积:相比静态链接,动态链接在编译时就不需要打到安装包里,所以包比较小

1、从 kernel 留下的原始调用栈引导和启动自己
2、将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
3、non-lazy 符号立即 link 到可执行文件,lazy 的存表里
4、Runs static initializers for the executable
5、找到可执行文件的 main 函数,准备参数并调用
6、程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口
7、程序main函数 return 后执行 static terminator
8、某些场景下 main 函数结束后调 libSystem 的 _exit 函数

从dyld的源代码里可以发现。
dyldStartup.s这个文件,其中用汇编实现里 __dyld_start的方法。它做了两件事:
1、调用 dyldbootstrap::start()方法
2、上述方法返回里main函数的地址,填入参数并调用main函数。

我们可以在Xcode里添加一个符号断点“_objc_init”,然后启动调试。

这个时runtime的初始化函数。
点开断点的调用栈发现,栈底的dyldbootstrap::start()方法,继而调用 dyld::_main()方法。
由于libSystem 默认引入,栈中出现libSystem_initializer的初始化方法。

dyld开源地址

2.3 ImageLoader

这里的image是一个二进制文件,里面是编译过的符号,代码,所以ImageLoder的作用是将这些文件加载到内存,且每一个文件由一个对应的ImageLoader实例来负责加载
我们调试的时候可以用lldb 调试 命令:(lldb) po image 0x1c4a21dc0 查看一个地址指向内存块的iamge布局,比如我们查看

(lldb) po image _bannerView
<PABCycleBannerCollectionView: 0x1078dbb60; frame = (0 0; 375 250); layer = <CALayer: 0x1c4a21dc0>>

  Fix-it applied, fixed expression was: 
    image; _bannerView
(lldb) po image 0x1c4a21dc0
<CALayer:0x1c4a21dc0; position = CGPoint (187.5 125); bounds = CGRect (0 0; 375 250); delegate = <PABCycleBannerCollectionView: 0x1078dbb60; frame = (0 0; 375 250); layer = <CALayer: 0x1c4a21dc0>>; sublayers = (<CALayer: 0x1c0a32f60>, <CALayer: 0x1c0a33360>); opaque = YES; allowsGroupOpacity = YES; >

  Fix-it applied, fixed expression was: 
    image; 0x1c4a21dc0

过程如下:
1、在程序运行时它先将动态链接的image 递归加载,(也就是上面测试栈中一串的递归调用的时刻)
2、再从可执行文件image递归加载所有符号
这些操作从逻辑上一看就是发生在main 函数执行前。

2.4 runtime 与 +load()

libSystem 是若干个系统lib的集合,所以它只是一个容器lib。而且它是开源的,里面实质上就是一个文件,init.c,由libSystem_initialzer逐步调用到了_objc_init,这里就是objc 和rumtime的初始化入口。
除了runtime环境的初始化外,_objc_init中绑定了新image被加载后的callback:
dyld_register_imaeg_state_change_handler(dyld_iamge_state_bound, 1, &map_iamges);
dyld_register_image_state_change_handler(dyld_iamge_state_dependents_initialized, 0, &load_iamges);
dyld担当了runtime和imageLoader中间的协调者,当新iamge加载进来后交由rumtime大厨去解析这个二进制文件的符号表和代码。

继续上面的断点法,来定位 +load方法。
发现整个调用栈和顺序:
1、dyld开始将程序二进制文件初始化
2、交由imageLoader读取iamge,其中包含了我们的类,方法等各种符号
3、由于runtime向dyld绑定了回调,当iamge加载到内存后,dyld会通知runtime进行处理
4、runtime接手后调用map_iamges做解析和处理,接下来load_iamges中调用call_load_methods方法,遍历所有加载进来的Class,按继承层级依次调用Class的+load方法和其Category的+load方法。
至此,可执行文件和动态库所有的符号(Class, Protocol, Selector, IMP,...)都已经按照格式成功加载到内存中,被runtime所管理,在这之后,runtime的那些方法(动态添加Class, swizzle等等才能生效)

load方法的几个问题:
Q:重载自己Class的+load方法时需不需要调用父类?
A:runtime负责按继承顺序递归调用,所以不能调用super

Q:在自己Class的+load方法时能不能替换系统framework(例如UIKit)中某个类方法的实现?
A:可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的

Q:重载+load时需要手动添加@autoreleasepool么?
A:不需要,在runtime调用+load方法前后是加了 objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop()的。

Q:想让一个类的+load方法被调用是否需要在某个地方import这个文件
A: 不需要,只要这个类的符号被编译到最后的可执行文件中,+load方法就会被调用。

main函数执行之前的事件是由dyld主导,完成运行环境的初始化,配合ImageLoader将二进制文件按格式加载到内存。
动态链接库,并由runtime负责加载成objc定义的结构,所有初始化工作结束后,dyld调用真正的main函数。

引用 sunnyxxx 的博客

猜你喜欢

转载自www.cnblogs.com/wjw-blog/p/12802232.html