前言和目錄
該文章主要整理一些小知識(shí)點(diǎn),主要涉及 iOS 以及計(jì)算基礎(chǔ)相關(guān)知識(shí)點(diǎn),某些知識(shí)點(diǎn)暫時(shí)只有標(biāo)題,后續(xù)會(huì)持續(xù)更新。筆者最近一段時(shí)間面試過程中發(fā)現(xiàn)一些普遍現(xiàn)象,對于一些很不起眼的問題,很多開發(fā)者都只停留在知道、聽說過的層面,但是一旦問 是什么 和 為什么 ,很多應(yīng)試者回答的并不理想,比如下面的幾個(gè)問題:
推薦閱讀:iOS開發(fā)——BAT面試題合集(持續(xù)更新中)
數(shù)組的下標(biāo)為什么從零開始?
經(jīng)常聽到深拷貝和淺拷貝,為什么會(huì)存在深拷貝和淺拷貝這一對概念?
block 和 函數(shù)指針有什么區(qū)別?
引用的本質(zhì)是什么?引用和指針有什么關(guān)系?
UI 性能優(yōu)化的時(shí)候,很多面試者會(huì)提到用CALayer代替視圖組件,如果某天產(chǎn)品改需求,要求添加觸發(fā)事件,那么CALayer上怎么添加觸發(fā)事件?
和 H5 交互的時(shí)候,經(jīng)常會(huì)用到userAgent, 請問 userAgent 是什么?(問過幾次,純 iOS 開發(fā)者沒幾人知道只說有印象)
標(biāo)準(zhǔn)的 MVC 架構(gòu)模式中,View 和 Model是完全獨(dú)立開來的,很多開發(fā)者都說自己使用的是 MVC 模式,當(dāng)問起:為什么實(shí)際開發(fā)中自定義視圖組件時(shí)通常都會(huì)引入 Model ,并重寫 setModel 方法?這還是不是 MVC ?
面試過程中筆者偶爾會(huì)問多線程的相關(guān)問題,印象中有兩位應(yīng)試者脫口而出 自旋鎖 ,當(dāng)問及什么是 互斥鎖 ?什么是 自旋鎖 ?應(yīng)試者一臉懵,明明是自己給自己挖坑。此外還會(huì)問到:為什么線程會(huì)不安全?也沒幾個(gè)應(yīng)試者能完整回答出。
很多應(yīng)試者都知道,http 和 https 的區(qū)別在于多了 SSL 層,但是 SSL 層里面有什么,做了什么,位于網(wǎng)絡(luò)模型什么位置?
很多人都知道內(nèi)存(堆內(nèi)存)回收,但是內(nèi)存(堆內(nèi)存)回收后發(fā)生了什么?是把內(nèi)存從堆空間清空了嗎?還是重置為 0 ?還是說做了其他什么操作?
MD5 安全嗎?如果不安全,有什么替代的方案?MD5算是加密算法的一種嗎?如果不是,和加密算法有什么區(qū)別?
pods 經(jīng)常用吧,pods 命令后面的參數(shù)--verbose 和 --no-repo-update 是什么意思?
令筆者比較驚訝的是,響應(yīng)鏈流程算是 iOS 入門基礎(chǔ)知識(shí)。筆者問了一道相關(guān)問題百分之七八十的面試者都很難回答上來。A 為父視圖,依次執(zhí)行[A addSubView:B]、[A addSubView:C]、C.userInteractionEnabled = NO,其中 B 視圖和 C 視圖有重疊,請問:B 視圖添加點(diǎn)擊事件能否響應(yīng)?多數(shù)應(yīng)試者第一反應(yīng)是不能,結(jié)合響應(yīng)鏈流程來看,答案顯然是錯(cuò)誤的。
super 經(jīng)常用,請問 super 調(diào)用方法和 self 調(diào)用方法有什么本質(zhì)區(qū)別?
以上僅是部分典型小知識(shí)點(diǎn),更多內(nèi)容請?jiān)斂创宋摹?/p>
目錄
一、CALayer如何添加點(diǎn)擊事件
二、為什么會(huì)存在堆空間
三、Tagged Pointer 是什么?
四、iOS平臺(tái)跨域訪問漏洞
五、緩存 NSDateFormatter
六、iOS 9 以后通知不再需要手動(dòng)移除
七、UIImage 名稱為空的警告(符號斷點(diǎn)解決)
八、NSUserDefaults 存儲(chǔ)字典的一個(gè)坑
九、performSelector:afterDelay:的坑
十、 @autoreleasepool
十一、如何對 NSMutableArray 進(jìn)行 KVO
十二、被忽略的UIViewController兩對API
十三、抗壓縮優(yōu)先級
十四、約束優(yōu)先級
十五、設(shè)置代碼只在 Debug 下起效
十六、為什么會(huì)有深拷貝和淺拷貝之分
十七、為什么交叉方法出現(xiàn)"死循環(huán)"
十八、為什么數(shù)組下標(biāo)從零開始
十九、copy 修飾符引發(fā)崩潰問題
二十、為什么量子密碼學(xué)會(huì)有取代傳統(tǒng)加密方法的趨勢
二十一、引用計(jì)數(shù)是怎么管理的
二十二、weak 原理
二十三、加鹽的意義
二十四、Shell 腳本
二十五、什么是UserAgent
二十六、JS和OC通信方式匯總
二十七、UIScrollView 原理
二十八、--verbose 和 --no-repo-update
二十九、dataSource 和 delegate 的本質(zhì)區(qū)別
三十、變種 MVC
三十一、函數(shù)指針和 Block
三十二、內(nèi)存(堆內(nèi)存)回收是什么意思
三十三、IP 和 MAC
三十四、MD5 相關(guān)小知識(shí)
三十五、響應(yīng)鏈問題
三十六、什么是線程不安全?線程不安全的本質(zhì)原因?
三十七、App 啟動(dòng)流程
三十八、包體積優(yōu)化中的內(nèi)聯(lián)函數(shù)
三十九、super 本質(zhì)
四十、引用的本質(zhì)(引用和指針的區(qū)別)
四十一、渲染框架分類
四十二、NSProxy & NSObject
四十三、如何給百萬數(shù)據(jù)排序
四十四、自旋鎖 & 互斥鎖
四十五、應(yīng)用 Crash 時(shí)系統(tǒng)做了什么操作
一、CALayer如何添加點(diǎn)擊事件
兩種方法: convertPoint和hitTest:,hitTest: 返回的順序嚴(yán)格按照圖層樹的圖層順序。
|
1
2
3
4
5
6
7
8
|
- (``void``)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject] locationInView:self.view];
CGPoint redPoint = [self.redLayer convertPoint:point fromLayer:self.view.layer];
if
([self.redLayer containsPoint:redPoint]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@``"point red"
message:@``""
delegate:nil cancelButtonTitle:@``"OK"
otherButtonTitles:nil];
[alert show];
}
}
|
|
1
2
3
4
5
6
7
8
9
10
11
|
- (``void``)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject] locationInView:self.view];
CALayer *layer = [self.view.layer hitTest:point];
if
(layer == self.redLayer) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@``"point red"
message:@``""
delegate:nil cancelButtonTitle:@``"OK"
otherButtonTitles:nil];
[alert show];
}``else
if
(layer == self.yellowLayer){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@``"point yellow"
message:@``""
delegate:nil cancelButtonTitle:@``"OK"
otherButtonTitles:nil];
[alert show];
}
}
|
二、為什么會(huì)存在堆空間
堆空間的存在主要是為了延長對象的生命周期,并使得對象的生命周期可控。
如果試圖用棧空間取代堆空間,顯然是不可行的。棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,如果申請的空間超過棧的剩余空間時(shí),將出現(xiàn)棧溢出,發(fā)生未知錯(cuò)誤。因此,能從棧獲得的空間較小。而堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲(chǔ)的空閑內(nèi)存地址的。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。 但是棧空間比堆空間響應(yīng)速度更快,所以一般類似int、NSInteger等占用內(nèi)存比較小的通常放在棧空間,對象一般放在堆空間。
如果試圖用數(shù)據(jù)區(qū)(全局區(qū))取代堆空間,顯然也是不可行的。因?yàn)槿謪^(qū)的生命周期會(huì)伴隨整個(gè)應(yīng)用而存在,比較消耗內(nèi)存,生命周期不向在堆空間那樣可控,堆空間中可以隨時(shí)創(chuàng)建和銷毀。
代碼區(qū)就不用想了,如果能夠輕易改變代碼區(qū),一個(gè)應(yīng)用就無任何安全性可言了。
三、Tagged Pointer 是什么?
從 64bit 開始,iOS 引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對象的存儲(chǔ)。在沒有使用Tagged Pointer之前, NSNumber等對象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對象的地址值;使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中。當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)。
四、iOS平臺(tái)跨域訪問漏洞
UIWebView 默認(rèn)開啟了WebKitAllowUniversalAccessFromFileURLs 和 WebKitAllowFileAccessFromFileURLs 屬性。利用這個(gè)漏洞給某個(gè) App 下發(fā)一個(gè) HTML 文件,當(dāng) UIWebView 使用 file 協(xié)議打開這個(gè) HTML 文件, HTML 文件中含有一段竊取用戶數(shù)據(jù)的 JS 代碼,就會(huì)導(dǎo)致用戶數(shù)據(jù)泄露。
|
1
2
3
|
NSString *filePath = [[NSBundle mainBundle] pathForResource:@``"index"
ofType:@``"html"``];
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]]];
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 這個(gè)可以是手機(jī)任意一個(gè)文件地址
var
localfile = ``"/etc/passwd"
var
xhr = ``new
XMLHttpRequest();
xhr.onreadystatechange = ``function``() {
if
(xhr.readyState == ``4``) {
alert(xhr.responseText);
}
}
try
{
xhr.open(``"GET"``, localfile, ``true``);
xhr.send();
} ``catch
(ex) {
alert(ex.message);
}
|
上面代碼可以讀取出手機(jī)端 /etc/passwd 的文件。這個(gè)漏洞訪問其他應(yīng)用的數(shù)據(jù),而不必需要用戶的許可。但WKWiebView的 WebKitAllowUniversalAccessFromFileURLs 和 WebKitAllowFileAccessFromFileURLs 默認(rèn)是關(guān)閉的(可以手動(dòng)控制),不會(huì)存在這樣的風(fēng)險(xiǎn)。
補(bǔ)充:針對 https 請求UIWebView需要做額外處理,借助NSURLConnection做證書驗(yàn)證,而WKWebView無需做過多額外處理。
五、緩存 NSDateFormatter
緩存原因參考蘋果官方文檔:
Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static variable.
六、iOS 9 以后通知不再需要手動(dòng)移除
通知 NSNotification 在注冊者被回收時(shí)需要手動(dòng)移除,是一直以來的使用準(zhǔn)則。原因是在 MRC 時(shí)代,通知中心持有的是注冊者的 unsafe_unretained 指針,在注冊者被回收時(shí)若不對通知進(jìn)行手動(dòng)移除,則指針指向被回收的內(nèi)存區(qū)域,變?yōu)橐爸羔槨4藭r(shí)發(fā)送通知會(huì)造成 crash 。而在 iOS 9 以后,通知中心持有的是注冊者的 weak 指針,這時(shí)即使不對通知進(jìn)行手動(dòng)移除,指針也會(huì)在注冊者被回收后自動(dòng)置空。因?yàn)橄蚩罩羔槹l(fā)送消息是不會(huì)有問題的。
七、UIImage 名稱為空的警告(符號斷點(diǎn)解決)
[UIImage imageNamed:] 傳了 nil 或者傳入@"",控制臺(tái)會(huì)輸出[framework] CUICatalog: Invalid asset name supplied: '(null)'。通過符號斷點(diǎn)可定位。
八、NSUserDefaults 存儲(chǔ)字典的一個(gè)坑
|
1
2
3
4
5
6
7
|
NSDictionary *dict = @{@``1``: @``"1"``,
@``2``: @``"2"``,
@``3``: @``"3"``,
@``4``: @``"4"``};
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:@``"key"``];
[[NSUserDefaults standardUserDefaults] synchronize];
|
執(zhí)行上述代碼會(huì)報(bào)如下錯(cuò)誤:
|
1
2
3
4
5
6
|
[User Defaults] Attempt to ``set
a non-property-list object {
3
= ``"3"``;
2
= ``"3"``;
1
= ``"1"``;
4
= ``"4"``;
} ``as
an NSUserDefaults/CFPreferences value ``for
key
key``
|
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
......
And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.
蘋果官網(wǎng)有上述這樣一段話,能往 NSUserDefaults 里存儲(chǔ)的對象只能是 property list objects,包括 NSData,NSString, NSNumber, NSDate, NSArray, NSDictionary,且對于 NSArray 和 NSDictionary 這兩個(gè)容器對象,它們所包含的內(nèi)容也必需是 property list objects。重點(diǎn)看最后一句話,雖然 NSDictionary 和 CFDictionary 對象的 Key 可以為任何類型(只要遵循 NSCopying 協(xié)議即可),但是如果當(dāng)Key 不為字符串 string 對象時(shí),此時(shí)這個(gè)字典對象就不能算是property list objects了,所以不能往 NSUserDefaults 中存儲(chǔ),不然就會(huì)報(bào)錯(cuò)。
九、performSelector:afterDelay:的坑
|
1
2
3
4
5
6
|
dispatch_queue_t queue = dispatch_get_global_queue(``0``, ``0``);
dispatch_async(queue, ^{
NSLog(@``"1"``);
[self performSelector:self withObject:@selector(test) afterDelay:.``0``];
NSLog(@``"3"``);
});
|
|
1
2
3
|
- (``void``)test{
NSLog(@``"2"``);
}
|
上述代碼的執(zhí)行結(jié)果并非 1 2 3 ,而是 1 3。原因是performSelector: withObject: afterDelay:的本質(zhì)是往 RunLoop中添加定時(shí)器,而子線程默認(rèn)是沒有啟動(dòng)RunLoop。performSelector: withObject: afterDelay:接口雖然和performSelector:系列接口長得很類似。但前者存在于RunLoop相關(guān)文件,后者存在于NSObject相關(guān)文件。
十、 @autoreleasepool
autoreleasepool 使用
每次遍歷的時(shí)候生成了很多占內(nèi)存大的對象,如果交于默認(rèn)的 autoreleasepool 去管理生命周期,會(huì)有因?yàn)閮?nèi)存飆升產(chǎn)生crash的風(fēng)險(xiǎn),遍歷過程中,可在適當(dāng)?shù)奈恢蒙先ナ褂聾autoreleasepool,一旦出了@autoreleasepool作用域,該作用域內(nèi)的變量會(huì)立馬釋放。如:
|
1
2
3
4
|
for``(``int
i = ``0``; i < ``10000``; i++){
@autoreleasepool {
}
}
|
但并不是所有的遍歷方法都要加上@autoreleasepool,比如enumerateObjectsUsingBlock:方法,仔細(xì)閱讀蘋果官方文檔,可發(fā)現(xiàn)該方法內(nèi)部已經(jīng)添加過@autoreleasepool處理。
autoreleasepool 底層
autoreleasepool 底層是個(gè)C++結(jié)構(gòu)體,創(chuàng)建和銷毀的時(shí)候分別會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
|
1
2
3
4
5
6
7
8
9
|
struct __AtAutoreleasePool {
__AtAutoreleasePool() { ``// 構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體的時(shí)候調(diào)用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { ``// 析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時(shí)候調(diào)用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void
* atautoreleasepoolobj;
};
|
系統(tǒng)默認(rèn) autoreleasepool
iOS 中有個(gè)默認(rèn)的autoreleasepool,主線程的 Runloop 中注冊了 2 個(gè) Observer:
第1個(gè)Observer監(jiān)聽kCFRunLoopEntry事件,會(huì)調(diào)系統(tǒng)默認(rèn)autoreleasepool的 objc_autoreleasePoolPush() ;
-
第2個(gè)Observer:
監(jiān)聽kCFRunLoopBeforeWaiting事件,會(huì)調(diào)系統(tǒng)默認(rèn)autoreleasepool的 objc_autoreleasePoolPop()、objc_autoreleasePoolPush();
監(jiān)聽了kCFRunLoopBeforeExit事件,會(huì)調(diào)系統(tǒng)默認(rèn)autoreleasepool的objc_autoreleasePoolPop();
autorelease和autoreleasepool
內(nèi)存管理中調(diào)用alloc、new、copy、mutableCopy方法返回對象,在不需要這個(gè)對象時(shí),要調(diào)用 release 或autorelease 來釋放它,MRC 中通常會(huì)使用 release 和 autorelease。autorelease 一般是僅用在 MRC 中。
十一、如何對 NSMutableArray 進(jìn)行 KVO
一般情況下只有通過調(diào)用 set 方法對值進(jìn)行改變才會(huì)觸發(fā) KVO。但是在調(diào)用NSMutableArray的 addObject或removeObject 系列方法時(shí),并不會(huì)觸發(fā)它的 set 方法。所以為了實(shí)現(xiàn)NSMutableArray的 KVO,官方為我們提供了如下方法:
|
1
|
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key
|
在增刪元素時(shí),使用上述方法來獲取要操作的可變數(shù)組,然后再執(zhí)行添加或刪除元素的操作,便能實(shí)現(xiàn) KVO 機(jī)制。如:
|
1
|
@property (nonatomic, strong) NSMutableArray *arr;
|
|
1
2
3
4
|
//添加元素操作
[[self mutableArrayValueForKey:@``"arr"``] addObject:item];
//移除元素操作
[[self mutableArrayValueForKey:@``"arr"``] removeObjectAtIndex:``0``];
|
十二、被忽略的UIViewController兩對API
如何判斷一個(gè)頁面的viewWillAppear方法是 push 或 present 進(jìn)來是調(diào)用的,還是 pop 或 dismiss 是調(diào)用的?一種比較笨拙的方法是通過添加屬性標(biāo)記是進(jìn)入還是返回調(diào)用viewWillAppear方法。還有一種最簡單的方法,是直接調(diào)用蘋果提供的兩對 API 。
針對 Push 和 Pop 或 add childViewController 和 remove childViewController 的 API:
|
1
2
|
@property(nonatomic, readonly, getter=isMovingToParentViewController) BOOL movingToParentViewController NS_AVAILABLE_IOS(5_0);
@property(nonatomic, readonly, getter=isMovingFromParentViewController) BOOL movingFromParentViewController NS_AVAILABLE_IOS(5_0);
|
針對 Present 和 Dismiss 的 API:
|
1
2
|
@property(nonatomic, readonly, getter=isBeingPresented) BOOL beingPresented NS_AVAILABLE_IOS(5_0);
@property(nonatomic, readonly, getter=isBeingDismissed) BOOL beingDismissed NS_AVAILABLE_IOS(5_0);
|
十三、抗壓縮優(yōu)先級
兩個(gè)水平布局的label,兩邊間隔分別是12,中間間隔為8(懂意思就行)。如果兩個(gè)label 都不設(shè)置寬度,則左邊 label 會(huì)拉長,右邊 label 自適應(yīng)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero];
label1.backgroundColor = [UIColor redColor];
label1.text = @``"我是標(biāo)題"``;
[self.view addSubview:label1];
[label1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.view);
make.left.equalTo(@(``12``));
}];
UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero];
label2.backgroundColor = [UIColor redColor];
label2.text = @``"我是描述"``;
[self.view addSubview:label2];
[label2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(label1);
make.left.equalTo(label1.mas_right).offset(``8``);
make.right.equalTo(self.view).offset(-``12``);
}];
|
如果想讓左邊 label 自適應(yīng),右邊 label 拉升,可以設(shè)置控件拉升阻力(即抗拉升),拉升阻力越大越不容易被拉升。所以只要 label1 的拉升阻力比 label2 的大就能達(dá)到效果。
|
1
2
3
4
5
6
|
//UILayoutPriorityRequired = 1000
[label1 setContentHuggingPriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
// //UILayoutPriorityDefaultLow = 250
[label2 setContentHuggingPriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
|
Content Hugging Priority:拉伸阻力,即抗拉伸。值越大,越不容易被拉伸。
Content Compression Resistance Priority:壓縮阻力,即抗壓縮。值越大,越不容易被壓縮。
十四、約束優(yōu)先級
從左到右依次為紅、藍(lán)、黃三個(gè)視圖三等分,藍(lán)色視圖布局依賴紅色,黃色視圖布局依賴藍(lán)色,如果突然將中間的藍(lán)色視圖移除,紅色和黃色視圖的寬度就無法計(jì)算。此種情況可以設(shè)置最后一個(gè)黃色視圖的做約束優(yōu)先級,移除中間藍(lán)色視圖后,紅色和黃色視圖二等分。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//紅 left bottom height
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view.mas_left).``with``.offset(``20``);
make.bottom.mas_equalTo(self.view.mas_bottom).``with``.offset(-``80``);
make.height.equalTo(@``50``);
}];
//藍(lán) left bottom height width=紅色
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(redView.mas_right).``with``.offset(``40``);
make.height.width.bottom.mas_equalTo(redView);
}];
//黃 left right height width=紅色
[yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(blueView.mas_right).``with``.offset(``40``);
make.right.mas_equalTo(self.view.mas_right).``with``.offset(-``20``);
make.height.width.bottom.mas_equalTo(redView);
//優(yōu)先級
//必須添加這個(gè)優(yōu)先級,否則blueView被移除后,redView 和 yellowView 的寬度就不能計(jì)算出來
make.left.mas_equalTo(redView.mas_right).``with``.offset(``20``).priority(``250``);
}];
//移除藍(lán)色
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(``4
* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[blueView removeFromSuperview];
[UIView animateWithDuration:``3
animations:^{
//不加這行代碼就直接跳到對應(yīng)的地方,加這行代碼就可以執(zhí)行動(dòng)畫。
//另外還要注意調(diào)用layoutIfNeeded的對象必須是執(zhí)行動(dòng)畫的父視圖。
//[blueView.superview layoutIfNeeded];
[self.view layoutIfNeeded];
}];
});
|
十五、設(shè)置代碼只在 Debug 下起效
源代碼中的測試代碼一般可以通過#ifdef DEBUG ... #endif
.a 靜態(tài)庫或 .framework 動(dòng)態(tài)庫,可以通過設(shè)置 Library Search Paths 和 Framework Search Paths,分別移除Release環(huán)境對應(yīng)的路徑,Debug環(huán)境對應(yīng)的路徑保持不變。
對于 CocoaPods 引入的測試庫,可以配置 configurations 選項(xiàng)讓對應(yīng)的庫只在 Debug 模式下生效,如:
|
1
|
pod ``'RongCloudIM/IMKit'``, ``'~> 2.8.3'``,:configurations => [``'Debug'``]
|
十六、為什么會(huì)有深拷貝和淺拷貝之分
上圖中觀察可知只有不可變 + 不可變組合的時(shí)候才出現(xiàn)淺拷貝,其他三種情況都是深拷貝。原因在于,兩個(gè)不可變對象內(nèi)容一旦確定都是不可變的,所以不會(huì)彼此干擾,為了節(jié)省內(nèi)容空間,兩個(gè)對象可以指向同一塊內(nèi)存。而其他三種情況,都有可變對象的存在,為了避免兩個(gè)對象之間的彼此干擾,所有會(huì)開辟額外的空間。
十七、為什么交叉方法出現(xiàn)"死循環(huán)"
因?yàn)榻粨Q了方法的實(shí)現(xiàn) IMP ,如果alert_replaceInitWithString方法內(nèi)部調(diào)用initWithString會(huì)出現(xiàn)真正的死循環(huán)。下面代碼的死循環(huán)只是一個(gè)假象。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@implementation NSAttributedString (Exception)
+ (``void``)load{
static
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass(``"NSConcreteAttributedString"``) swizzleMethod:@selector(initWithString:) swizzledSelector:@selector(alert_replaceInitWithString:)];
}
});
}
-(instancetype)alert_replaceInitWithString:(NSString*)aString{
if
(!aString) {
NSString *string = [NSString stringWithFormat:@``"[%s:%d行]"``,[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
[[[ExceptionAlert alloc]init]showAlertWithString:string];
;
return
nil;
}
return
[self alert_replaceInitWithString:aString];
}
@end
|
十八、為什么數(shù)組下標(biāo)從零開始
數(shù)組下標(biāo)最確切的定義應(yīng)該偏移(offset),如果用 a 來表示數(shù)組的首地址,a[0] 就是偏移為 0 的位置,也就是首地址,a[k] 就表示偏移 k 個(gè) type_size 的位置,所以計(jì)算 a[k] 的內(nèi)存地址只需要用這個(gè)公式:
|
1
|
a[k]_address = base_address + k * type_size
|
但是,如果數(shù)組從 1 開始計(jì)數(shù),那我們計(jì)算數(shù)組元素 a[k]的內(nèi)存地址就會(huì)變?yōu)椋?/p>
|
1
|
a[k]_address = base_address + (k-``1``)*type_size
|
對比兩個(gè)公式,不難發(fā)現(xiàn),從 1 開始編號,每次隨機(jī)訪問數(shù)組元素都多了一次減法運(yùn)算,對于 CPU 來說,就是多了一次減法指令。數(shù)組作為非常基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),通過下標(biāo)隨機(jī)訪問數(shù)組元素又是其非常基礎(chǔ)的編程操作,效率的優(yōu)化就要盡可能做到極致。所以為了減少一次減法操作,數(shù)組選擇了從 0 開始編號,而不是從 1 開始。
十九、copy修飾符引發(fā)崩潰問題
可變數(shù)組或字典經(jīng)過 copy 修飾符修飾后,變成不可變數(shù)組或字典,此時(shí)再去執(zhí)行添加或插入元素的時(shí)候會(huì)發(fā)生崩潰。
二十、為什么量子密碼學(xué)會(huì)有取代傳統(tǒng)加密方法的趨勢
傳統(tǒng)的加密方式存在兩個(gè)問題:
如在非對稱加密 RSA 體系中存在私鑰,只要對方獲取到私鑰就能破解,為此會(huì)有針對私鑰泄露相關(guān)的吊銷證書檢測機(jī)制。所以只能說相對安全不能說絕對安全。
RSA 加密是基于互質(zhì)關(guān)系實(shí)現(xiàn)的,不是沒有破解的可能,只是需要時(shí)間,如果并發(fā)機(jī)器足夠多,時(shí)間足夠多(幾十年或幾百年),RSA 加密依然是可以破解的。
關(guān)于互質(zhì)關(guān)系
如果兩個(gè)正整數(shù),除了1以外,沒有其他公因數(shù),我們就稱這兩個(gè)數(shù)是互質(zhì)關(guān)系(coprime)。
量子密碼學(xué)是基于量子形態(tài)做加解密,如果想破解必須要介入到量子狀態(tài)中,但是量子傳輸過程中可監(jiān)聽到監(jiān)聽者的介入。目前量子密碼仍處于研究階段,并沒有成熟的應(yīng)用,量子很容易收到外界的干擾而改變狀態(tài)。
二十一、引用計(jì)數(shù)是怎么管理的
在arm64架構(gòu)之前,isa 就是一個(gè)普通的指針,存儲(chǔ)著Class、Meta-Class對象的內(nèi)存地址。從arm64架構(gòu)開始,對isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來存儲(chǔ)更多的信息。 isa 的結(jié)構(gòu)如下:
extra_r:里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
-
has_sidetable_rc表示引用計(jì)數(shù)器是否過大無法存儲(chǔ)在isa中,如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中。
SideTable 結(jié)構(gòu)如下,其中 refcnts 是一個(gè)存放著對象引用計(jì)數(shù)的散列表,用當(dāng)前對象的地址值作為 key ,對象的引用計(jì)數(shù)作為 Value。
二十二、weak 原理
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- (``void``)viewDidLoad {
[``super
viewDidLoad];
__strong Person *person1;
__weak Person *person2;
Person *person3;
NSLog(@``"111"``);
{
Person *person = [[MJPerson alloc] init];
//如果只開啟該代碼,person在111,222 之后釋放,調(diào)用dealloc
//person1 = person;
//如果只開啟該代碼,person會(huì)在111,222 中間釋放
//person2 = person;
//person3 = person;
}
NSLog(@``"222"``);
}
|
|
1
2
3
4
5
|
@implementation Person
- (``void``)dealloc{
NSLog(@``"%s"``, __func__);
}
@end
|
上述代碼如果開啟了person1 = person person 會(huì)在輸出111,222 之后釋放,調(diào)用dealloc;如果開啟了person2 = person person 會(huì)在111,222 中間釋放;如果開啟person3 = person,效果同第一種。
__strong是強(qiáng)引用,所以只有離開了viewDidLoad方法后 person 對象才被釋放。
所謂的 weak 指針原理是指:如何做到對象被銷毀之后,指向?qū)ο蟮?weak 指針立馬被清空,置位nil。
默認(rèn)是強(qiáng)引用。
weak 原理說明
一個(gè)對象可能會(huì)被多次弱引用,當(dāng)這個(gè)對象被銷毀時(shí),我們需要找到這個(gè)對象的所有弱引用,所以我們需要將這些弱引用的地址(即指針)放在一個(gè)容器里(比如數(shù)組)。當(dāng)對象不再被強(qiáng)引用時(shí)需要銷毀的時(shí)候,可以在SideTable中 通過這個(gè)對象的地址找到引用值,清空引用值。同時(shí), SideTable結(jié)構(gòu)中還有weak_table_t結(jié)構(gòu),該結(jié)構(gòu)也是一個(gè)散列表,key 為對象地址,value 為一個(gè)數(shù)組,里面保存著指向該對象的所有弱指針。當(dāng)對象釋放的時(shí)候,先清空引用哈希表RefcountMap對應(yīng)的引用值,遍歷弱指針數(shù)組,依次將各個(gè)弱指針置為 nil。
二十三、加鹽的意義
用戶設(shè)置的密碼復(fù)雜度可能不夠高,同時(shí)不同的用戶極有可能會(huì)使用相同的密碼,那么這些用戶對應(yīng)的密文也會(huì)相同,這樣,當(dāng)存儲(chǔ)用戶密碼的數(shù)據(jù)庫泄露后,攻擊者會(huì)很容易便能找到相同密碼的用戶,從而也降低了破解密碼的難度,因此,在對用戶密碼進(jìn)行加密時(shí),需要考慮對密碼進(jìn)行掩飾,即使是相同的密碼,也應(yīng)該要保存為不同的密文,即使用戶輸入的是弱密碼,也需要考慮進(jìn)行增強(qiáng),從而增加密碼被攻破的難度,而使用帶鹽的加密hash值便能滿足該需求。筆者實(shí)際項(xiàng)目開發(fā)中,為了網(wǎng)絡(luò)安全,請求參數(shù)按照一定的規(guī)則拼接成字符串,然后在字符串中加鹽,最后 MD5 簽名。后端依照同樣的規(guī)則校驗(yàn)簽名,若簽名值一致則通過校驗(yàn)。
二十四、Shell 腳本
二十五、什么是User Agent
User Agent中文名為用戶代理,簡稱 UA,它是一個(gè)特殊字符串頭,使得服務(wù)器能夠識(shí)別客戶使用的操作系統(tǒng)及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器插件等。網(wǎng)站在手機(jī)端 app 打開和直接在瀏覽器中打開看到的內(nèi)容可能不一樣,是因?yàn)榫W(wǎng)頁可以根據(jù) UA 判斷是 app 打開的還是瀏覽器打開的。
navigator 可以獲取到瀏覽器的信息:navigator.userAgent。webView中獲取 User Agent 方式如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
+(``void``)initialize{
if
([NSThread isMainThread]) {
[self getUserAgent];
}``else``{
dispatch_async(dispatch_get_main_queue(), ^{
[self getUserAgent];
});
}
}
+(``void``)getUserAgent{
UIWebView *webView = [[UIWebView alloc]initWithFrame:CGRectZero];
NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@``"navigator.userAgent"``];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@``"%@meicaiMallIOS"``,userAgent],@``"UserAgent"``,nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
}
|
二十六、JS和OC通信方式匯總
JS 調(diào) OC
JS 調(diào) OC ?目前主要的?方式有兩種:
通過 JSCore 中的 block
通過 JSCore 中的 JSExport
在 JS 執(zhí)?行行環(huán)境中添加?一個(gè) _OC_catch 的 block,那么在 JS 代碼中就可以直接調(diào)?用 _OC_catch 這 個(gè)函數(shù),當(dāng)在 JS 中調(diào)?用 _OC_catch 這個(gè)函數(shù)后,我們剛才注冊的 block 就會(huì)被執(zhí)行。也就是通過 JS 成功的調(diào)?了 OC 代碼。
|
1
2
3
|
context[@``"_OC_catch"``] = ^(JSValue *msg, JSValue *stack) {
};
|
JSExport 可以導(dǎo)出 Objective-C 的屬性、實(shí)例方法、類方法和初始化?方法到 JS 環(huán)境,這樣就可 以通過 JS 代碼直接調(diào)?用 Objective-C 。通過 JSExport 不僅可以導(dǎo)出?自定義類的方法、屬性,也可以導(dǎo)出已有類的?方法、屬性。在導(dǎo)出過程中,類的方法名會(huì)被轉(zhuǎn)換成 JS 類型命名,第二個(gè)參數(shù)的第一個(gè)字?會(huì)被大寫,比如- (void)addX:(int)x andY:(int)y;被轉(zhuǎn)為addXAndY(x, y)。除此,JSExport還可以導(dǎo)出已有類的?方法、屬性。
OC 調(diào) JS
OC 調(diào) JS 主要有 UIWebView 、WKWebView 和 JSCore 這三種?方式。? UIWebView 的方式其實(shí)可以看作是 JSCore 的?方式。
- JSCore 方式
|
1
2
3
4
|
// 要執(zhí)行的 JS 代碼,定義一個(gè) add 函數(shù)并執(zhí)?行行
NSString *addjs = @``"function add(a, b) {return a + b;};add(1,3)"``;
// sumValue 為執(zhí)?行行后的結(jié)果
JSValue *sumValue = [self.context evaluateScript:addjs];
|
-
UIWebView 方式
這種?方式說?白了了就是使?用 JSCore ,通過 UIWebView 來獲取 JSContext ,這樣直接通過獲取到 context 來執(zhí)?行行 JS 代碼。
|
1
2
3
4
5
6
7
|
//通過 UIWebView 獲取 context
JSContext *context = [_webView
valueForKeyPath:@``"documentView.webView.mainFrame.JSContext"``];
// 要執(zhí)行的 JS 代碼,定義一個(gè) add 函數(shù)并執(zhí)?行行
NSString *addjs = @``"function add(a, b) {return a + b;};add(1,3)"``;
// sumValue 為執(zhí)?行行后的結(jié)果
JSValue *sumValue = [self.context evaluateScript:addjs];
|
-
WKWebView 方式
WKWebView 沒有提供獲取 JSContext 的方法,但是它提供了執(zhí)行 JS 的方法 evaluateJS: ,通過下面方法來執(zhí)行 JS 代碼。
|
1
2
3
|
[self.webView evaluateJS:@``"function add(a, b) {return a + b;};add(1,3)"
completionHandler:^(id _Nullable msg, NSError * _Nullable error) {
NSLog(@``"evaluateJS add: %@, error: %@"``, msg, error);
}];
|
二十七、UIScrollView 原理
UIScrollView繼承自UIView,內(nèi)部有一個(gè) UIPanGestureRecongnizer手勢。 frame 是相對父視圖坐標(biāo)系來決定自己的位置和大小,而bounds是相對于自身坐標(biāo)系的位置和尺寸的。改視圖 bounds 的 origin 視圖本身沒有發(fā)生變化,但是它的子視圖的位置卻發(fā)生了變化,因?yàn)?bounds 的 origin 值是基于自身的坐標(biāo)系,當(dāng)自身坐標(biāo)系的位置被改變了,里面的子視圖肯定得變化, bounds 和 panGestureRecognize 是實(shí)現(xiàn) UIScrollView 滑動(dòng)效果的關(guān)鍵技術(shù)點(diǎn)。
二十八、--verbose 和 --no-repo-update
verbose意思為 冗長的、啰嗦的,一般在程序中表示詳細(xì)信息。此參數(shù)可以顯示命令執(zhí)行過程中都發(fā)生了什么。
pod install或pod update可能會(huì)卡在Analyzing dependencies步驟,因?yàn)檫@兩個(gè)命令會(huì)升級 CocoaPods 的 spec 倉庫,追加該參數(shù)可以省略此步驟,命令執(zhí)行速度會(huì)提升。
二十九、dataSource 和 delegate 的本質(zhì)區(qū)別
普遍開發(fā)者得理解是:一個(gè)是數(shù)據(jù),一個(gè)是操作。如果從數(shù)據(jù)傳遞方向的角度來看,兩者的本質(zhì)是數(shù)據(jù)傳遞的方向不同。dataSource 是外部將數(shù)據(jù)傳遞到視圖內(nèi),而 delegate 是將視圖內(nèi)的數(shù)據(jù)和操作等傳遞到外部。實(shí)際開發(fā)封裝自定義視圖,可以參照數(shù)據(jù)傳遞方向分別設(shè)置 dataSource 和 delegate。
三十、變種 MVC
真正的 MVC 應(yīng)該是蘋果提供的經(jīng)典UITableView的使用,實(shí)際開發(fā)中經(jīng)常在 Cell 中引入Model,本質(zhì)上來說不算是真正的 MVC ,只能算是 MVC 的變種。
三十一、函數(shù)指針和 Block
相同點(diǎn):
二者都可以看成是一個(gè)代碼片段。
函數(shù)指針類型和 Block 類型都可以作為變量和函數(shù)參數(shù)的類型(typedef定義別名之后,這個(gè)別名就是一個(gè)類型)。
不同點(diǎn):
- 函數(shù)指針只能指向預(yù)先定義好的函數(shù)代碼塊,函數(shù)地址是在編譯鏈接時(shí)就已經(jīng)確定好的。從內(nèi)存的角度看,函數(shù)指針只不過是指向代碼區(qū)的一段可執(zhí)行代碼,而 block 本質(zhì)是 OC對象,是 NSObject的子類,是程序運(yùn)行過程中在棧內(nèi)存動(dòng)態(tài)創(chuàng)建的對象,可以向其發(fā)送copy消息將block對象拷貝到堆內(nèi)存,以延長其生命周期。
補(bǔ)充:指針函數(shù)和函數(shù)指針的區(qū)別
指針函數(shù)是指帶指針的函數(shù),即本質(zhì)是一個(gè)函數(shù),函數(shù)返回類型是某一類型的指針。它是一個(gè)函數(shù),只不過這個(gè)函數(shù)的返回值是一個(gè)地址值。
|
1
|
int
*f(x,y);
|
函數(shù)指針是指向函數(shù)的指針變量,即本質(zhì)是一個(gè)指針變量。
|
1
2
|
int
(*f) (``int
x); ``/*聲明一個(gè)函數(shù)指針 */
f = func; ``/* 將func函數(shù)的首地址賦給指針f */
|
三十二、內(nèi)存(堆內(nèi)存)回收是什么意思
|
1
|
NSObject *obj = [[NSObject alloc] init];
|
代碼對應(yīng)的內(nèi)存布局如下,obj 指針存在于棧取,obj 對象存在于堆區(qū)。obj 指針的回收由棧區(qū)自動(dòng)管理,堆區(qū)的內(nèi)存需要開發(fā)者自己管理(MRC)情況。所謂的堆內(nèi)存回收并不是指將 obj 對象占有的內(nèi)存給挖去或是將空間數(shù)據(jù)清空為0,而是指 obj 對象原本占有的空間可以被其他人利用(即其他指針可以指向該空間)。其他指針指向該空間時(shí),重新初始化該空間,將空間原有數(shù)據(jù)清零。
三十三、IP 和 MAC
IP 是地址,有定位功能;MAC 是身份唯一標(biāo)識(shí),無定位功能;有了 MAC 地址為什么還要有 IP 地址?舉個(gè)例子,現(xiàn)在我要和你通信(寫信給你),地址用你的身份證號,信能送到你手上嗎? 明顯不能!身份證號前六位能定位你出生的縣,MAC 地址前幾位也可以定位生產(chǎn)廠家。但是你出生后會(huì)離開這個(gè)縣(IP 地址變動(dòng)),哪怕你還在這個(gè)縣,我總不能滿大街喊著你的身份證號去問路邊人是否認(rèn)識(shí)這個(gè)身份證號的主人,所以此刻需要借助 IP 的定位功能。
三十四、MD5 相關(guān)小知識(shí)
具體可參考筆者之前文章 iOS 簽名機(jī)制,文章中可以找到答案。
三十五、響應(yīng)鏈問題
這是文章前言的問題,請參考 這篇文章 。
三十六、什么是線程不安全?線程不安全的本質(zhì)原因?
不能確定代碼的運(yùn)行順序和結(jié)果,是線程不安全的。線程安全是相對于多線程而言的,單線程不會(huì)存在線程安全問題。因?yàn)閱尉€程代碼的執(zhí)行順序是唯一確定的,進(jìn)而可以確定代碼的執(zhí)行結(jié)果。
線程不安全的本質(zhì)原因在于:表面展現(xiàn)在我們眼前的可能是一行代碼,但轉(zhuǎn)換成匯編代碼后可能對應(yīng)多行。當(dāng)多個(gè)線程同時(shí)去訪問代碼資源時(shí),代碼的執(zhí)行邏輯就會(huì)發(fā)生混亂。如數(shù)據(jù)的寫操作,底層實(shí)現(xiàn)可能是先讀取,再在原有數(shù)據(jù)的基礎(chǔ)上改動(dòng)。如果此時(shí)有一個(gè)讀操作,原本意圖是想在寫操作完畢之后再讀取數(shù)據(jù),但不巧的這個(gè)讀操作剛好發(fā)生在寫操作執(zhí)行的中間步驟中。雖然讀操作后與寫操作執(zhí)行,但數(shù)據(jù)讀取的值并不是寫操作的結(jié)果值,運(yùn)氣不好時(shí)還可能發(fā)生崩潰。
|
1
2
3
4
5
6
|
- (``void``)viewDidLoad {
[``super
viewDidLoad];
int
a = ``100``;
a += ``200``;
NSLog(@``"%d"``,a);
}
|
如上述代碼中的int a = 100;和a += 200;轉(zhuǎn)換的匯編代碼,為下面中間八行匯編代碼。
|
1
2
3
4
5
6
7
8
9
10
|
0x1098e7621
: callq ``0x1098e7a32
; symbol stub ``for``: objc_msgSendSuper2
0x1098e7626
: leaq ``0x1a33``(%rip), %rax ; @``"%d"
0x1098e762d
: movl $``0x64``, -``0x24``(%rbp)
0x1098e7634
: movl -``0x24``(%rbp), %ecx
0x1098e7637
: addl $``0xc8``, %ecx
0x1098e763d
: movl %ecx, -``0x24``(%rbp)
-> ``0x1098e7640
: movl -``0x24``(%rbp), %esi
0x1098e7643
: movq %rax, %rdi
0x1098e7646
: movb $``0x0``, %al
0x1098e7648
: callq ``0x1098e7a14
; symbol stub ``for``: NSLog
|
三十七、App 啟動(dòng)流程
APP 啟動(dòng)分為冷啟動(dòng)和熱啟動(dòng),這里主要說下冷啟動(dòng)過程。冷啟動(dòng)分為三階段: dyld 階段、runtime階段、main函數(shù)階段,一般啟動(dòng)時(shí)間的優(yōu)化也是從這三大步著手。
dyld階段:dyld(dynamic link editor)是Apple的動(dòng)態(tài)鏈接器,可以用來裝載 Mach-O 文件(可執(zhí)行文件、動(dòng)態(tài)庫等)。啟動(dòng)APP時(shí),dyld 首先裝載可執(zhí)行文件,同時(shí)會(huì)遞歸加載所有依賴的動(dòng)態(tài)庫。
runtime 階段:首先解析可執(zhí)行文件,之后調(diào)用所有類和分類的+load方法,并進(jìn)行各種objc結(jié)構(gòu)的初始化(注冊O(shè)bjc類 、初始化類對象等等)。到此為止,可執(zhí)行文件和動(dòng)態(tài)庫中所有的符號(Class、Protocol、Selector、IMP …)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime 所管理。
main函數(shù)階段:所有初始化工作結(jié)束后,dyld就會(huì)調(diào)用main函數(shù)。
三十八、包體積優(yōu)化中的內(nèi)聯(lián)函數(shù)
在關(guān)于 App 包體積優(yōu)化的一些博客文章中,偶爾看到包體積的優(yōu)化可以從 C++ 入手,其中有一條是減少內(nèi)聯(lián)函數(shù)的使用。問題來了,什么是內(nèi)聯(lián)函數(shù)?為什么要減少內(nèi)聯(lián)函數(shù)的使用?它和一般函數(shù)有什么異同點(diǎn)?和宏相比有什么異同點(diǎn)?
內(nèi)聯(lián)函數(shù)關(guān)鍵字是 inline ,C++ 中普通函數(shù)使用的申明或?qū)崿F(xiàn)使用inline 修飾后,即為內(nèi)聯(lián)函數(shù)。注意:遞歸函數(shù)即使被 inline 修飾后也不是內(nèi)聯(lián)函數(shù),依然是普通函數(shù)。
|
1
2
3
|
inline ``int
sum(``int
a, ``int
b){
return
a + b;
}
|
普通函數(shù)調(diào)用會(huì)開辟一段棧空間執(zhí)行相關(guān)代碼,函數(shù)執(zhí)行完再將對應(yīng)的棧空間回收。而內(nèi)聯(lián)函數(shù)調(diào)用中,編譯器會(huì)將函數(shù)調(diào)用直接展開為函數(shù)代碼。如cout << sum(1, 2) << endl會(huì)直接轉(zhuǎn)換為cout << 1 + 2<< endl,由此可見內(nèi)聯(lián)函數(shù)和一般的宏很類似,都是直接替換相關(guān)代碼。同宏相比,內(nèi)聯(lián)函數(shù)只是多了一些函數(shù)特性和語法檢測功能。
綜上,內(nèi)聯(lián)函數(shù)或宏可以減少函數(shù)調(diào)用的開銷,但是會(huì)增加代碼體積,所以減少內(nèi)聯(lián)函數(shù)或宏的使用一定程度上可以減少包體積。但并不是說為了減小包體積完全不去使用內(nèi)聯(lián)函數(shù),建議經(jīng)常會(huì)被調(diào)用的代碼,且代碼量不是很多的時(shí)候(不超過10行),為減少函數(shù)調(diào)用的開銷,可適當(dāng)使用內(nèi)聯(lián)函數(shù)。
三十九、super 本質(zhì)
有兩個(gè)類 Animal 和 Cat ,其中 Cat 繼承自 Animal 類,在 Cat 類實(shí)現(xiàn)如下代碼,試問打印結(jié)果是什么?
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@implementation Cat
- (instancetype)init{
self = [``super
init];
if
(self) {
NSLog(@``"%@"``,[self ``class``]);``//Cat
NSLog(@``"%@"``,[self superclass]);``//Animal
NSLog(@``"%@"``,[``super
class``]);``//Cat
NSLog(@``"%@"``,[``super
superclass]);``//Animal
}
return
self;
}
@end
|
上述代碼打印結(jié)果一次為: Cat Animal Cat Animal,前兩個(gè)結(jié)果不足為奇,后兩個(gè)結(jié)果似乎有點(diǎn)費(fèi)解。
super 調(diào)用底層會(huì)轉(zhuǎn)換為objc_msgSendSuper函數(shù)的調(diào)用,objc_msgSendSuper 函數(shù)接收 2 個(gè)參數(shù) objc_super 結(jié)構(gòu)體和 SEL ,objc_super結(jié)構(gòu)如下:
|
1
2
3
4
|
struct objc_super {
__unsafe_unretained _Nonnull id receiver; ``// 消息接收者
__unsafe_unretained _Nonnull Class super_class; ``// 消息接收者的父類
};
|
[super class] 在調(diào)用過程中,底層轉(zhuǎn)化為 objc_msgSendSuper({self, [Animal class]}, @selector(class)); ,同 objc_msgSend 函數(shù)相比相當(dāng)于多了第二個(gè)參數(shù),但消息接收者仍然是 self ,所以打印結(jié)果為 Cat。
the superclass at which to start searching for the method implementation.
objc_msgSendSuper 方法中的第二個(gè)參數(shù)主要作用是告訴從哪里開始搜索方法實(shí)現(xiàn),一般傳入的是父類。這也是實(shí)際開發(fā)中 [super superClassMethod] 直接調(diào)用父類方法的原因。
四十、引用的本質(zhì)(引用和指針的區(qū)別)
待更新。。。。。。
四十一、渲染框架分類
說實(shí)在的有時(shí)會(huì)對各種渲染框架感覺混亂,一會(huì)CA、一會(huì)CG等等,于是就把這些渲染框架簡單匯總了下。
1、UIKit & AppKit :這個(gè)不多說。
2、Core Animation:UIView底下封裝了一層CALayer樹,Core Animation 層是真正的渲染層,我們之所以能在屏幕上看到內(nèi)容,真正的渲染工作是在 Core Animation 層的。
3、Core Graphics:用于運(yùn)行時(shí)繪制圖像。可以繪制路徑、顏色,當(dāng)開發(fā)者需要在運(yùn)行時(shí)創(chuàng)建圖像時(shí),可以使用 Core Graphics 去繪制。
4、** Core Image**:用來處理已經(jīng)創(chuàng)建的圖像。該框架擁有一系列現(xiàn)成的圖像過濾器,能對已存在的圖像進(jìn)行高效的處理。
5、SceneKit & SpriteKit:普通開發(fā)者可能會(huì)對 SceneKit 和 SpriteKit 感到陌生,SceneKit 主要用于某些 3D 場景需求,而 SpriteKit 更多的用于游戲開發(fā)。SceneKit 和 SpriteKit 都包含了粒子系統(tǒng)、物理引擎等,即使是非游戲應(yīng)用中也可以使用它們來完成一些比較炫酷的特效和物理模擬。
6、Metal:Metal 存在于以上渲染框架的最底層。Core Animation、Core Image、SceneKit、SpriteKit等等渲染框架都是構(gòu)建于 Metal 之上的。
四十二、耗時(shí)代碼定位
實(shí)際開發(fā)中可能會(huì)遇到嚴(yán)重線程阻塞的情況,比如筆者之前就遇到過使用 MJ 下拉刷新,刷新完畢后 MJ 復(fù)位無動(dòng)畫效果,第一猜測就是有阻塞,于是借助 Product --> Profile-->TimeProfiler 工具 第一時(shí)間定位到耗時(shí)較多的代碼。結(jié)果發(fā)現(xiàn)在渲染 Cell 的時(shí)候動(dòng)態(tài)的調(diào)用了蘋果接口中 html 轉(zhuǎn)屬性文本的方法,該方法的解析異常耗時(shí)。可按照下圖設(shè)置 Call Tree ,方便定位耗時(shí)代碼。
四十三、如何給百萬數(shù)據(jù)排序
桶排序定義
給百萬數(shù)據(jù)排序可以用"桶排序",核心思想是將數(shù)據(jù)分到幾個(gè)有序的桶,每個(gè)桶里的數(shù)據(jù)再單獨(dú)進(jìn)行排序。桶內(nèi)排完序之后,再把每個(gè)桶里的數(shù)據(jù)按照順序依次取出,組成的序列就是有序的了。
桶排序時(shí)間復(fù)雜度
如果要排序的數(shù)據(jù)為 n 個(gè),均勻地劃分到 m 個(gè)桶內(nèi),每個(gè)桶里就有 k = n/m 個(gè)元素。每個(gè)桶內(nèi)部使用快速排序,則每個(gè)桶內(nèi)時(shí)間復(fù)雜度為 O(k * logk)。m 個(gè)桶排序的時(shí)間復(fù)是 O(m * k * logk),因?yàn)?k = n/m,所以整個(gè)桶排序的時(shí)間復(fù)雜度就是 O(n*log(n/m))。當(dāng)桶的個(gè)數(shù) m 接近數(shù)據(jù)個(gè)數(shù) n 時(shí),log(n/m) 就是非常小的常量,這個(gè)時(shí)候桶排序的時(shí)間復(fù)雜度接近 O(n)。
桶排序缺點(diǎn)
桶排序?qū)σ判驍?shù)據(jù)的要求是非常苛刻的。
1、桶與桶之間有著大小順序。這樣每個(gè)桶內(nèi)的數(shù)據(jù)都排序完之后,桶與桶之間的數(shù)據(jù)不需要再進(jìn)行排序。
2、數(shù)據(jù)在各個(gè)桶之間的分布是比較均勻的。如果數(shù)據(jù)經(jīng)過桶的劃分之后,有些桶里的數(shù)據(jù)非常多,有些非常少,很不平均。時(shí)間復(fù)雜度就不是常量級了。極端情況下,如果數(shù)據(jù)都被劃分到一個(gè)桶里,那就退化為 O(nlogn) 的排序算法了。
內(nèi)存不足時(shí),如何排序?
假設(shè)有 10GB 的訂單數(shù)據(jù)需要排序,內(nèi)存有限,只有幾百 MB,沒辦法一次性把 10GB 的數(shù)據(jù)都加載到內(nèi)存中。先掃秒訂單知道金額最小是 1 元,最大是 10 萬元。可以將訂單劃到100個(gè)桶內(nèi),第一個(gè)桶我們存儲(chǔ)金額在 1 元到 1000 元之內(nèi)的訂單,第二桶存儲(chǔ)金額在 1001 元到 2000 元之內(nèi)的訂單,以此類推。但是訂單的數(shù)據(jù)分布可能并不是非常均勻,某些桶內(nèi)的數(shù)據(jù)依然是大于內(nèi)存空間,此時(shí)可以將該桶內(nèi)的數(shù)據(jù)再次進(jìn)行劃分,直到能加載到內(nèi)存為止。
四十四、自旋鎖 & 互斥鎖
線程安全中為了實(shí)現(xiàn)線程阻塞,一般有兩種方案:一種是讓線程處于休眠狀態(tài),此時(shí)不會(huì)消耗 CPU 資源;另一種方案是讓線程忙等或空轉(zhuǎn),此時(shí)會(huì)消耗一定的 CPU 資源。前者屬于互斥,后者屬于自旋。
自旋在線程加鎖的情況下,會(huì)一直嘗試是否解鎖,如果沒有解鎖,會(huì)一直循環(huán)判斷,如果鎖已經(jīng)放開,則繼續(xù)執(zhí)行,不再是空轉(zhuǎn)狀態(tài)。OSSpinLock 屬于自旋鎖,Pthred 庫中相關(guān)的鎖,以及 NSLock、@synchronized 等都屬于互斥鎖。OSSpinLock目前已經(jīng)不再安全,因?yàn)闀?huì)出現(xiàn)優(yōu)先級反轉(zhuǎn)問題。 現(xiàn)代操作系統(tǒng)一般采用 時(shí)間片輪轉(zhuǎn)算法 調(diào)度進(jìn)程或線程,按照線程的優(yōu)先級為不同的線程分配不同的時(shí)間,優(yōu)先級越高分配的時(shí)間片越多。假設(shè)有兩個(gè)線程 thread1 和 thread2,其中 thread1 的優(yōu)先級高于 thread2,即thread1 分配的時(shí)間片多余 thread2。如果 thread2 正在鎖內(nèi)安全執(zhí)行,一段時(shí)間后 thread1 執(zhí)行任務(wù)時(shí),發(fā)現(xiàn)鎖未打開,于是會(huì)處于忙等狀態(tài)。由于thread1 的優(yōu)先級高于 thread2,此時(shí)系統(tǒng)會(huì)分配更多的時(shí)間片給 thread1,thread2 時(shí)間片減少,遲遲不能完成,thread1 卻一直等待。如此就造成線程優(yōu)先級反轉(zhuǎn)。
四十五、應(yīng)用 Crash 時(shí)為什么對操作系統(tǒng)無影響?
雙模式、I/O 保護(hù)和內(nèi)存保護(hù)、定時(shí)器三者是確保操作系統(tǒng)能夠運(yùn)行的關(guān)鍵技術(shù),可以避免外界應(yīng)用崩潰對操作系統(tǒng)的影響。
-
雙模式
為了保證操作系統(tǒng)不受其它故障程序的影響,進(jìn)而產(chǎn)生系統(tǒng)崩潰的可能。一種常用的辦法是引入雙重模式,即用戶模式和內(nèi)核模式。內(nèi)核模式只能運(yùn)行操作系統(tǒng)的程序。所有的用戶應(yīng)用程序只能在用戶模式下運(yùn)行。 雙模式需要CPU的支持,如果CPU有模式位,則可以在操作系統(tǒng)中實(shí)現(xiàn)雙模式,目前主流的CPU基本都有模式位。雙模式允許操作系統(tǒng)不受其它故障應(yīng)用程序的影響。特權(quán)指令是指可能引起崩潰的指令,該指令只能運(yùn)行在內(nèi)核模式中。 如果用戶程序需要使用特權(quán)指令,可以通過系統(tǒng)提供的API調(diào)用。
-
I/O保護(hù)和內(nèi)存保護(hù)
定義所有I/O指令為特權(quán)指令,用戶應(yīng)用程序無法直接訪問I/O指令,只能通過系統(tǒng)調(diào)用進(jìn)行I/O操作,從而避免非法I/O操作。
利用基址寄存器和限長寄存器隔離不同程序的內(nèi)存地址。
-
定時(shí)器
如果用戶程序死循環(huán)或用戶程序不調(diào)用系統(tǒng)調(diào)用,此時(shí)操作系統(tǒng)將無法獲得CPU并對系統(tǒng)進(jìn)行管理。解決方法是引入定時(shí)器,在一段時(shí)間后發(fā)生中斷,將CPU控制權(quán)返回給操作系統(tǒng)。
四十六、硬盤重量會(huì)隨著存儲(chǔ)數(shù)據(jù)大小而變化嗎?
如果是磁盤重量不變,如果是 SSD硬盤(固態(tài)硬盤)會(huì)受到影響。
磁硬盤能存儲(chǔ)數(shù)據(jù)靠的是里面的磁鐵的方向改變。一個(gè)長條形狀的磁鐵有南極和北極兩個(gè)端,一種端代表0,另一端代表1,然后通過 01 不同的組合代表不同的意義,只要磁鐵足夠多,就能用它們排列的順序代表所有的信息,數(shù)據(jù)就是這樣存在磁硬盤中的。所以磁硬盤重量不會(huì)收存儲(chǔ)數(shù)據(jù)大小的影響。
SSD 內(nèi)部有上萬億個(gè)小單元,每個(gè)單元表示 0 還是 1,取決于這個(gè)單元里裝了多少個(gè)電子,比如裝進(jìn)去100個(gè)電子后,這個(gè)單元就代表 1 ,低于這個(gè)數(shù)值就代表 0。所以對SSD的重量會(huì)受到內(nèi)部電子的影響。一個(gè)電子是0.000000……9公斤,30個(gè)零。2TB的數(shù)據(jù)至少要用2×10^13個(gè)電子。質(zhì)量大約就是0.0000000000002公斤,12個(gè)零。
四十七、如何消除小數(shù)誤差
小數(shù)誤差的原因:
計(jì)算機(jī)之所以會(huì)出現(xiàn)運(yùn)算錯(cuò)誤的原因是因?yàn)橐恍┬?shù)無法轉(zhuǎn)換二進(jìn)制數(shù),例如 0.1 就無法用二進(jìn)制數(shù)正確表示。下圖說明了小數(shù)的二進(jìn)制小數(shù)表達(dá)方式,小數(shù)的表示方式和整數(shù)表示方式類似。
消除小數(shù)誤差:
把小數(shù)擴(kuò)大對應(yīng)的倍數(shù),轉(zhuǎn)成整數(shù)進(jìn)行計(jì)算。計(jì)算機(jī)在進(jìn)行小數(shù)計(jì)算時(shí)可能會(huì)出錯(cuò),但是在計(jì)算整數(shù)的時(shí)候,只要不超過可處理數(shù)值的范圍一定不會(huì)出現(xiàn)問題。
四十八、運(yùn)行時(shí)是否是 OC 的專利?
runtime 并非是 Objective-C 的專利,絕大多數(shù)語言都有這個(gè)概念,runtime 就是動(dòng)態(tài)庫(運(yùn)行時(shí)庫)的一部分。比如 C 語言中 glibc 動(dòng)態(tài)鏈接庫通常會(huì)被很多操作依賴,包括字符串處理(strlen、strcpy)、信號處理、socket、線程、IO、動(dòng)態(tài)內(nèi)存分配等等。由于每個(gè)程序都依賴于運(yùn)行時(shí)庫,這些庫一般都是動(dòng)態(tài)鏈接的。這樣一來,運(yùn)行時(shí)庫可以存儲(chǔ)在操作系統(tǒng)中,很多程序共享一個(gè)動(dòng)態(tài)庫,這樣就可以節(jié)省內(nèi)存占用空間和應(yīng)用程序大小。
補(bǔ)充:鏈接一般分為靜態(tài)鏈接和動(dòng)態(tài)鏈接。一般說的預(yù)編譯、編譯、匯編、鏈接,其中的鏈接是指靜態(tài)鏈接。所謂的動(dòng)態(tài)鏈接是指: 鏈接過程被推遲到運(yùn)行時(shí)再進(jìn)行。
四十九、線程保活
|
1
2
3
4
5
6
7
8
9
10
|
- (``void``)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@``"1"``);
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
- (``void``)test{
NSLog(@``"2"``);
}
|
直接執(zhí)行上述代碼,在輸出 1 之后,會(huì)直接崩潰。主要原因在于,執(zhí)行完 [thread start] 后,線程立馬被殺死。此時(shí)再次在線程中調(diào)用 test 方法會(huì)直接崩潰。 解決該問題的思路主要是保證線程的生命周期,即線程保活。AFN 中,異步網(wǎng)絡(luò)發(fā)起請求,請求回來之后,線程依然沒有被殺死,也是利用了線程保活技術(shù)。代碼如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
- (``void``)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@``"1"``);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
- (``void``)test{
NSLog(@``"2"``);
}
|
五十、包體積優(yōu)化總結(jié)
主要從以下四個(gè)方面作總結(jié)資源文件、源代碼、編譯參數(shù)配置以及蘋果自身優(yōu)化。
資源文件
1、檢測未使用的圖片:開源項(xiàng)目LSUnusedResources或腳本工具
2、圖片壓縮處理imageoptim。順便補(bǔ)充一些很多電商網(wǎng)站里面涉及大量的商品圖片,為了節(jié)省流量通常可以使用 webp 格式圖片,webp 格式圖片不僅僅體積小,還支持 gif 格式,可參考這里。
3、簡單的圖片可以使用代碼自動(dòng)生成。很類似的圖片,僅僅只有顏色不同,可以通過代碼處理圖片的顏色。
4、啟動(dòng)圖和偽啟動(dòng)圖不要直接在資源文件中保留兩份,偽啟動(dòng)圖容器可以通過代碼獲取啟動(dòng)圖資源。一次實(shí)際優(yōu)化過程中,在該點(diǎn)下手,包體積立馬減少了 4M 左右。
5、Xcode 中也會(huì)有一些圖片相關(guān)設(shè)置,Compress PNG Files 和 Remove Text Medadata From PNG Files。前者打包的時(shí)候自動(dòng)對圖片進(jìn)行無損壓縮,后者會(huì)移除 PNG 圖像名稱、作者、版權(quán)、創(chuàng)作時(shí)間、注釋等信息。
|
1
2
3
4
5
6
7
8
9
10
11
|
-(UIImage*)imageChangeColor:(UIColor*)color{
UIGraphicsBeginImageContextWithOptions(self.size, NO, ``0``.0f);``//獲取畫布
[color setFill];``//畫筆沾取顏色
CGRect bounds = CGRectMake(``0``, ``0``, self.size.width, self.size.height);
UIRectFill(bounds);
[self drawInRect:bounds blendMode:kCGBlendModeOverlay alpha:``1``.0f];``//繪制一次
[self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:``1``.0f];``//再繪制一次
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();``//獲取圖片
return
img;
}
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
+ (UIImage *)getLaunchImage{
CGSize viewSize = [UIScreen mainScreen].bounds.size;
NSString *viewOr = @``"Portrait"``;``//垂直
NSString *launchImage = nil;
NSArray *launchImages = [[[NSBundle mainBundle] infoDictionary] valueForKey:@``"UILaunchImages"``];
for
(NSDictionary *dict ``in
launchImages) {
CGSize imageSize = CGSizeFromString(dict[@``"UILaunchImageSize"``]);
if
(CGSizeEqualToSize(viewSize, imageSize) && [viewOr isEqualToString:dict[@``"UILaunchImageOrientation"``]]) {
launchImage = dict[@``"UILaunchImageName"``];
}
}
return
[UIImage imageNamed:launchImage];
}
|
源代碼
1、使用fui工具檢測沒用到的 import 代碼文件,因?yàn)檫\(yùn)行時(shí)的原因,刪除類之前做一下核對。
2、SameCodeFinder 可以在源代碼?文件中檢測到相同的 function, 過多的類似方法,可以將其抽離出來提取為工具類。
3、注意控制宏和內(nèi)聯(lián)函數(shù)。可看本文的第三十八個(gè)小知識(shí)點(diǎn)包體積優(yōu)化中的內(nèi)聯(lián)函數(shù)。
4、LinkMap 可以得出每個(gè)類或者庫所占用的空間大小(代碼段+數(shù)據(jù)段),方便開發(fā)者快速定位需要優(yōu)化的類或靜態(tài)庫。
5、OC 中項(xiàng)目中 Debug 代碼即使沒有使用,也沒有導(dǎo)入頭文件,依然會(huì)增加包體積,因?yàn)?OC 是基于運(yùn)行時(shí)機(jī)制,編譯器無法確定哪些代碼將來是否會(huì)使用。但是 Swift 不同 OC,Swift 是靜態(tài)的,在編譯階段編譯器優(yōu)化就可以去除無用代碼。
編譯參數(shù)配置
1、Optional Level-->Fastest,Smallest[-OS]:含義可以參照該篇文章 2.1 小節(jié)。
2、Link-Time Optimization : 它是 LLVM 編譯器的一個(gè)特性,用于在 link 中間代碼時(shí),對全局代碼進(jìn)行優(yōu)化。這個(gè)優(yōu)化是自動(dòng)完成的,因此不需要修改現(xiàn)有的代碼。蘋果使用了新的優(yōu)化方式 Incremental,大大減少了鏈接的時(shí)間。筆者在實(shí)際的項(xiàng)目開發(fā)中開啟這個(gè)配置后,包體積減少了 4 - 5M 左右。
3、Deployment Postprocessing、Strip Linked Product、Strip Debug Symbols During Copy、Symbols hidden by default 四者設(shè)置為 YES 后可以去掉不必要的符號信息,減少可執(zhí)行文件大小。但去除了符號信息之后我們就只能使用 dSYM 來進(jìn)行符號化了,所以需要將 Debug Information Format 修改為 DWARF with dSYM file。
其它、Dead Code Stripping(僅對靜態(tài)語言有效):刪除靜態(tài)鏈接的可執(zhí)行文件中未引用的代碼。Debug 設(shè)置為 NO, Release 設(shè)置為 YES 可減少可執(zhí)行文件大小。Xcode 默認(rèn)會(huì)開啟此選項(xiàng),C/C++/Swift 等靜態(tài)語言編譯器會(huì)在 link 的時(shí)候移除未使用的代碼,但是對于 Objective-C 等動(dòng)態(tài)語言是無效的。因?yàn)?Objective-C 是建立在運(yùn)行時(shí)上面的,底層暴露給編譯器的都是 Runtime 源碼編譯結(jié)果,所有的部分應(yīng)該都是會(huì)被判別為有效代碼。
其它、Generate Debug Symbols(有作用但不建議修改): 當(dāng) Generate Debug Symbol s選項(xiàng)設(shè)置為 YES時(shí),每個(gè)源文件在編譯成 .o 文件時(shí),編譯參數(shù)多了 -g 和 -gmodules 兩項(xiàng)。打包會(huì)生成 symbols 文件。設(shè)置為 NO 則 ipa 中不會(huì)生成 symbol 文件,可以減少 ipa 大小。但會(huì)影響到崩潰的定位。保持默認(rèn)的開啟,不做修改。
蘋果自身優(yōu)化
1、Slicing : 創(chuàng)建、分發(fā)不同變體以適應(yīng)不同目標(biāo)設(shè)備的過程,App Slicing 僅向設(shè)備傳送與之相關(guān)的資源(取決于屏幕分辨率,架構(gòu)等等)。如 2x 和 3x 的圖片放在 Asset Catalog 中會(huì)自動(dòng)管理僅保留合適的圖片。但 Bundle 內(nèi)則會(huì)同時(shí)包含2x 和 3x 。所以資源圖片盡可能放在 Asset Catalog 中。代碼資源會(huì)對應(yīng)不同的設(shè)備生成不同的執(zhí)行文件。如果用心的話,還會(huì)發(fā)現(xiàn) AppStrore 中同一款應(yīng)用在不同設(shè)備上顯示的包體積大小不同。
注意 : ****代碼架構(gòu)的拆分主要是由 Slicing 完成的,并非是 Bitcode 。Bitcode 的優(yōu)勢更多體現(xiàn)在性能、以及后續(xù)的維護(hù)上。如果開啟了 Bitcode,以后 Apple 推出了新的 CPU 架構(gòu)(不是指新iPhone設(shè)備)或者以后 LLVM 推出了一系列優(yōu)化,我們也不再需要為其發(fā)布新的安裝包了,Apple Store 會(huì)為我們自動(dòng)完成這步。