OC和Swift語(yǔ)言基礎(chǔ)
1、@synthesize和@dynamic分別有什么作用?
- @property有兩個(gè)對(duì)應(yīng)的詞细溅,一個(gè)是 @synthesize,一個(gè)是 @dynamic儡嘶。如果 @synthesize和 @dynamic都沒(méi)寫(xiě)喇聊,那么默認(rèn)的就是@syntheszie var = _var;
- @synthesize 的語(yǔ)義是如果你沒(méi)有手動(dòng)實(shí)現(xiàn) setter 方法和 getter 方法,那么編譯器會(huì)自動(dòng)為你加上這兩個(gè)方法蹦狂。
- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實(shí)現(xiàn)誓篱,不自動(dòng)生成朋贬。(當(dāng)然對(duì)于 readonly 的屬性只需提供 getter 即可)。假如一個(gè)屬性被聲明為 @dynamic var窜骄,然后你沒(méi)有提供 @setter方法和 @getter 方法锦募,編譯的時(shí)候沒(méi)問(wèn)題,但是當(dāng)程序運(yùn)行到 instance.var = someVar邻遏,由于缺 setter 方法會(huì)導(dǎo)致程序崩潰糠亩;或者當(dāng)運(yùn)行到 someVar = var 時(shí),由于缺 getter 方法同樣會(huì)導(dǎo)致崩潰准验。編譯時(shí)沒(méi)問(wèn)題赎线,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動(dòng)態(tài)綁定糊饱。
2垂寥、Swift和OC的區(qū)別?
- 快速另锋、現(xiàn)代矫废、安全、互動(dòng)砰蠢,而且明顯優(yōu)于 Objective-C 語(yǔ)言
- 可以使用現(xiàn)有的 Cocoa 和 Cocoa Touch 框架
- Swift 取消了 Objective C 的指針/地址等不安全訪問(wèn)的使用
- 提供了類(lèi)似 Java 的名字空間(namespace)蓖扑、泛型 - (generic)var、運(yùn)算對(duì)象重載(operator overloading
- Swift 被簡(jiǎn)單的形容為 “沒(méi)有 C 的 Objective-C”(Objective-C without the C)
- 為蘋(píng)果開(kāi)發(fā)工具帶來(lái)了Xcode Playgrounds功能台舱,該功能提供強(qiáng)大的互動(dòng)效果律杠,能讓Swift 源代碼在撰寫(xiě)過(guò)程中實(shí)時(shí)顯示出其運(yùn)行結(jié)果;
- 基于C和Objective-C竞惋,而卻沒(méi)有C的一些兼容約束柜去;
- 采用了安全的編程模式;
- 舍棄 Objective C 早期應(yīng)用 Smalltalk 的語(yǔ)法拆宛,保留了Smalltalk的動(dòng)態(tài)特性嗓奢,全面改為句點(diǎn)表示法
- 類(lèi)型嚴(yán)謹(jǐn) 對(duì)比oc的動(dòng)態(tài)綁定
3、Swift中struct和class的區(qū)別浑厚?
-
struct
值類(lèi)型股耽,深拷貝峻汉,分配在棧上
沒(méi)有析構(gòu)函數(shù)
不能繼承
不會(huì)發(fā)生內(nèi)存泄漏皮仁,線程安全
實(shí)例方法修改屬性時(shí)用mutating標(biāo)記 -
class
引用類(lèi)型熏瞄,淺拷貝错英,分配在堆上
有析構(gòu)函數(shù)
可以單繼承
可以有單例
無(wú)需mutating標(biāo)記
每一個(gè)成員變量都必須初始化
- class 直接對(duì)屬性賦值岩调,也就是沒(méi)有通過(guò)構(gòu)造器賦值的放妈,在創(chuàng)建對(duì)象對(duì)屬性賦值只能是如下方式:
class ClassPerson {
var name: String?
var age: Int?
}
struct StructPerson {
var name: String?
var age: Int?
}
let p1 = ClassPerson()
p1.name = "123"
print(p1.name)
var p2 = StructPerson(name: "abc", age: 20)
p2.name = "123"
print(p2.name)
原因: class 在初始化時(shí)不能直接把 property 放在默認(rèn)的 constructor 的參數(shù)里诡曙,而是需要自己創(chuàng)建一個(gè)帶參數(shù)的constructor
- struct是值類(lèi)型, class是引用類(lèi)型
class ClassPerson {
var name: String
var age: Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
struct StructPerson {
var name: String
var age: Int
}
let p1 = ClassPerson(name: "abc", age: 10)
let secondP1 = p1
secondP1.name = "123"
print(p1.name)
let p2 = StructPerson(name: "abc", age: 20)
var secondP2 = p2
secondP2.name = "123"
print(p2.name)
- 在struct的成員函數(shù)中修改自己本身的值谁不,應(yīng)該在函數(shù)簽名上加上mutating關(guān)鍵字,而class則沒(méi)有此限制
class ClassPerson {
var name: String
var age: Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
func changeName(){
self.name = self.name + "name"
}
}
struct StructPerson {
var name: String
var age: Int
mutating func changeName(){
self.name = self.name + "name"
}
}
let p1 = ClassPerson(name: "abc", age: 10)
print(p1.name)
p1.changeName()
print(p1.name)
var p2 = StructPerson(name: "abc", age: 20)
print(p2.name)
p2.changeName()
print(p2.name)
- struct初始化為let的對(duì)象無(wú)法修改,修改會(huì)編譯報(bào)錯(cuò)震嫉,而class沒(méi)有此限制
class ClassPerson {
var name: String?
var age: Int?
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
struct StructPerson {
var name: String?
var age: Int?
}
let p1 = ClassPerson(name: "abc", age: 10)
p1.name = "123"
print(p1.name)
let p2 = StructPerson(name: "abc", age: 20)
p2.name = "123"
print(p2.name)
- OC里面無(wú)法調(diào)用Swift里的struct森瘪,因?yàn)橐贠C里調(diào)用Swift代碼的話,對(duì)象需要繼承自NSObject票堵。
- struct不能被序列化成NSData柜砾,不能歸解檔,class可以换衬,因?yàn)闅w解檔的類(lèi)必須遵守NSCoding協(xié)議痰驱,而NSCoding只適用于繼承自NSObject的類(lèi),struct不能遵守NSCoding協(xié)議瞳浦。
解決方案:
定義一個(gè)protocol担映,包含兩個(gè)方法:
1.從結(jié)構(gòu)體中得到一個(gè)NSDictionary對(duì)象
2.使用一個(gè)NSDictionary對(duì)象實(shí)例化結(jié)構(gòu)體
NSDictionary可以使用NSKeyedArchiver進(jìn)行序列化
好處:所有遵守該協(xié)議的結(jié)構(gòu)體都可以被序列化
4、KVC實(shí)現(xiàn)原理叫潦?
KVC蝇完,鍵-值編碼,使用字符串直接訪問(wèn)對(duì)象的屬性矗蕊。
底層實(shí)現(xiàn)短蜕,當(dāng)一個(gè)對(duì)象調(diào)用setValue方法時(shí),方法內(nèi)部會(huì)做以下操作:
- 檢查是否存在相應(yīng)key的set方法傻咖,如果存在朋魔,就調(diào)用set方法
- 如果set方法不存在,就會(huì)查找與key相同名稱(chēng)并且?guī)聞澗€的成員屬性卿操,如果有警检,則直接給成員屬性賦值
- 如果沒(méi)有找到_key,就會(huì)查找相同名稱(chēng)的屬性key害淤,如果有就直接賦值
- 如果還沒(méi)找到扇雕,則調(diào)用
valueForUndefinedKey:
和setValue:forUndefinedKey:
方法
5、KVO的實(shí)現(xiàn)原理窥摄?
- 當(dāng)給A類(lèi)添加KVO的時(shí)候镶奉,runtime動(dòng)態(tài)的生成了一個(gè)子類(lèi)NSKVONotifying_A,讓A類(lèi)的isa指針指向NSKVONotifying_A類(lèi)崭放,重寫(xiě)class方法哨苛,隱藏對(duì)象真實(shí)類(lèi)信息
- 重寫(xiě)監(jiān)聽(tīng)屬性的setter方法,在setter方法內(nèi)部調(diào)用了Foundation 的
_NSSetObjectValueAndNotify
函數(shù) - _NSSetObjectValueAndNotify函數(shù)內(nèi)部
a) 首先會(huì)調(diào)用willChangeValueForKey
b) 然后給屬性賦值
c) 最后調(diào)用didChangeValueForKey
d) 最后調(diào)用 observer 的observeValueForKeyPath
去告訴監(jiān)聽(tīng)器屬性值發(fā)生了改變 . - 重寫(xiě)了dealloc做一些 KVO 內(nèi)存釋放
6莹菱、如何手動(dòng)觸發(fā)KVO方法移国?
- 手動(dòng)調(diào)用
willChangeValueForKey
和didChangeValueForKey
方法 - 鍵值觀察通知依賴(lài)于 NSObject 的兩個(gè)方法:
willChangeValueForKey:
和didChangeValueForKey:
。在一個(gè)被觀察屬性發(fā)生改變之前道伟,willChangeValueForKey:
一定會(huì)被調(diào)用,這就會(huì)記錄舊的值。而當(dāng)改變發(fā)生后蜜徽,didChangeValueForKey :
會(huì)被調(diào)用祝懂,繼而observeValueForKey:ofObject:change:context:
也會(huì)被調(diào)用。
7拘鞋、為什么Block用copy關(guān)鍵字砚蓬?
- Block在沒(méi)有使用外部變量時(shí),內(nèi)存存在全局區(qū)盆色,然而灰蛙,當(dāng)Block在使用外部變量的時(shí)候,內(nèi)存是存在于棧區(qū)隔躲,當(dāng)Block copy之后摩梧,是存在堆區(qū)的。存在于棧區(qū)的特點(diǎn)是對(duì)象隨時(shí)有可能被銷(xiāo)毀宣旱,一旦銷(xiāo)毀在調(diào)用的時(shí)候仅父,就會(huì)造成系統(tǒng)的崩潰。所以Block要用copy關(guān)鍵字浑吟。
8笙纤、 weak和assign的區(qū)別,什么場(chǎng)景下使用组力,代理為什么使用weak省容?
- weak是弱指針, 在對(duì)象被銷(xiāo)毀的時(shí)候會(huì)把weak修飾的屬性置為空燎字,避免造成野指針蓉冈,只能修飾對(duì)象類(lèi)型。
- assign對(duì)象被釋放的時(shí)候不會(huì)指向nil轩触,對(duì)象被釋放了還是指向原來(lái)的地址寞酿。調(diào)用的話容易產(chǎn)生野指針。assign可以修對(duì)象和基本數(shù)據(jù)類(lèi)型脱柱。
- 代理要使用weak伐弹,weak可以說(shuō)是非持有關(guān)系,對(duì)象釋放了就指向nil榨为,什么時(shí)候釋放是由外部來(lái)控制惨好,可以用assign但是用assign的時(shí)需要對(duì)象被釋放的時(shí)候,把delegate指向nil随闺。
9日川、load和initialize的區(qū)別
- load方法的本質(zhì):直接執(zhí)行函數(shù)指針,其實(shí)就是直接執(zhí)行函數(shù)指針矩乐,不會(huì)執(zhí)行消息發(fā)送objc_msgSend那一套流程龄句。子類(lèi)回论、分類(lèi)的load方法不會(huì)覆蓋父類(lèi)的load方法。
static void schedule_class_load(class_t *cls)
{
assert(isRealized(cls)); // _read_images should realize
if (cls->data->flags & RW_LOADED) return;
//確保先將父類(lèi)添加到全局列表里 (loadable_class)
class_t *supercls = getSuperclass(cls);
if (supercls) schedule_class_load(supercls);
//再將當(dāng)前類(lèi)添加到全局列表里 (loadable_class)
add_class_to_loadable_list((Class)cls);
changeInfo(cls, RW_LOADED, 0);
}
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
IMP load_method = classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
}
(*load_method) ((id) cls, SEL_load);
}
場(chǎng)景1 :子類(lèi)分歇、父類(lèi)傀蓉、分類(lèi)都實(shí)現(xiàn)load方法,調(diào)用情況
答:SuperClass->SubClass->CategoryClass場(chǎng)景2 :子類(lèi)职抡、父類(lèi)葬燎、分類(lèi)中子類(lèi)不實(shí)現(xiàn)load方法,調(diào)用情況
答:SuperClass->CategoryClass場(chǎng)景3 :子類(lèi)缚甩、父類(lèi)谱净、分類(lèi)1、分類(lèi)2都實(shí)現(xiàn)load方法擅威,調(diào)用情況
答:SuperClass->SubClass->Category1Class->Category2Classinitialize方法的本質(zhì)
在類(lèi)壕探、或者子類(lèi),接收到第一條消息之前被執(zhí)行(如初始化)
initialize方法最終通過(guò)objc_msgSend來(lái)執(zhí)行
initialize方法在main函數(shù)之后調(diào)用
如果一直沒(méi)有使用類(lèi)裕寨,則initialize方法不會(huì)被調(diào)用
如果子類(lèi)沒(méi)有實(shí)現(xiàn)initialize方法浩蓉,則會(huì)調(diào)用父類(lèi)的initialize方法。
__private_extern__ void _class_initialize(Class cls)
{
Class supercls;
BOOL reallyInitialize = NO;
// Get the real class from the metaclass. The superclass chain
// hangs off the real class only.
cls = _class_getNonMetaClass(cls);
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = _class_getSuperclass(cls);
if (supercls && !_class_isInitialized(supercls)) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
_class_setInitializing(cls);
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
_class_getName(cls));
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
_class_getName(cls));
}
// Done initializing.
......
}
優(yōu)先執(zhí)行父類(lèi)的initialize方法宾袜;通過(guò)_class_getSupercass取出父類(lèi)捻艳,遞歸調(diào)用父類(lèi)的initialize方法;initialize方法最終通過(guò)objc_msgSend來(lái)執(zhí)行的庆猫。
- 場(chǎng)景1 :子類(lèi)认轨、父類(lèi)都實(shí)現(xiàn)initialize方法,調(diào)用情況
答:SuperClass->SubClass - 場(chǎng)景2 :子類(lèi)月培、父類(lèi)中子類(lèi)不實(shí)現(xiàn)initialize方法嘁字,調(diào)用情況
答:SuperClass->SuperClass(子類(lèi)未實(shí)現(xiàn),則會(huì)調(diào)用父類(lèi)的initialize杉畜,導(dǎo)致父類(lèi)調(diào)用多次) - 場(chǎng)景3:子類(lèi)纪蜒、父類(lèi)、子類(lèi)分類(lèi)都實(shí)現(xiàn)initialize方法此叠,調(diào)用情況
答:SuperClass->CategoryClass(category中initialize方法覆蓋其本類(lèi)) - 場(chǎng)景4:子類(lèi)纯续、父類(lèi)、父類(lèi)分類(lèi)1灭袁、父類(lèi)分類(lèi)2都實(shí)現(xiàn)initialize方法猬错,調(diào)用情況
答:CategoryClass->SubClass(category中initialize方法根據(jù)Compile Sources排序執(zhí)行最后一個(gè))
執(zhí)行順序
load
app啟動(dòng)自動(dòng)加載所有l(wèi)oad方法,load方法會(huì)在程序運(yùn)行前加載一次茸歧。
1.先調(diào)用類(lèi)的load倦炒,再調(diào)用分類(lèi)的load
2.先編譯的類(lèi),優(yōu)先調(diào)用load软瞎,調(diào)用子類(lèi)的load之前逢唤,會(huì)先調(diào)用父類(lèi)的load
3.先編譯的分類(lèi)拉讯,優(yōu)先調(diào)用load,順序和Compile Sources中順序一致
initialize
initialize方法會(huì)在類(lèi)或者子類(lèi)在 第一次使用的時(shí)候調(diào)用智玻,當(dāng)有分類(lèi)的時(shí)候會(huì)調(diào)用多次遂唧,如Son *s = [[Son alloc]init];
1.父類(lèi)先于子類(lèi)執(zhí)行芙代;(同load方法)
2.子類(lèi)未實(shí)現(xiàn)吊奢,則會(huì)調(diào)用父類(lèi)的initialize方法;
3.分類(lèi)實(shí)現(xiàn)了initialize方法纹烹,則會(huì)覆蓋類(lèi)中的initialize方法(同category)页滚;
4.存在多個(gè)分類(lèi),依賴(lài)Compile Sources中的順序铺呵,執(zhí)行最后一個(gè)分類(lèi)的initialize方法(同category)裹驰;
使用場(chǎng)景
1.load通常用于Method Swizzle;
2.initialize可以用于初始化全局變量或靜態(tài)變量片挂;initialize方法可能被其分類(lèi)中的initialize方法覆蓋幻林,導(dǎo)致無(wú)法調(diào)用。
注意:load和initialize方法內(nèi)部使用了鎖音念,因此他們是線程安全的沪饺。使用時(shí)避免阻塞線程,不要使用線程鎖闷愤。
10整葡、如何理解copy-on-write?
蘋(píng)果建議當(dāng)復(fù)制大的值類(lèi)型數(shù)據(jù)的時(shí)候讥脐,使用寫(xiě)時(shí)復(fù)制技術(shù)遭居,那什么是寫(xiě)時(shí)復(fù)制呢?我們現(xiàn)在看一段代碼:
值類(lèi)型(比如:struct),在復(fù)制時(shí),復(fù)制對(duì)象與原對(duì)象實(shí)際上在內(nèi)存中指向同一個(gè)對(duì)象,當(dāng)且僅當(dāng)修改復(fù)制的對(duì)象時(shí),才會(huì)在內(nèi)存中創(chuàng)建一個(gè)新的對(duì)象
為了提升性能旬渠,Struct, String俱萍、Array、Dictionary告丢、Set采取了Copy On Write的技術(shù)
比如僅當(dāng)有“寫(xiě)”操作時(shí)枪蘑,才會(huì)真正執(zhí)行拷貝操作
對(duì)于標(biāo)準(zhǔn)庫(kù)值類(lèi)型的賦值操作,Swift 能確保最佳性能芋齿,所有沒(méi)必要為了保證最佳性能來(lái)避免賦值
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
array2.append(4)
print(address: array2) //0x6000000aa100
我們看到當(dāng)array2的值沒(méi)有發(fā)生變化的時(shí)候腥寇,array1和array2指向同一個(gè)地址,但是當(dāng)array2的發(fā)生變化時(shí)觅捆,array2指向地址也變了赦役,很奇怪是吧。
UI
1栅炒、UIView和CALayer的區(qū)別和聯(lián)系掂摔?
- UIView 繼承 UIResponder术羔,而 UIResponder 是響應(yīng)者對(duì)象,可以對(duì)iOS 中的事件響應(yīng)及傳遞乙漓,CALayer 沒(méi)有繼承自 UIResponder级历,所以 CALayer 不具備響應(yīng)處理事件的能力。CALayer 是 QuartzCore 中的類(lèi)叭披,是一個(gè)比較底層的用來(lái)繪制內(nèi)容的類(lèi)寥殖,用來(lái)繪制UI
- UIView 對(duì) CALayer 封裝屬性,對(duì) UIView 設(shè)置 frame涩蜘、center嚼贡、bounds 等位置信息時(shí),其實(shí)都是UIView 對(duì) CALayer 進(jìn)一層封裝同诫,使得我們可以很方便地設(shè)置控件的位置粤策;例如圓角、陰影等屬性误窖, UIView 就沒(méi)有進(jìn)一步封裝叮盘,所以我們還是需要去設(shè)置 Layer 的屬性來(lái)實(shí)現(xiàn)功能。
- UIView 是 CALayer 的代理霹俺,UIView 持有一個(gè) CALayer 的屬性柔吼,并且是該屬性的代理,用來(lái)提供一些 CALayer 行的數(shù)據(jù)吭服,例如動(dòng)畫(huà)和繪制嚷堡。
2、談?wù)剬?duì)UIResponder的理解艇棕?
UIResponder類(lèi)是專(zhuān)門(mén)用來(lái)響應(yīng)用戶的操作處理各種事件的蝌戒,包括觸摸事件(Touch Events)、運(yùn)動(dòng)事件(Motion Events)沼琉、遠(yuǎn)程控制事件(Remote Control Events)北苟。我們知道UIApplication、UIView打瘪、UIViewController這幾個(gè)類(lèi)是直接繼承自UIResponder友鼻,所以這些類(lèi)都可以響應(yīng)事件。當(dāng)然我們自定義的繼承自UIView的View以及自定義的繼承自UIViewController的控制器都可以響應(yīng)事件闺骚。
- 響應(yīng)過(guò)程
iOS系統(tǒng)檢測(cè)到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象彩扔,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來(lái)處理僻爽,UIWindow對(duì)象首先會(huì)使用hitTest:withEvent:
方法尋找此次Touch操作初始點(diǎn)所在的視圖(View)虫碉,即需要將觸摸事件傳遞給其處理的視圖(最合適來(lái)處理的控件),這個(gè)過(guò)程稱(chēng)之為hit-test view胸梆。
那么什么是最適合來(lái)處理事件的控件?
1.自己能響應(yīng)觸摸事件
2.觸摸點(diǎn)在自己身上
3.從后往前遞歸遍歷子控件, 重復(fù)上兩步
4.如果沒(méi)有符合條件的子控件, 那么就自己最合適處理
-
hitTest:withEvent:
事件傳遞給控件的時(shí)候敦捧, 就會(huì)調(diào)用該方法须板,去尋找最合適的view并返回看可以響應(yīng)的view
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.如果控件不允許與用用戶交互,那么返回nil
if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES){
return nil;
}
// 2\. 如果點(diǎn)擊的點(diǎn)在不在當(dāng)前控件中,返回nil
if (![self pointInside:point withEvent:event]){
return nil;
}
// 3.從后往前遍歷每一個(gè)子控件
for(int i = (int)self.subviews.count - 1 ; i >= 0 ;i--){
// 3.1獲取一個(gè)子控件
UIView *childView = self.subviews[i];
// 3.2當(dāng)前觸摸點(diǎn)的坐標(biāo)轉(zhuǎn)換為相對(duì)于子控件觸摸點(diǎn)的坐標(biāo)
CGPoint childP = [self convertPoint:point toView:childView];
// 3.3判斷是否在在子控件中找到了更合適的子控件(遞歸循環(huán))
UIView *fitView = [childView hitTest:childP withEvent:event];
// 3.4如果找到了就返回
if (fitView) {
return fitView;
}
}
// 4.沒(méi)找到,表示沒(méi)有比自己更合適的view,返回自己
return self;
}
-
pointInside:withEvent:
該方法判斷觸摸點(diǎn)是否在控件身上,是則返回YES兢卵,否則返回NO习瑰,point參數(shù)必須是方法調(diào)用者的坐標(biāo)系.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return NO;
}
3、loadView的作用秽荤?
loadView方法會(huì)在每次訪問(wèn)UIViewController的view(比如controller.view甜奄、self.view)而且view為nil時(shí)會(huì)被調(diào)用,此方法主要用來(lái)負(fù)責(zé)創(chuàng)建UIViewController的view(重寫(xiě)loadView方法王滤,并且不需要調(diào)用[super loadView])
[super loadView]執(zhí)行流程:
- 它會(huì)先去查找與UIViewController相關(guān)聯(lián)的xib文件贺嫂,通過(guò)加載xib文件來(lái)創(chuàng)建UIViewController的view滓鸠,如果在初始化UIViewController指定了xib文件名雁乡,就會(huì)根據(jù)傳入的xib文件名加載對(duì)應(yīng)的xib文件,如果沒(méi)有明顯地傳xib文件名糜俗,就會(huì)加載跟UIViewController同名的xib文件
- 如果沒(méi)有找到相關(guān)聯(lián)的xib文件踱稍,就會(huì)創(chuàng)建一個(gè)空白的UIView,然后賦值給UIViewController的view屬性
- 綜上悠抹,在需要自定義UIViewController的view時(shí)珠月,可以通過(guò)重寫(xiě)loadView方法且不需要調(diào)用[super loadView]方法。
內(nèi)存管理
RunLoop
1楔敌、RunLoop 的本質(zhì)是什么啤挎?
“Run loops are part of thefundamental infrastructure associated withthreads. A run loop is an event processing loopthat you use to schedule work and coordinatethe receipt of incoming events. The purpose ofa runloop is to keep your thread busy whenthere is work to do and put your thread tosleep when there is none.”
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
//mach_port
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
1.NSRunLoop只是比CFRunLoop多了一層簡(jiǎn)單的OC封裝,底層還是CFRunLoop卵凑,CFRunLoop本質(zhì)是一個(gè)結(jié)構(gòu)體庆聘,而NSRunLoop是一個(gè)NSObject對(duì)象。NSRunLoop存在于Foundation框架中勺卢,CFRunLoop是存在于CoreFoundation框架中的伙判。NSRunLoop不是線程安全的,CFRunLoop時(shí)候線程安全的黑忱。
2.RunLoop是一個(gè)與線程相關(guān)的底層機(jī)制宴抚,用來(lái)接收事件和調(diào)度任務(wù)。runloop目的是讓線程在有工作的時(shí)候保持忙碌甫煞,在沒(méi)有工作的時(shí)候睡眠菇曲。
3.RunLoop是與線程相關(guān)的,它們的關(guān)系一一對(duì)應(yīng):一個(gè)線程只能對(duì)應(yīng)一個(gè)RunLoop抚吠,即在某一時(shí)刻常潮,一個(gè)線程只能運(yùn)行在某一個(gè)RunLoop上。當(dāng)運(yùn)行一個(gè)應(yīng)用程序的時(shí)候埃跷,系統(tǒng)會(huì)為應(yīng)用程序的主線程創(chuàng)建一個(gè)RunLoop用來(lái)處理主線程上的事件蕊玷,例如UI刷新和觸屏事件邮利。因此猿推,開(kāi)發(fā)者不需要為主線程顯式地創(chuàng)建和運(yùn)行一個(gè)RunLoop胯府,而子線程需要顯式地運(yùn)行一個(gè)RunLoop,再將輔助線程放到RunLoop中運(yùn)行荔燎,否則線程不會(huì)自動(dòng)開(kāi)啟RunLoop贸诚。
2方庭、Runloop和線程是什么關(guān)系?
線程和 RunLoop 之間是Key-value的對(duì)應(yīng)關(guān)系酱固,是保存在一個(gè)全局的 Dictionary 里械念,線程是key,RunLoop是value运悲,而且是懶加載的龄减。
3、Runloop的底層數(shù)據(jù)結(jié)構(gòu)是什么樣的班眯?有幾種運(yùn)行模式(mode)希停?每個(gè)運(yùn)行模式下面的CFRunloopMode是哪些?他們分別是什么職責(zé)署隘?
- Mode宠能,運(yùn)行模式
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // mode名稱(chēng)
Boolean _stopped; // mode是否被終止
char _padding[3];
// 幾種事件,下面這四個(gè)字段磁餐,在蘋(píng)果官方文檔里面稱(chēng)為Item
// RunLoop中有個(gè)commomitems字段违崇,里面就是保存的下面這些內(nèi)容
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要監(jiān)聽(tīng)的port诊霹,比如_wakeUpPort羞延,_timerPort都保存在這個(gè)數(shù)組中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
模式(Mode)指的是一個(gè)包括輸入源(Inputsource)、定時(shí)器(Timer)畅哑、觀察者(Observer)的模型對(duì)象肴楷。簡(jiǎn)單點(diǎn)來(lái)說(shuō),模式就是用來(lái)存儲(chǔ)runloop需要響應(yīng)的事件荠呐,這些事件包括許多輸入源赛蔫、定時(shí)器和觀察者。
系統(tǒng)默認(rèn)注冊(cè)5個(gè)Mode:
1.NSDefaultRunLoopMode: App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
2.UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView`追蹤觸摸滑動(dòng)泥张,保證界面滑動(dòng)時(shí)不受其他Mode影響呵恢。
3.NSRunLoopCommonModes:并不是一個(gè)真的模式,它只是一個(gè)標(biāo)記媚创,如:被標(biāo)記的 Timer可以在Default模式和UITracking下運(yùn)行渗钉。
4.UIInitializationRunLoopMode:私有的mode,App啟動(dòng)的時(shí)候的狀態(tài),加載出第一個(gè)頁(yè)面后鳄橘,就轉(zhuǎn)成了Default声离,通常用不到
5.GSEventReceiveRunLoopMode:系統(tǒng)的內(nèi)部 Mode,通常用不到
- Source瘫怜,輸入源/事件源
//CFRunLoop.h
typedef struct __CFRunLoopSource *CFRunLoopSourceRef;
//CFRunLoop.c
struct __CFRunLoopSource{
CFRuntimeBase _base;//
uint32_t _bits;
pthread_mutex_t lock;
CFIndex _order; /*immutable*/
CFMutableBagRef _runLoops;
union{
CFRunLoopSourceContext version0; /*immutable,except invalidation*/
CFRunLoopSourceContext1 version1; /*immutable,except invalidattion*/
}_context;
};
1.聯(lián)合體的作用是共享存儲(chǔ)空間术徊,也就是說(shuō),version0和version1兩個(gè)變量共享一段存儲(chǔ)空間鲸湃,一個(gè)__CFRunLoopSource結(jié)構(gòu)體變量要么對(duì)應(yīng)version0類(lèi)型的事件源赠涮,要么對(duì)應(yīng)version1類(lèi)型的事件源。其中暗挑,version0和version1分別在源碼中對(duì)應(yīng)事件源Source0和Source1笋除。
2.Source0對(duì)應(yīng)需要手動(dòng)觸發(fā)的事件,對(duì)應(yīng)官方文檔Input Source中的Custom和performSelector:onThread事件源炸裆。
3.Source1表示基于端口觸發(fā)的事件垃它,對(duì)應(yīng)官方文檔Input Source中Port的事件源。
- Timer晒衩,定時(shí)源
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; //標(biāo)記fire狀態(tài)
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加該timer的runloop
CFMutableSetRef _rlModes; //存放所有 包含該timer的 mode的 modeName嗤瞎,意味著一個(gè)timer可能會(huì)在多個(gè)mode中存在
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; //理想時(shí)間間隔 /* immutable */
CFTimeInterval _tolerance; //時(shí)間偏差 /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
__CFRunLoopTimer是一個(gè)基于mk_timer實(shí)現(xiàn)的定時(shí)器,通過(guò)_callout回調(diào)實(shí)現(xiàn)定時(shí)執(zhí)行任務(wù)听系。NSTimer其實(shí)是對(duì)CFRunLoopTimerRef的一個(gè)上層封裝。
- Observer虹菲,觀察者
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加該Observer的RunLoop
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; //設(shè)置回調(diào)函數(shù)靠胜,回調(diào)指針 /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserver是觀察者,可以觀察RunLoop的各種狀態(tài)毕源,每個(gè) Observer 都包含了一個(gè)回調(diào)(也就是上面的CFRunLoopObserverCallBack函數(shù)指針)浪漠,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過(guò)回調(diào)接受到這個(gè)變化霎褐。
4址愿、Runloop 的監(jiān)聽(tīng)狀態(tài)有哪幾種?
Entry->BeforeTimers->BeforeSources->BeforeWaiting(休眠)->AfterWaiting(喚醒)->Exit->AllActivities
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有狀態(tài)
};
1)kCFRunLoopEntry表示剛進(jìn)入runloop的時(shí)候冻璃。
2)kCFRunLoopBeforeTimers表示將要處理timer响谓。
3)kCFRunLoopBeforeSources表示將要處理Source。
4)kCFRunLoopBeforeWaiting表示將要進(jìn)入休眠狀態(tài)省艳。
5)kCFRunLoopAfterWaiting表示將要從休眠狀態(tài)進(jìn)入喚醒狀態(tài)娘纷。
6)kCFRunLoopExit表示退出狀態(tài)。
7)kCFRunLoopAllActivities表示所有1)~6)中的狀態(tài)跋炕。
5赖晶、Runloop 的工作流程?
內(nèi)部邏輯:
- 通知 Observer 已經(jīng)進(jìn)入了 RunLoop
- 通知 Observer 即將處理 Timer
- 通知 Observer 即將處理非基于端口的輸入源(即將處理 Source0)
- 處理那些準(zhǔn)備好的非基于端口的輸入源(處理 Source0)
- 如果基于端口的輸入源準(zhǔn)備就緒并等待處理辐烂,請(qǐng)立刻處理該事件遏插。轉(zhuǎn)到第 9 步(處理 Source1)
- 通知 Observer 線程即將休眠
- 將線程置于休眠狀態(tài)捂贿,直到發(fā)生以下事件之一
- 事件到達(dá)基于端口的輸入源(port-based input sources)(也就是 Source0)
- Timer 到時(shí)間執(zhí)行
- 外部手動(dòng)喚醒
- 為 RunLoop 設(shè)定的時(shí)間超時(shí)
- 通知 Observer 線程剛被喚醒(還沒(méi)處理事件)
- 處理待處理事件
- 如果是 Timer 事件,處理 Timer 并重新啟動(dòng)循環(huán)胳嘲,跳到第 2 步
- 如果輸入源被觸發(fā)眷蜓,處理該事件(文檔上是 deliver the event)
- 如果 RunLoop 被手動(dòng)喚醒但尚未超時(shí),重新啟動(dòng)循環(huán)胎围,跳到第 2 步
6吁系、Runloop 有哪些應(yīng)用?
滑動(dòng)scrollview時(shí)候的mode切換白魂,cell的圖片下載 將多個(gè)耗時(shí)操作分開(kāi)執(zhí)行汽纤,在每次 RunLoop喚醒時(shí)去做一個(gè)耗時(shí)任務(wù)。
7福荸、Runloop的內(nèi)核態(tài)和用戶態(tài)蕴坪?
- 用戶態(tài)->內(nèi)核態(tài) 沒(méi)有消息需要處理時(shí),休眠以避免資源占用敬锐;
- 內(nèi)核態(tài)->用戶態(tài) 有消息需要處理時(shí)背传,立刻被喚醒。
8台夺、點(diǎn)擊APP圖標(biāo)径玖,從程序啟動(dòng)、運(yùn)行颤介、退出這個(gè)過(guò)程當(dāng)中梳星,系統(tǒng)都發(fā)生了什么?
- 程序啟動(dòng)后滚朵,調(diào)用
main
函數(shù)后冤灾,會(huì)調(diào)用UIApplicationmain
函數(shù),此函數(shù)內(nèi)部會(huì)啟動(dòng)主線程的RunLoop辕近,經(jīng)過(guò)一系列處理韵吨,最終主線程RunLoop處于休眠狀態(tài); - 如果此時(shí)點(diǎn)擊了屏幕移宅,會(huì)產(chǎn)生一個(gè)mach_port归粉,基于mach_port最終轉(zhuǎn)成Source1,喚醒主線程吞杭,運(yùn)行處理盏浇;
- 當(dāng)把程序殺死后,RunLoop退出芽狗,并且發(fā)送通知給觀察者绢掰。RunLoop退出后線程即刻銷(xiāo)毀。
Runtime
1、概念
- oc是一門(mén)動(dòng)態(tài)語(yǔ)言滴劲,所謂動(dòng)態(tài)語(yǔ)言就是在編譯階段無(wú)法確定調(diào)用的函數(shù)以及屬性的類(lèi)型攻晒,只有在運(yùn)行階段首次確定類(lèi)型和調(diào)用的函數(shù)。
- Runtime就是動(dòng)態(tài)語(yǔ)言下核心的一個(gè)庫(kù)班挖,底層都會(huì)通過(guò)
objc_msgSend
來(lái)處理消息轉(zhuǎn)發(fā)機(jī)制鲁捏。也是因?yàn)閾碛蠷untime使得oc語(yǔ)言靈活性比較強(qiáng),能夠具有動(dòng)態(tài)萧芙、動(dòng)態(tài)綁定给梅、動(dòng)態(tài)解析的特性。
- objc_msgSend
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
這是官方的聲明双揪,這是個(gè)最基本的用于發(fā)送消息的函數(shù)动羽。另外,這個(gè)函數(shù)并不能發(fā)送所有類(lèi)型的消息渔期,只能發(fā)送基本的消息运吓。比如,在一些處理器上疯趟,我們必須使用objc_msgSend_stret
來(lái)發(fā)送返回值類(lèi)型為結(jié)構(gòu)體的消息拘哨,使用objc_msgSend_fpret
來(lái)發(fā)送返回值類(lèi)型為浮點(diǎn)類(lèi)型的消息,而又在一些處理器上信峻,還得使用objc_msgSend_fp2ret
來(lái)發(fā)送返回值類(lèi)型為浮點(diǎn)類(lèi)型的消息倦青。要調(diào)用objc_msgSend
函數(shù),必須要將函數(shù)強(qiáng)制轉(zhuǎn)換成合適的函數(shù)指針類(lèi)型才能調(diào)用站欺。
從objc_msgSend
函數(shù)的聲明來(lái)看姨夹,它應(yīng)該是不帶返回值的,但是我們?cè)谑褂弥袇s可以強(qiáng)制轉(zhuǎn)換類(lèi)型矾策,以便接收返回值。另外峭沦,它的參數(shù)列表是可以任意多個(gè)的贾虽,前提也是要強(qiáng)制函數(shù)指針類(lèi)型。
編譯器會(huì)根據(jù)情況在objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
, 或 objc_msgSendSuper_stret
四個(gè)方法中選擇一個(gè)來(lái)調(diào)用吼鱼。如果消息是傳遞給超類(lèi)蓬豁,那么會(huì)調(diào)用名字帶有”Super”
的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡(jiǎn)單值時(shí)菇肃,那么會(huì)調(diào)用名字帶有”stret”
的函數(shù)地粪。
- id
objc_msgSend
第一個(gè)參數(shù)類(lèi)型為id,它是一個(gè)指向objc_object結(jié)構(gòu)體的指針:
typedef struct objc_object *id;
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
uintptr_t overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
uintptr_t rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
size_t sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
};
objc_object
結(jié)構(gòu)體包含一個(gè)isa指針琐谤,根據(jù)isa指針就可以找到對(duì)象所屬的類(lèi)蟆技。
注意:isa指針不總是指向?qū)嵗龑?duì)象所屬的類(lèi),不能依靠它來(lái)確定類(lèi)型,而是應(yīng)該用class方法來(lái)確定實(shí)例對(duì)象的類(lèi)质礼。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define ISA_HAS_INLINE_RC 1
# define RC_HAS_SIDETABLE_BIT 55
# define RC_ONE_BIT (RC_HAS_SIDETABLE_BIT+1)
# define RC_ONE (1ULL<<RC_ONE_BIT)
# define RC_HALF (1ULL<<7)
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define ISA_HAS_INLINE_RC 1
# define RC_HAS_SIDETABLE_BIT 44
# define RC_ONE_BIT (RC_HAS_SIDETABLE_BIT+1)
# define RC_ONE (1ULL<<RC_ONE_BIT)
# define RC_HALF (1ULL<<18)
# endif
所以在isa_t聯(lián)合體中Class cls和uintptr_t bits是互斥的旺聚。
由 typedef unsigned long uintptr_t; 所知,bits占據(jù)8字節(jié)眶蕉,共64位砰粹,64位中存儲(chǔ)的即ISA_BITFIELD宏定義中的內(nèi)容。
uintptr_t nonpointer : 1; 是否對(duì)isa指針開(kāi)啟優(yōu)化造挽。0:純isa指針 1:不只類(lèi)對(duì)象地址碱璃,還包括了類(lèi)信息,對(duì)象對(duì)引用計(jì)數(shù)等饭入。
uintptr_t has_assoc : 1; 關(guān)聯(lián)對(duì)象標(biāo)識(shí)位 0:沒(méi)有 1:存在嵌器。
uintptr_t has_cxx_dtor : 1; 是否有c++或objc的析構(gòu)函數(shù) 如果有則需要調(diào)用析構(gòu)邏輯,如果沒(méi)有則可以更快釋放對(duì)象圣拄。
uintptr_t shiftcls : 33; 存儲(chǔ)類(lèi)指針的值嘴秸,開(kāi)啟指針優(yōu)化時(shí),有33位用來(lái)存放類(lèi)指針庇谆。
uintptr_t magic : 6; 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是未初始化的空間岳掐。
uintptr_t weakly_referenced : 1; 標(biāo)志對(duì)象是否被指向或曾經(jīng)指向一個(gè)ARC的弱變量,沒(méi)有弱引用的對(duì)象可以更快的釋放饭耳。
uintptr_t deallocating : 1; 標(biāo)志對(duì)象是否正在釋放內(nèi)存串述。
uintptr_t has_sidetable_rc : 1; 當(dāng)引用計(jì)數(shù)大于10時(shí),則需要借助該變量存儲(chǔ)進(jìn)位寞肖。
uintptr_t extra_rc : 19 表示該對(duì)象的引用計(jì)數(shù)減1纲酗,如果引用計(jì)數(shù)為10,則extra_rc為9新蟆,如果引用計(jì)數(shù)大于10觅赊,則需要借助has_sidetable_rc。
SEL
SEL其實(shí)是一個(gè)指向objc_selector結(jié)構(gòu)體的指針:typedef struct objc_selector *SEL
;
objc_msgSend
函數(shù)第二個(gè)參數(shù)類(lèi)型為SEL琼稻,它是selector
在Objc中的表示類(lèi)型(Swift中是Selector類(lèi))吮螺。
其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()
或者 Runtime 系統(tǒng)的sel_registerName
函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器帕翻。Class
Class其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class
;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
const class_ro_t *safe_ro() const {
return bits.safe_ro();
}
.....
}
struct class_data_bits_t {
friend objc_class;
class_rw_t* data() const {
...
}
void setData(class_rw_t *newData)
{
...
}
const class_ro_t *safe_ro() const {
...
}
}
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
// _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
union {
// Note: _flags on ARM64 needs to line up with the unused bits of
// _originalPreoptCache because we access some flags (specifically
// FAST_CACHE_HAS_DEFAULT_CORE and FAST_CACHE_HAS_DEFAULT_AWZ) on
// unrealized classes with the assumption that they will start out
// as 0.
struct {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && !__LP64__
// Outlined cache mask storage, 32-bit, we have mask and occupied.
explicit_atomic<mask_t> _mask;
uint16_t _occupied;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && __LP64__
// Outlined cache mask storage, 64-bit, we have mask, occupied, flags.
explicit_atomic<mask_t> _mask;
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#elif __LP64__
// Inline cache mask storage, 64-bit, we have occupied, flags, and
// empty space to line up flags with originalPreoptCache.
//
// Note: the assembly code for objc_release_xN knows about the
// location of _flags and the
// FAST_CACHE_HAS_CUSTOM_DEALLOC_INITIATION flag within. Any changes
// must be applied there as well.
uint32_t _unused;
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#else
// Inline cache mask storage, 32-bit, we have occupied, flags.
uint16_t _occupied;
uint16_t _flags;
# define CACHE_T_HAS_FLAGS 1
#endif
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
...
public:
// The following four fields are public for objcdt's use only.
// objcdt reaches into fields while the process is suspended
// hence doesn't care for locks and pesky little details like this
// and can safely use these.
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
};
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
}
struct class_rw_t {
...
explicit_atomic<uintptr_t> ro_or_rw_ext;
...
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
// 設(shè)置ro
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
// 獲取相關(guān)信息
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
}
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
Class本身是運(yùn)行時(shí)加載的鸠补,在運(yùn)行時(shí)會(huì)被改變,所以本身Class就是屬于臟內(nèi)存嘀掸。那么如果想要獲取Class的干凈內(nèi)存紫岩,也就是編譯時(shí)確定的數(shù)據(jù)結(jié)構(gòu)包括方法列表、成員變量等的睬塌,該怎么辦泉蝌?這其實(shí)就是class_ro_t的作用歇万。因?yàn)閏lass_ro_t是只讀,意味著class_ro_t是從mach-o讀取類(lèi)的數(shù)據(jù)之后梨与,就不會(huì)被改變堕花。那如果我們想在運(yùn)行時(shí)修改類(lèi)的信息,比如添加方法粥鞋,比如加載category怎么辦呢缘挽?那這時(shí)候就有一個(gè)與之對(duì)應(yīng)的class_rw_t結(jié)構(gòu),class_rw_t是運(yùn)行時(shí)存儲(chǔ)類(lèi)的信息呻粹,可讀可寫(xiě)的壕曼,可以在運(yùn)行時(shí)修改。說(shuō)到這里等浊,好像還漏掉一個(gè)結(jié)構(gòu)class_rw_ext_t腮郊,這個(gè)東西又是干什么用的呢?存在的意義是什么筹燕?其實(shí)還是跟運(yùn)行時(shí)有關(guān)轧飞。實(shí)際上在我們的app運(yùn)行中,需要運(yùn)行時(shí)修改的類(lèi)是非少的撒踪,據(jù)統(tǒng)計(jì)平均大概就10%左右过咬。那也就是說(shuō)大部分只需要讀取class_ro_t中的數(shù)據(jù)就夠了,少部分才需要修改制妄。因此才會(huì)有class_rw_ext_t這個(gè)擴(kuò)展的結(jié)構(gòu)體掸绞。class_rw_ext_t的作用是這樣的:當(dāng)我們需要修改類(lèi)結(jié)構(gòu)時(shí),比如添加方法耕捞、加載category等時(shí)衔掸,class_rw_t回去開(kāi)辟一個(gè)額外的空間rwe(class_rw_ext_t),用于存儲(chǔ)新的方法和class_ro_t中的方法等信息俺抽。這樣做的目的有一個(gè)好處就是敞映,對(duì)于絕大部分類(lèi)是不需要這個(gè)開(kāi)辟class_rw_ext_t這個(gè)結(jié)構(gòu)體,節(jié)省內(nèi)存磷斧。
2驱显、Runtime 如何實(shí)現(xiàn) weak 屬性?
weak 此特質(zhì)表明該屬性定義了一種「非擁有關(guān)系」(nonowning relationship)瞳抓。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不持有新值(新指向的對(duì)象)伏恐,也不釋放舊值(原來(lái)指向的對(duì)象)孩哑。
Runtime 對(duì)注冊(cè)的類(lèi),會(huì)進(jìn)行內(nèi)存布局翠桦,維護(hù)一個(gè) hash 表横蜒,這是一個(gè)全局表胳蛮,表中是用 weak 指向的對(duì)象內(nèi)存地址作為 key,用所有指向該對(duì)象的weak指針表作為 value丛晌。當(dāng)此對(duì)象的引用計(jì)數(shù)為 0 的時(shí)候會(huì)調(diào)用dealloc
仅炊,假如該對(duì)象內(nèi)存地址是 a,那么就會(huì)以 a 為 key澎蛛,在這個(gè) weak 表中搜索抚垄,找到所有以 a 為鍵的 weak 對(duì)象,從而設(shè)置為 nil谋逻。
Runtime 如何實(shí)現(xiàn) weak 屬性具體流程大致分為 3 步:
- 初始化時(shí):runtime 會(huì)調(diào)用
objc_initWeak()
函數(shù)呆馁,初始化一個(gè)新的 weak 指針指向?qū)ο蟮牡刂贰?/li> - 添加引用時(shí):
objc_initWeak()
函數(shù)會(huì)調(diào)用objc_storeWeak()
函數(shù),objc_storeWeak()
的作用是更新指針指向(指針可能原來(lái)指向著其他對(duì)象毁兆,這時(shí)候需要將該 weak 指針與舊對(duì)象解除綁定浙滤,會(huì)調(diào)用到weak_unregister_no_lock
),如果指針指向的新對(duì)象非空气堕,則創(chuàng)建對(duì)應(yīng)的弱引用表纺腊,將 weak 指針與新對(duì)象進(jìn)行綁定,會(huì)調(diào)用到weak_register_no_lock
茎芭。在這個(gè)過(guò)程中揖膜,為了防止多線程中競(jìng)爭(zhēng)沖突,會(huì)有一些鎖的操作骗爆。 - 釋放時(shí):調(diào)用
clearDeallocating()
函數(shù)次氨,該函數(shù)首先根據(jù)對(duì)象地址獲取所有 weak 指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為 nil摘投,最后把這個(gè) entry 從 weak 表中刪除煮寡,最后清理對(duì)象的記錄。
3犀呼、Runtime具體應(yīng)用
- 利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類(lèi)添加屬性
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
- 第一個(gè)參數(shù): id object : 需要傳入的是 : 對(duì)象的主分支
- 第二個(gè)參數(shù): const void *key : 是一個(gè) static 類(lèi)型的關(guān)鍵字,這里根據(jù)開(kāi)發(fā)者自身來(lái)定義就行
- 第三個(gè)參數(shù): id value : 傳入的是: 對(duì)象的子分支
- 第四個(gè)參數(shù): objc_AssociationPolicy policy :是當(dāng)前關(guān)聯(lián)對(duì)象的類(lèi)型 strong,weak,copy (枚舉類(lèi)型:開(kāi)發(fā)者可以點(diǎn)進(jìn)去看)
objc_getAssociatedObject(<#id object#>, <#const void *key#>)就相對(duì)來(lái)說(shuō)容易理解一點(diǎn)了
- 第一個(gè)參數(shù) : id object : 需要傳入的是 : 對(duì)象的主分支
- 第二個(gè)參數(shù) : const void *key : 是一個(gè) static 類(lèi)型的關(guān)鍵字,這里根據(jù)開(kāi)發(fā)者自身來(lái)定義就行
- 遍歷類(lèi)的所有成員變量(修改textfield的占位文字顏色幸撕、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
- 交換方法實(shí)現(xiàn)外臂,在方法上增加額外功能(交換系統(tǒng)的方法)
- 有這樣一個(gè)場(chǎng)景坐儿,出于某些需求,我們需要跟蹤記錄APP中按鈕的點(diǎn)擊次數(shù)和頻率等數(shù)據(jù)宋光,怎么解決貌矿?當(dāng)然通過(guò)繼承按鈕類(lèi)或者通過(guò)類(lèi)別實(shí)現(xiàn)是一個(gè)辦法,但是帶來(lái)其他問(wèn)題比如別人不一定會(huì)去實(shí)例化你寫(xiě)的子類(lèi)罪佳,或者其他類(lèi)別也實(shí)現(xiàn)了點(diǎn)擊方法導(dǎo)致不確定會(huì)調(diào)用哪一個(gè)逛漫,runtime可以這樣解決:
@implementation UIButton (Hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [self class];
SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
SEL cusSEL = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[CountTool addClickCount];
[self mySendAction:action to:target forEvent:event];
}
@end
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題
- 動(dòng)態(tài)變量控制
在程序中,xiaowen的age是20赘艳,后來(lái)被runtime變成10
動(dòng)態(tài)獲取xiaowen類(lèi)中的所有屬性[包括私有]
Ivar *ivar = class_copyIvarList([self.xiaowen class], &count);遍歷屬性找到對(duì)應(yīng)name字段
const char *varName = ivar_getName(var);修改對(duì)應(yīng)的字段值成20
object_setIvar(self.xiaowen, var, @"20");
- (void)changeAge {
unsigned int count = 0;
Ivar *ivar = class_copyIvarList([self.xiaowen class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivar[i];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithUTF8String:varName];
if ([name isEqualToString:@"_age"]) {
object_setIvar(self.xiaowen, var, @"20");
break;
}
}
NSLog(@"age is %@",self.xiaowen.age);
}
- 動(dòng)態(tài)添加方法
- 動(dòng)態(tài)給Person類(lèi)中添加study方法:
- (void)addMethod {
class_addMethod([self.xiaowen class], @selector(study), (IMP)studyImp, "v@:");
if ([self.xiaowen respondsToSelector:@selector(study)]) {
[self.xiaowen performSelector:@selector(study)];
} else{
NSLog(@"Sorry,I don't know");
}
}
void studyImp(id self,SEL _cmd) {
NSLog(@"i am from beijing");
}
(IMP)studyImp 意思是studyImp的地址指針;
"v@:" 意思是酌毡,v代表無(wú)返回值void克握,如果是i則代表int;@代表 id self; : 代表 SEL _cmd;
“v@:@@” 意思是枷踏,兩個(gè)參數(shù)的沒(méi)有返回值牙捉。
- KVC 字典轉(zhuǎn)模型
- 先實(shí)現(xiàn)最外層的屬性轉(zhuǎn)換
// 創(chuàng)建對(duì)應(yīng)模型對(duì)象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.獲取成員屬性數(shù)組
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍歷所有的成員屬性名,一個(gè)一個(gè)去字典中取出對(duì)應(yīng)的value給模型屬性賦值
for (int i = 0; i < count; i++) {
// 2.1 獲取成員屬性
Ivar ivar = ivarList[i];
// 2.2 獲取成員屬性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成員屬性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出對(duì)應(yīng)value給模型屬性賦值
id value = dict[key];
// 獲取成員屬性類(lèi)型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}
- 內(nèi)層數(shù)組斥赋,字典的轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
// 是字典對(duì)象,并且屬性名對(duì)應(yīng)類(lèi)型是自定義類(lèi)型
// 處理類(lèi)型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 自定義對(duì)象,并且值是字典
// value:user字典 -> User模型
// 獲取模型(user)類(lèi)對(duì)象
Class modalClass = NSClassFromString(ivarType);
// 字典轉(zhuǎn)模型
if (modalClass) {
// 字典轉(zhuǎn)模型 user
value = [modalClass objectWithDict:value];
}
}
if ([value isKindOfClass:[NSArray class]]) {
// 判斷對(duì)應(yīng)類(lèi)有沒(méi)有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 轉(zhuǎn)換成id類(lèi)型归园,就能調(diào)用任何對(duì)象的方法
id idSelf = self;
// 獲取數(shù)組中字典對(duì)應(yīng)的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組亭枷,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel objectWithDict:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
- 動(dòng)態(tài)的添加對(duì)象的成員變量和方法
- 實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔,我們把encodeWithCoder 和 initWithCoder這兩個(gè)方法抽成宏
#import "Person.h"
#import <objc/runtime.h>
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\
#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\
@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
encodeRuntime(Person)
}
- (id)initWithCoder:(NSCoder *)decoder {
initCoderRuntime(Person)
}
@end
4下梢、Runtime方法調(diào)用流程客蹋?
1、當(dāng)調(diào)用對(duì)象方法的時(shí)候孽江,會(huì)通過(guò)obj_object的isa指針找對(duì)對(duì)應(yīng)的歸屬類(lèi)讶坯。
2、從歸屬類(lèi)(obj_class)類(lèi)中的cache中尋找對(duì)應(yīng)的相等的sel方法編號(hào)岗屏。
3辆琅、如果沒(méi)有找到,繼續(xù)obj_class中的method_list中查找这刷,如果找到寫(xiě)入cache中婉烟。
4、如果沒(méi)有到找到暇屋,會(huì)一直找到它的元類(lèi)上似袁。
5、如果元類(lèi)也沒(méi)有的話咐刨,會(huì)調(diào)用消息動(dòng)態(tài)解析方法+resovleInstanceMethod:
和+resloveClassMethod:
的方法昙衅,查看是否存在綁定的方法。
6定鸟、如果沒(méi)有綁定方法而涉,會(huì)調(diào)用消息轉(zhuǎn)發(fā)方法-forwardingTargetForSelector:
的方法。查看是否存在轉(zhuǎn)發(fā)對(duì)象联予。
7啼县、如果沒(méi)有存在消息轉(zhuǎn)發(fā)對(duì)象,會(huì)調(diào)用-methodSignatureForSelector:
的方法沸久,查看是否有方法簽名返回類(lèi)型和參數(shù)類(lèi)型季眷。
8、不存在簽名方法和類(lèi)型卷胯,就會(huì)來(lái)到-doseNotRecognizeSelector:
方法內(nèi)部程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance瘟裸。
9、存在簽名的方法诵竭,就是繼續(xù)執(zhí)行-forwardInvocation:
尋找IMP话告,沒(méi)有找到IMP,就會(huì)來(lái)到-doseNotRecognizeSelector:
方法內(nèi)部程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance卵慰。
@implementation Person
- (BOOL)respondsToSelector:(SEL)aSelector {
bool a= [super respondsToSelector:aSelector];
return a;
}
//如果方法沒(méi)有實(shí)現(xiàn)沙郭,默認(rèn)返回false
//如果返回false,就會(huì)走消息轉(zhuǎn)發(fā)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
bool a = [super resolveInstanceMethod:sel];
return a;
}
//默認(rèn)返回空
//又被稱(chēng)為快速消息轉(zhuǎn)發(fā)裳朋。
// 如果為空病线,走慢速消息轉(zhuǎn)發(fā),繼續(xù)轉(zhuǎn)發(fā)消息
- (id)forwardingTargetForSelector:(SEL)aSelector {
id a = [super forwardingTargetForSelector:aSelector];
return a;
}
// 默認(rèn)一般普通方法是返回空的鲤嫡。
// 如果是協(xié)議方法送挑,沒(méi)有實(shí)現(xiàn),不會(huì)反回空暖眼。
//反回空惕耕,到這里就會(huì)崩潰了
//如果這里返回了簽名,會(huì)再次調(diào)用resolveInstanceMethod:(SEL)sel判斷是否實(shí)現(xiàn)
//如果仍然沒(méi)有實(shí)現(xiàn)诫肠,就會(huì)走到fowardInvocation:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *a = [super methodSignatureForSelector:aSelector];
return a;
}
//默認(rèn)實(shí)現(xiàn)是崩潰
//并且不能用try-catch捕獲
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[super forwardInvocation:anInvocation];
NSLog(@"");
}
@end
5司澎、Runtime的方法交換的流程?
1栋豫、方法交換要放在+(viod)load
中處理挤安。
2、在load中首先使用class_addMethod
的方法添加新方法丧鸯。
3蛤铜、添加成功后,使用class_replaceMethod
替換原來(lái)的方法丛肢。
4围肥、如果添加失敗的話,則說(shuō)明已經(jīng)有添加成功摔踱。直接使用class_exchangeMethod
的方法替換虐先。
5、在交換方法時(shí)候派敷,使用dispach_one
的方法蛹批。
6、在新方法中調(diào)用新方法篮愉。
使用注意要點(diǎn):
1腐芍、使用load時(shí)候,切記不要做初始化和大開(kāi)銷(xiāo)大內(nèi)存邏輯试躏。因?yàn)槌绦蝽樞蚴侵碛拢割?lèi)->當(dāng)前類(lèi)->分類(lèi)->mian
2、使用的時(shí)候如果方法相同是不會(huì)覆蓋原來(lái)的方法颠蕴,會(huì)放在置頂泣刹,所以一般不會(huì)調(diào)用到原來(lái)的方法助析。
3、在新方法中調(diào)用新方法椅您。
6外冀、常見(jiàn)方法?
- 獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
- 獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"Method---->%@", NSStringFromSelector(method_getName(method)));
}
- 獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
- 獲取協(xié)議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
計(jì)算機(jī)網(wǎng)絡(luò)
1掀泳、網(wǎng)絡(luò)七層協(xié)議
-
應(yīng)用層:
1.用戶接口雪隧、應(yīng)用程序;
2.Application典型設(shè)備:網(wǎng)關(guān)员舵;
3.典型協(xié)議脑沿、標(biāo)準(zhǔn)和應(yīng)用:TELNET、FTP马僻、HTTP -
表示層:
1.數(shù)據(jù)表示庄拇、壓縮和加密presentation
2.典型設(shè)備:網(wǎng)關(guān)
3.典型協(xié)議、標(biāo)準(zhǔn)和應(yīng)用:ASCLL巫玻、PICT丛忆、TIFF、JPEG|MPEG
4.表示層相當(dāng)于一個(gè)東西的表示仍秤,表示的一些協(xié)議熄诡,比如圖片、聲音和視頻MPEG诗力。 -
會(huì)話層:
1.會(huì)話的建立和結(jié)束凰浮;
2.典型設(shè)備:網(wǎng)關(guān);
3.典型協(xié)議苇本、標(biāo)準(zhǔn)和應(yīng)用:RPC袜茧、SQL、NFS瓣窄、X WINDOWS笛厦、ASP -
傳輸層:
1.主要功能:端到端控制Transport;
2.典型設(shè)備:網(wǎng)關(guān)俺夕;
3.典型協(xié)議裳凸、標(biāo)準(zhǔn)和應(yīng)用:TCP、UDP劝贸、SPX -
網(wǎng)絡(luò)層:
1.主要功能:路由姨谷、尋址Network;
2.典型設(shè)備:路由器映九;
3.典型協(xié)議梦湘、標(biāo)準(zhǔn)和應(yīng)用:IP、IPX、APPLETALK捌议、ICMP哼拔; -
數(shù)據(jù)鏈路層:
1.主要功能:保證無(wú)差錯(cuò)的疏忽鏈路的data link;
2.典型設(shè)備:交換機(jī)禁灼、網(wǎng)橋管挟、網(wǎng)卡;
3.典型協(xié)議弄捕、標(biāo)準(zhǔn)和應(yīng)用:802.2、802.3ATM导帝、HDLC守谓、FRAME RELAY; -
物理層:
1.主要功能:傳輸比特流Physical您单;
2.典型設(shè)備:集線器斋荞、中繼器
3.典型協(xié)議、標(biāo)準(zhǔn)和應(yīng)用:V.35虐秦、EIA/TIA-232.
2平酿、Http 和 Https 的區(qū)別?Https為什么更加安全悦陋?
-
區(qū)別
1.HTTPS 需要向機(jī)構(gòu)申請(qǐng) CA 證書(shū)蜈彼,極少免費(fèi)。
2.HTTP 屬于明文傳輸俺驶,HTTPS基于 SSL 進(jìn)行加密傳輸幸逆。
3.HTTP 端口號(hào)為 80,HTTPS 端口號(hào)為 443 暮现。
4.HTTPS 是加密傳輸还绘,有身份驗(yàn)證的環(huán)節(jié),更加安全栖袋。 -
安全
SSL(安全套接層) TLS(傳輸層安全)
以上兩者在傳輸層之上拍顷,對(duì)網(wǎng)絡(luò)連接進(jìn)行加密處理,保障數(shù)據(jù)的完整性塘幅,更加的安全昔案。
3、HTTPS的連接建立流程晌块?
- 服務(wù)器端的公鑰和私鑰爱沟,用來(lái)進(jìn)行非對(duì)稱(chēng)加密
- 客戶端生成的隨機(jī)密鑰,用來(lái)進(jìn)行對(duì)稱(chēng)加密
如上圖匆背,HTTPS連接過(guò)程大致可分為八步:
客戶端訪問(wèn)HTTPS連接呼伸。
客戶端會(huì)把安全協(xié)議版本號(hào)、客戶端支持的加密算法列表、隨機(jī)數(shù)C發(fā)給服務(wù)端括享。服務(wù)端發(fā)送證書(shū)給客戶端
服務(wù)端接收密鑰算法配件后搂根,會(huì)和自己支持的加密算法列表進(jìn)行比對(duì),如果不符合铃辖,則斷開(kāi)連接剩愧。否則,服務(wù)端會(huì)在該算法列表中娇斩,選擇一種對(duì)稱(chēng)算法(如AES)仁卷、一種公鑰算法(如具有特定秘鑰長(zhǎng)度的RSA)和一種MAC算法發(fā)給客戶端。
服務(wù)器端有一個(gè)密鑰對(duì)犬第,即公鑰和私鑰锦积,是用來(lái)進(jìn)行非對(duì)稱(chēng)加密使用的,服務(wù)器端保存著私鑰歉嗓,不能將其泄露丰介,公鑰可以發(fā)送給任何人。
在發(fā)送加密算法的同時(shí)還會(huì)把數(shù)字證書(shū)和隨機(jī)數(shù)S發(fā)送給客戶端
客戶端驗(yàn)證server證書(shū)
會(huì)對(duì)server公鑰進(jìn)行檢查鉴分,驗(yàn)證其合法性哮幢,如果發(fā)現(xiàn)發(fā)現(xiàn)公鑰有問(wèn)題,那么HTTPS傳輸就無(wú)法繼續(xù)志珍。客戶端組裝會(huì)話秘鑰
如果公鑰合格橙垢,那么客戶端會(huì)用服務(wù)器公鑰來(lái)生成一個(gè)前主秘鑰(Pre-Master Secret,PMS)碴裙,并通過(guò)該前主秘鑰和隨機(jī)數(shù)C钢悲、S來(lái)組裝成會(huì)話秘鑰客戶端將前主秘鑰加密發(fā)送給服務(wù)端
是通過(guò)服務(wù)端的公鑰來(lái)對(duì)前主秘鑰進(jìn)行非對(duì)稱(chēng)加密,發(fā)送給服務(wù)端服務(wù)端通過(guò)私鑰解密得到前主秘鑰
服務(wù)端接收到加密信息后舔株,用私鑰解密得到主秘鑰莺琳。服務(wù)端組裝會(huì)話秘鑰
服務(wù)端通過(guò)前主秘鑰和隨機(jī)數(shù)C、S來(lái)組裝會(huì)話秘鑰载慈。
至此惭等,服務(wù)端和客戶端都已經(jīng)知道了用于此次會(huì)話的主秘鑰。數(shù)據(jù)傳輸
客戶端收到服務(wù)器發(fā)送來(lái)的密文办铡,用客戶端密鑰對(duì)其進(jìn)行對(duì)稱(chēng)解密辞做,得到服務(wù)器發(fā)送的數(shù)據(jù)。
同理寡具,服務(wù)端收到客戶端發(fā)送來(lái)的密文秤茅,用服務(wù)端密鑰對(duì)其進(jìn)行對(duì)稱(chēng)解密,得到客戶端發(fā)送的數(shù)據(jù)童叠。
4框喳、解釋一下三次握手和四次揮手?
三次握手
1.由客戶端向服務(wù)端發(fā)送 SYN 同步報(bào)文。
2.當(dāng)服務(wù)端收到 SYN 同步報(bào)文之后五垮,會(huì)返回給客戶端 SYN 同步報(bào)文和 ACK 確認(rèn)報(bào)文乍惊。
3.客戶端會(huì)向服務(wù)端發(fā)送 ACK 確認(rèn)報(bào)文,此時(shí)客戶端和服務(wù)端的連接正式建立放仗。建立連接
1.這個(gè)時(shí)候客戶端就可以通過(guò) Http 請(qǐng)求報(bào)文润绎,向服務(wù)端發(fā)送請(qǐng)求
2.服務(wù)端接收到客戶端的請(qǐng)求之后,向客戶端回復(fù) Http 響應(yīng)報(bào)文诞挨。四次揮手
1.先由客戶端向服務(wù)端發(fā)送 FIN 結(jié)束報(bào)文莉撇。
2.服務(wù)端會(huì)返回給客戶端 ACK 確認(rèn)報(bào)文 。此時(shí)惶傻,由客戶端發(fā)起的斷開(kāi)連接已經(jīng)完成稼钩。
3.服務(wù)端會(huì)發(fā)送給客戶端 FIN 結(jié)束報(bào)文 和 ACK 確認(rèn)報(bào)文。
4.客戶端會(huì)返回 ACK 確認(rèn)報(bào)文到服務(wù)端达罗,至此,由服務(wù)端方向的斷開(kāi)連接已經(jīng)完成静秆。
5粮揉、TCP 和 UDP的區(qū)別?
- TCP:面向連接抚笔、傳輸可靠(保證數(shù)據(jù)正確性,保證數(shù)據(jù)順序)扶认、用于傳輸大量數(shù)據(jù)(流模式)、速度慢殊橙,建立連接需要開(kāi)銷(xiāo)較多(時(shí)間辐宾,系統(tǒng)資源)。
- UDP:面向非連接膨蛮、傳輸不可靠叠纹、用于傳輸少量數(shù)據(jù)(數(shù)據(jù)包模式)、速度快敞葛。
多線程
1誉察、進(jìn)程與線程
進(jìn)程:
1.進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運(yùn)行活動(dòng),它是操作系統(tǒng)分配資源的基本單元.
2.進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序惹谐,就是一段程序的執(zhí)行過(guò)程,我們可以理解為手機(jī)上的一個(gè)app.
3.每個(gè)進(jìn)程之間是獨(dú)立的持偏,每個(gè)進(jìn)程均運(yùn)行在其專(zhuān)用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨(dú)立運(yùn)行所需的全部資源線程:
1.程序執(zhí)行流的最小單元氨肌,線程是進(jìn)程中的一個(gè)實(shí)體.
2.一個(gè)進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動(dòng)的時(shí)候鸿秆,系統(tǒng)會(huì)默認(rèn)開(kāi)啟一條線程,也就是主線程進(jìn)程和線程的關(guān)系
1.線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
2.線程是 CPU 分配資源和調(diào)度的最小單位
3.一個(gè)程序可以對(duì)應(yīng)多個(gè)進(jìn)程(多進(jìn)程),一個(gè)進(jìn)程中可有多個(gè)線程,但至少要有一條線程
4.同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程資源
2怎囚、什么是多線程卿叽?
- 多線程的實(shí)現(xiàn)原理:事實(shí)上,同一時(shí)間內(nèi)單核的CPU只能執(zhí)行一個(gè)線程,多線程是CPU快速的在多個(gè)線程之間進(jìn)行切換(調(diào)度)附帽,造成了多個(gè)線程同時(shí)執(zhí)行的假象埠戳。
- 如果是多核CPU就真的可以同時(shí)處理多個(gè)線程了。
- 多線程的目的是為了同步完成多項(xiàng)任務(wù)蕉扮,通過(guò)提高系統(tǒng)的資源利用率來(lái)提高系統(tǒng)的效率整胃。
3、多線程的優(yōu)點(diǎn)和缺點(diǎn)喳钟?
優(yōu)點(diǎn):
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU屁使、內(nèi)存利用率)缺點(diǎn):
- 開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M奔则,子線程占用512KB)蛮寂,如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間易茬,降低程序的性能
- 線程越多酬蹋,CPU在調(diào)度線程上的開(kāi)銷(xiāo)就越大
- 程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
4抽莱、多線程的并行和并發(fā)有什么區(qū)別范抓?
- 并行:充分利用計(jì)算機(jī)的多核,在多個(gè)線程上同步進(jìn)行
- 并發(fā):在一條線程上通過(guò)快速切換食铐,讓人感覺(jué)在同步進(jìn)行
5匕垫、iOS中實(shí)現(xiàn)多線程的幾種方案,各自有什么特點(diǎn)虐呻?
- NSThread 面向?qū)ο蟮南蟊茫枰绦騿T手動(dòng)創(chuàng)建線程,但不需要手動(dòng)銷(xiāo)毀斟叼。子線程間通信很難偶惠。
- GCD C語(yǔ)言,充分利用了設(shè)備的多核犁柜,自動(dòng)管理線程生命周期洲鸠。比NSOperation效率更高。
- NSOperation 基于GCD封裝馋缅,更加面向?qū)ο蟀峭螅菺CD多了一些功能。
6萤悴、多個(gè)網(wǎng)絡(luò)請(qǐng)求完成后執(zhí)行下一步瘾腰?
使用GCD的dispatch_group_t
創(chuàng)建一個(gè)dispatch_group_t
每次網(wǎng)絡(luò)請(qǐng)求前先dispatch_group_enter
,請(qǐng)求回調(diào)后再dispatch_group_leave
,enter和leave必須配合使用覆履,有幾次enter就要有幾次leave蹋盆,否則group會(huì)一直存在费薄。
當(dāng)所有enter的block都leave后,會(huì)執(zhí)行dispatch_group_notify
的block栖雾。
柵欄函數(shù)中傳入的參數(shù)隊(duì)列必須是由 dispatch_queue_create
方法創(chuàng)建的隊(duì)列楞抡,否則,與 dispatch_async
無(wú)異析藕,起不到“柵欄”的作用召廷。
NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_group_leave(downloadGroup);
}];
[task resume];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
使用GCD的信號(hào)量dispatch_semaphore_t
dispatch_semaphore信號(hào)量為基于計(jì)數(shù)器的一種多線程同步機(jī)制。如果semaphore計(jì)數(shù)大于等于1账胧,計(jì)數(shù)-1竞慢,返回,程序繼續(xù)運(yùn)行治泥。如果計(jì)數(shù)為0筹煮,則等待。dispatch_semaphore_signal(semaphore)
為計(jì)數(shù)+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
為設(shè)置等待時(shí)間居夹,這里設(shè)置的等待時(shí)間是一直等待败潦。
創(chuàng)建semaphore為0,等待准脂,等10個(gè)網(wǎng)絡(luò)請(qǐng)求都完成了变屁,dispatch_semaphore_signal(semaphore)為計(jì)數(shù)+1,然后計(jì)數(shù)-1返回
NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
count++;
if (count==10) {
dispatch_semaphore_signal(sem);
count = 0;
}
}];
[task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
7意狠、多個(gè)網(wǎng)絡(luò)請(qǐng)求順序執(zhí)行后執(zhí)行下一步?
使用信號(hào)量semaphore
每一次遍歷疮胖,都讓其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
环戈,這個(gè)時(shí)候線程會(huì)等待,阻塞當(dāng)前線程澎灸,直到dispatch_semaphore_signal(sem)
調(diào)用之后
NSString *str = @"http://www.reibang.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_semaphore_signal(sem);
}];
[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
8院塞、異步操作兩組數(shù)據(jù)時(shí), 執(zhí)行完第一組之后, 才能執(zhí)行第二組?
這里使用dispatch_barrier_async柵欄方法即可實(shí)現(xiàn)
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"第一次任務(wù)的主線程為: %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第二次任務(wù)的主線程為: %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"第一次任務(wù), 第二次任務(wù)執(zhí)行完畢, 繼續(xù)執(zhí)行");
});
dispatch_async(queue, ^{
NSLog(@"第三次任務(wù)的主線程為: %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第四次任務(wù)的主線程為: %@", [NSThread currentThread]);
});
9性昭、多線程中的死鎖拦止?
死鎖是由于多個(gè)線程(進(jìn)程)在執(zhí)行過(guò)程中,因?yàn)闋?zhēng)奪資源而造成的互相等待現(xiàn)象糜颠,你可以理解為卡主了汹族。產(chǎn)生死鎖的必要條件有四個(gè):
- 互斥條件:指進(jìn)程對(duì)所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用其兴。如果此時(shí)還有其它進(jìn)程請(qǐng)求資源顶瞒,則請(qǐng)求者只能等待,直至占有資源的進(jìn)程用畢釋放元旬。
- 請(qǐng)求和保持條件:指進(jìn)程已經(jīng)保持至少一個(gè)資源榴徐,但又提出了新的資源請(qǐng)求守问,而該資源已被其它進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程阻塞坑资,但又對(duì)自己已獲得的其它資源保持不放耗帕。
- 不可剝奪條件:指進(jìn)程已獲得的資源,在未使用完之前袱贮,不能被剝奪仿便,只能在使用完時(shí)由自己釋放。
- 環(huán)路等待條件:指在發(fā)生死鎖時(shí)字柠,必然存在一個(gè)進(jìn)程——資源的環(huán)形鏈探越,即進(jìn)程集合{P0,P1窑业,P2钦幔,···,Pn}中的P0正在等待一個(gè)P1占用的資源常柄;P1正在等待P2占用的資源忌堂,……狠持,Pn正在等待已被P0占用的資源。
最常見(jiàn)的就是 同步函數(shù) + 主隊(duì)列 的組合,本質(zhì)是隊(duì)列阻塞统刮。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"1");
// 什么也不會(huì)打印,直接報(bào)錯(cuò)
10剔氏、GCD執(zhí)行原理酗宋?
- GCD有一個(gè)底層線程池,這個(gè)池中存放的是一個(gè)個(gè)的線程品姓。之所以稱(chēng)為“池”寝并,很容易理解出這個(gè)“池”中的線程是可以重用的,當(dāng)一段時(shí)間后這個(gè)線程沒(méi)有被調(diào)用的話腹备,這個(gè)線程就會(huì)被銷(xiāo)毀衬潦。注意:開(kāi)多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統(tǒng)自動(dòng)來(lái)維護(hù)植酥,不需要我們程序員來(lái)維護(hù)镀岛,我們只關(guān)心的是向隊(duì)列中添加任務(wù),隊(duì)列調(diào)度即可友驮。
- 如果隊(duì)列中存放的是同步任務(wù)漂羊,則任務(wù)出隊(duì)后,底層線程池中會(huì)提供一條線程供這個(gè)任務(wù)執(zhí)行卸留,任務(wù)執(zhí)行完畢后這條線程再回到線程池拨与。這樣隊(duì)列中的任務(wù)反復(fù)調(diào)度,因?yàn)槭峭降陌拢援?dāng)我們用currentThread打印的時(shí)候买喧,就是同一條線程捻悯。
- 如果隊(duì)列中存放的是異步的任務(wù),(注意異步可以開(kāi)線程)淤毛,當(dāng)任務(wù)出隊(duì)后今缚,底層線程池會(huì)提供一個(gè)線程供任務(wù)執(zhí)行,因?yàn)槭钱惒綀?zhí)行低淡,隊(duì)列中的任務(wù)不需等待當(dāng)前任務(wù)執(zhí)行完畢就可以調(diào)度下一個(gè)任務(wù)姓言,這時(shí)底層線程池中會(huì)再次提供一個(gè)線程供第二個(gè)任務(wù)執(zhí)行,執(zhí)行完畢后再回到底層線程池中蔗蹋。
- 這樣就對(duì)線程完成一個(gè)復(fù)用何荚,而不需要每一個(gè)任務(wù)執(zhí)行都開(kāi)啟新的線程,也就從而節(jié)約的系統(tǒng)的開(kāi)銷(xiāo)猪杭,提高了效率餐塘。在iOS7.0的時(shí)候,使用GCD系統(tǒng)通常只能開(kāi)5-8條線程皂吮,iOS8.0以后戒傻,系統(tǒng)可以開(kāi)啟很多條線程,但是實(shí)在開(kāi)發(fā)應(yīng)用中蜂筹,建議開(kāi)啟線程條數(shù):3-5條最為合理需纳。
項(xiàng)目架構(gòu)
1、MVC艺挪、MVVM模式
MVC(Model不翩、View、Controller)
MVC是比較直觀的架構(gòu)模式麻裳,最核心的就是通過(guò)Controller層來(lái)進(jìn)行調(diào)控
Model和View永遠(yuǎn)不能相互通信慌盯,只能通過(guò)Controller傳遞
Controller可以直接與Model對(duì)話(讀寫(xiě)調(diào)用Model),Model通過(guò)Notification和KVO機(jī)制與Controller間接通信
Controller可以直接與View對(duì)話掂器,通過(guò)IBoutlet直接操作View,IBoutlet直接對(duì)應(yīng)View的控件(例如創(chuàng)建一個(gè)Button:需聲明一個(gè) IBOutlet UIButton * btn)俱箱,View通過(guò)action向Controller報(bào)告時(shí)間的發(fā)生(用戶點(diǎn)擊了按鈕)国瓮。Controller是View的直接數(shù)據(jù)源
- 優(yōu)點(diǎn): 對(duì)于混亂的項(xiàng)目組織方式,有了一個(gè)明確的組織方式狞谱。通過(guò)Controller來(lái)掌控全局乃摹,同時(shí)將View展示和Model的變化分開(kāi)
- 缺點(diǎn): 愈發(fā)笨重的Controller,隨著業(yè)務(wù)邏輯的增加跟衅,大量的代碼放進(jìn)Controller孵睬,導(dǎo)致Controller越來(lái)越臃腫,堆積成千上萬(wàn)行代碼伶跷,后期維護(hù)起來(lái)費(fèi)時(shí)費(fèi)力
MVVM(Model掰读、Controller/View秘狞、ViewModel)
在MVVM中,View和ViewController聯(lián)系在一起蹈集,我們把它們視為一個(gè)組件烁试,View和ViewController都不能直接引用model,而是引用是視圖模型即ViewModel拢肆。 ViewModel是一個(gè)用來(lái)放置用戶輸入驗(yàn)證邏輯减响、視圖顯示邏輯、網(wǎng)絡(luò)請(qǐng)求等業(yè)務(wù)邏輯的地方郭怪,這樣的設(shè)計(jì)模式支示,會(huì)輕微增加代碼量,但是會(huì)減少代碼的復(fù)雜性
- 優(yōu)點(diǎn): View可以獨(dú)立于Model的變化和修改鄙才,一個(gè)ViewModel可以綁定到不同的View上颂鸿,降低耦合,增加重用
-
缺點(diǎn): 過(guò)于簡(jiǎn)單的項(xiàng)目不適用咒循、大型的項(xiàng)目視圖狀態(tài)較多時(shí)構(gòu)建和維護(hù)成本太大
合理的運(yùn)用架構(gòu)模式有利于項(xiàng)目据途、團(tuán)隊(duì)開(kāi)發(fā)工作,不同的設(shè)計(jì)模式叙甸,只是讓不同的場(chǎng)景有了更多的選擇方案颖医。根據(jù)項(xiàng)目場(chǎng)景和開(kāi)發(fā)需求,選擇最合適的解決方案裆蒸。
調(diào)試技巧
1熔萧、LLDB常用的調(diào)試命令?
po:print object的縮寫(xiě)僚祷,表示顯示對(duì)象的文本描述佛致,如果對(duì)象不存在則打印nil。
p:可以用來(lái)打印基本數(shù)據(jù)類(lèi)型辙谜。
call:執(zhí)行一段代碼 如:call NSLog(@"%@", @"yang")
expr:動(dòng)態(tài)執(zhí)行指定表達(dá)式
bt:打印當(dāng)前線程堆棧信息 (bt all 打印所有線程堆棧信息)
image:常用來(lái)尋找棧地址對(duì)應(yīng)代碼位置 如:image lookup --address 0xxxx
2俺榆、斷點(diǎn)調(diào)試?
條件斷點(diǎn)
打上斷點(diǎn)之后装哆,對(duì)斷點(diǎn)進(jìn)行編輯罐脊,設(shè)置相應(yīng)過(guò)濾條件。下面簡(jiǎn)單的介紹一下條件設(shè)置:
- Condition:返回一個(gè)布爾值蜕琴,當(dāng)布爾值為真觸發(fā)斷點(diǎn)萍桌,一般里面我們可以寫(xiě)一個(gè)表達(dá)式。
- Ignore:忽略前N次斷點(diǎn)凌简,到N+1次再觸發(fā)斷點(diǎn)上炎。
- Action:斷點(diǎn)觸發(fā)事件,分為六種:
- AppleScript:執(zhí)行腳本雏搂。
- Capture GPU Frame:用于OpenGL ES調(diào)試藕施,捕獲斷點(diǎn)處GPU當(dāng)前繪制幀寇损。
- Debugger Command:和控制臺(tái)中輸入LLDB調(diào)試命令一致。
- Log Message:輸出自定義格式信息至控制臺(tái)铅碍。
- Shell Command:接收命令文件及相應(yīng)參數(shù)列表润绵,Shell Command是異步執(zhí)行的,只有勾選“Wait until done”才會(huì)等待Shell命令執(zhí)行完在執(zhí)行調(diào)試胞谈。
- Sound:斷點(diǎn)觸發(fā)時(shí)播放聲音尘盼。
- Options(Automatically continue after evaluating actions選項(xiàng)):選中后,表示斷點(diǎn)不會(huì)終止程序的運(yùn)行烦绳。
異常斷點(diǎn)
異常斷點(diǎn)可以快速定位不滿足特定條件的異常卿捎,比如常見(jiàn)的數(shù)組越界,這時(shí)候很難通過(guò)異常信息定位到錯(cuò)誤所在位置径密。這個(gè)時(shí)候異常斷點(diǎn)就可以發(fā)揮作用了午阵。
Exception:可以選擇拋出異常對(duì)象類(lèi)型:OC或C++。
Break:選擇斷點(diǎn)接收的拋出異常來(lái)源是Throw還是Catch語(yǔ)句享扔。
符號(hào)斷點(diǎn)
符號(hào)斷點(diǎn)的創(chuàng)建方式和異常斷點(diǎn)一樣一樣的底桂,在符號(hào)斷點(diǎn)中可以指定要中斷執(zhí)行的方法:
Symbol:[類(lèi)名 方法名]可以執(zhí)行到指定類(lèi)的指定方法中開(kāi)始斷點(diǎn)。
3惧眠、iOS 常見(jiàn)的崩潰類(lèi)型有哪些籽懦?
- unrecognized selector crash
- KVO crash
- NSNotification crash
- NSTimer crash
- Container crash
- NSString crash
- Bad Access crash (野指針)
- UI not on Main Thread Crash
性能優(yōu)化
1、造成tableView卡頓的原因有哪些氛魁?
- 最常用的就是cell的重用暮顺, 注冊(cè)重用標(biāo)識(shí)符
- 如果不重用cell時(shí),每當(dāng)一個(gè)cell顯示到屏幕上時(shí)秀存,就會(huì)重新創(chuàng)建一個(gè)新的cell
- 如果重用cell捶码,為cell創(chuàng)建一個(gè)ID,每當(dāng)需要顯示cell 的時(shí)候或链,都會(huì)先去緩沖池中尋找可循環(huán)利用的cell惫恼,如果沒(méi)有再重新創(chuàng)建cell
- 避免cell的重新布局
- cell的布局填充等操作比較耗時(shí),一般創(chuàng)建時(shí)就布局好
- 提前計(jì)算并緩存cell的屬性及內(nèi)容
- 當(dāng)我們創(chuàng)建cell的數(shù)據(jù)源方法時(shí)澳盐,編譯器并不是先創(chuàng)建cell 再定cell的高度
- 而是先根據(jù)內(nèi)容一次確定每一個(gè)cell的高度祈纯,高度確定后,再創(chuàng)建要顯示的cell洞就,滾動(dòng)時(shí),每當(dāng)cell進(jìn)入憑虛都會(huì)計(jì)算高度掀淘,提前估算高度告訴編譯器旬蟋,編譯器知道高度后,緊接著就會(huì)創(chuàng)建cell革娄,這時(shí)再調(diào)用高度的具體計(jì)算方法倾贰,這樣可以方式浪費(fèi)時(shí)間去計(jì)算顯示以外的cell
- 減少cell中控件的數(shù)量
- 盡量使cell得布局大致相同冕碟,不同風(fēng)格的cell可以使用不用的重用標(biāo)識(shí)符,初始化時(shí)添加控件匆浙,
- 不適用的可以先隱藏
- 不要使用ClearColor安寺,無(wú)背景色,透明度也不要設(shè)置為0
- 渲染耗時(shí)比較長(zhǎng)
- 使用局部更新
- 如果只是更新某組的話首尼,使用reloadSection進(jìn)行局部更
- 加載網(wǎng)絡(luò)數(shù)據(jù)挑庶,下載圖片,使用異步加載软能,并緩存
- 少使用addView 給cell動(dòng)態(tài)添加view
- 按需加載cell迎捺,cell滾動(dòng)很快時(shí),只加載范圍內(nèi)的cell
- 不要實(shí)現(xiàn)無(wú)用的代理方法查排,tableView只遵守兩個(gè)協(xié)議
- 緩存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在凳枝,這兩者同時(shí)存在才會(huì)出現(xiàn)“竄動(dòng)”的bug。所以我的建議是:只要是固定行高就寫(xiě)預(yù)估行高來(lái)減少行高調(diào)用次數(shù)提升性能跋核。如果是動(dòng)態(tài)行高就不要寫(xiě)預(yù)估方法了岖瑰,用一個(gè)行高的緩存字典來(lái)減少代碼的調(diào)用次數(shù)即可
- 不要做多余的繪制工作。在實(shí)現(xiàn)drawRect:的時(shí)候砂代,它的rect參數(shù)就是需要繪制的區(qū)域蹋订,這個(gè)區(qū)域之外的不需要進(jìn)行繪制。例如上例中泊藕,就可以用CGRectIntersectsRect辅辩、CGRectIntersection或CGRectContainsRect判斷是否需要繪制image和text,然后再調(diào)用繪制方法娃圆。
- 預(yù)渲染圖像玫锋。當(dāng)新的圖像出現(xiàn)時(shí),仍然會(huì)有短暫的停頓現(xiàn)象讼呢。解決的辦法就是在bitmap context里先將其畫(huà)一遍撩鹿,導(dǎo)出成UIImage對(duì)象,然后再繪制到屏幕悦屏;
- 使用正確的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)节沦。
2、如何提升 tableview 的流暢度础爬?
本質(zhì)上是降低 CPU甫贯、GPU 的工作,從這兩個(gè)大的方面去提升性能看蚜。
CPU:對(duì)象的創(chuàng)建和銷(xiāo)毀叫搁、對(duì)象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版渴逻、圖片的格式轉(zhuǎn)換和解碼疾党、圖像的繪制
GPU:紋理的渲染
- 卡頓優(yōu)化在 CPU 層面
- 盡量用輕量級(jí)的對(duì)象,比如用不到事件處理的地方惨奕,可以考慮使用 CALayer 取代 UIView
- 不要頻繁地調(diào)用 UIView 的相關(guān)屬性雪位,比如 frame、bounds梨撞、transform 等屬性雹洗,盡量減少不必要的修改
- 盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整對(duì)應(yīng)的屬性聋袋,不要多次修改屬性
- Autolayout 會(huì)比直接設(shè)置 frame 消耗更多的 CPU 資源
- 圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
- 控制一下線程的最大并發(fā)數(shù)量
- 盡量把耗時(shí)的操作放到子線程
- 文本處理(尺寸計(jì)算队伟、繪制)
- 圖片處理(解碼、繪制)
- 卡頓優(yōu)化在 GPU層面
- 盡量避免短時(shí)間內(nèi)大量圖片的顯示幽勒,盡可能將多張圖片合成一張進(jìn)行顯示
- GPU能處理的最大紋理尺寸是 4096x4096嗜侮,一旦超過(guò)這個(gè)尺寸,就會(huì)占用 CPU 資源進(jìn)行處理啥容,所以紋理盡量不要超過(guò)這個(gè)尺寸
- 盡量減少視圖數(shù)量和層次
- 減少透明的視圖(alpha<1)锈颗,不透明的就設(shè)置 opaque 為 YES
- 盡量避免出現(xiàn)離屏渲染
3、iOS 保持界面流暢的技巧咪惠?
- 預(yù)排版击吱,提前計(jì)算
- 在接收到服務(wù)端返回的數(shù)據(jù)后,盡量將 CoreText 排版的結(jié)果遥昧、單個(gè)控件的高度覆醇、cell 整體的高度提前計(jì)算好,將其存儲(chǔ)在模型的屬性中炭臭。需要使用時(shí)永脓,直接從模型中往外取,避免了計(jì)算的過(guò)程鞋仍。
- 盡量少用 UILabel常摧,可以使用 CALayer 。避免使用 AutoLayout 的自動(dòng)布局技術(shù)威创,采取純代碼的方式
- 預(yù)渲染落午,提前繪制
- 例如圓形的圖標(biāo)可以提前在,在接收到網(wǎng)絡(luò)返回?cái)?shù)據(jù)時(shí)肚豺,在后臺(tái)線程進(jìn)行處理溃斋,直接存儲(chǔ)在模型數(shù)據(jù)里,回到主線程后直接調(diào)用就可以了
- 避免使用 CALayer 的 Border吸申、corner梗劫、shadow寞奸、mask 等技術(shù),這些都會(huì)觸發(fā)離屏渲染在跳。
- 異步繪制
- 全局并發(fā)線程
- 高效的圖片異步加載
4、APP啟動(dòng)時(shí)間應(yīng)從哪些方面優(yōu)化隐岛?
App啟動(dòng)時(shí)間可以通過(guò)xcode提供的工具來(lái)度量猫妙,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,將環(huán)境變量DYLD_PRINT_STATISTICS設(shè)為YES聚凹,優(yōu)化需以下方面入手
- dylib loading time
- 核心思想是減少dylibs的引用
- 合并現(xiàn)有的dylibs(最好是6個(gè)以內(nèi))
- 使用靜態(tài)庫(kù)
- rebase/binding time
- 核心思想是減少DATA塊內(nèi)的指針
- 減少Object C元數(shù)據(jù)量割坠,減少Objc類(lèi)數(shù)量,減少實(shí)例變量和函數(shù)(與面向?qū)ο笤O(shè)計(jì)思想沖突)
- 減少c++虛函數(shù)
- 多使用Swift結(jié)構(gòu)體(推薦使用swift)
- ObjC setup time
- 核心思想同上妒牙,這部分內(nèi)容基本上在上一階段優(yōu)化過(guò)后就不會(huì)太過(guò)耗時(shí)
- initializer time
- 使用initialize替代load方法
- 減少使用c/c++的attribute((constructor))彼哼;推薦使用
dispatch_once() pthread_once() std:once()
等方法 - 推薦使用swift
- 不要在初始化中調(diào)用
dlopen()
方法,因?yàn)榧虞d過(guò)程是單線程湘今,無(wú)鎖敢朱,如果調(diào)用dlopen則會(huì)變成多線程,會(huì)開(kāi)啟鎖的消耗摩瞎,同時(shí)有可能死鎖 - 不要在初始化中創(chuàng)建線程