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()方法。一般也會把方法交換寫在這里。也就是main函數之前,load方法都已經執行了。
【相比之下, +initialize()方法是在程序運行時第一次向某個類發送消息的時候才調用該類的+initialize()方法】

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 的博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM