Objective C/C/C++
使用的編譯器前端是Clang,Swift是swift,後端都是LLVM.前端
二、3爲編譯器前端處理ios
靜態庫:連接時,靜態庫會被完整地複製到可執行文件中,被屢次使用就有多份冗餘拷貝c++
系統動態庫:連接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存git
一個NSObject對象佔用多少內存?web
系統分配了16個字節給NSObject對象(經過malloc_size函數得到) 但NSObject對象內部只使用了8個字節的空間(64bit環境下,能夠經過class_getInstanceSize函數得到)
複製代碼
對象的isa指針指向哪裏?面試
instance對象的isa指向class對象 class對象的isa指向meta-class對象 meta-class對象的isa指向基類的meta-class對象
複製代碼
OC的類信息存放在哪裏?objective-c
對象方法、屬性、成員變量、協議信息,存放在class對象中 類方法,存放在meta-class對象中 成員變量的具體值,存放在instance對象
複製代碼
class_rw_t 與 class_ro_t 區別算法
class_rw_t結構體內有一個指向class_ro_t結構體的指針.
class_ro_t存放的是編譯期間就肯定的;而class_rw_t是在runtime時才肯定,它會先將class_ro_t的內容拷貝過去,而後再將當前類的分類的這些屬性、方法等拷貝到其中。因此能夠說class_rw_t是class_ro_t的超集
固然實際訪問類的方法、屬性等也都是訪問的class_rw_t中的內容
屬性(property)存放在class_rw_t中,實例變量(ivar)存放在class_ro_t中。
複製代碼
msg_send 消息轉發swift
分爲三個階段
一、動態方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run)) {
return NO;//返回 NO, 纔會執行第二步
}
return [super resolveInstanceMethod:sel];
}
二、快速轉發
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
// return [Dog new]; //替換其餘消息接受者
return nil; //返回nil 則會走到第3階段,徹底消息轉發機制(慢速轉發)
}
return [super forwardingTargetForSelector:aSelector];
}
三、徹底消息轉發
3.1方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
Dog *dog = [Dog new];
return [dog methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
3.2 消息轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Dog *dog = [Dog new];
if ([dog respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:dog];
} else {
[super forwardInvocation:anInvocation];
}
}
複製代碼
iOS用什麼方式實現對一個對象的KVO?(KVO的本質是什麼?)後端
利用RuntimeAPI動態生成一個子類,而且讓instance對象的isa指向這個全新的子類 當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueAndNotify函數 willChangeValueForKey: 父類原來的setter didChangeValueForKey: 內部會觸發監聽器(Oberser)的監聽方法( observeValueForKeyPath:ofObject:change:context:)
複製代碼
IMP、SEL、Method的區別
SEL是方法編號,也是方法名
IMP是函數實現指針,找IMP就是找函數實現的過程
Method就是具體的實現
SEL和IMP的關係就能夠解釋爲:
SEL就至關於書本的⽬錄標題
IMP就是書本的⻚碼
Method就是具體頁碼對應的內容
SEL是在dyld加載鏡像到內存時,經過_read_image方法加載到內存的表中了
複製代碼
AssociationsManager類 管理了着一個鎖還有一個全局的哈希鍵值對錶.
// 初始化一個AssociationsManager實例對象的時候,會獲取一個分配了內存的鎖,而且調用它的assocations()方法來懶加載獲取哈希表
AssociationsHashMap
ObjectAssociationMap
ObjectAssociation
複製代碼
@synchronized
使用@synchronized雖然解決了多線程的問題,可是並不完美。由於只有在single未建立時,咱們加鎖纔是有必要的。若是single已經建立.這時候鎖不只沒有好處,並且還會影響到程序執行的性能(多個線程執行@synchronized中的代碼時,只有一個線程執行,其餘線程須要等待)。
@synchronized是一把支持多線程遞歸的互斥鎖
objc_sync_enter跟objc_sync_exit
複製代碼
dispatch_once
當onceToken= 0時,線程執行dispatch_once的block中代碼 當onceToken= -1時,線程跳過dispatch_once的block中代碼不執行 當onceToken爲其餘值時,線程被阻塞,等待onceToken值改變
複製代碼
發送通知所在的線程就是接收通知所在的線程
兩張表 Named Table NotificationName做爲表的key, nameless table 沒有傳入NotificationName wildcard
一、傳入了NotificationName,則會以NotificationName爲key去查找對應的Value,若找到value,則取出對應的value;若未找到對應的value,則新建一個table,而後將這個table以NotificationName爲key添加到Named Table中。
二、若是沒有傳入NotificationName,直接根據對應的 object 爲key去找對應的鏈表而已。
三、若是既沒有傳入NotificationName也沒有傳入object,則這個觀察者會添加到wildcard鏈表中。
複製代碼
使用 copy 的目的是爲了讓本對象的屬性不受外界影響,使用 copy 不管給我傳入是一個可變對象仍是不可對象,我自己持有的就是一個不可變的副本.
若是咱們使用 strong ,那麼這個屬性就有可能指向一個可變對象,若是這個可變對象在外部被修改了,那麼會影響該屬性.
NSMutableString *string = [[NSMutableString alloc] initWithString:@"0"];
NSString *copyString = [string mutableCopy];
NSString *strongString = string;
[string appendString:@"1"];
NSLog(@"copyString = %@", copyString);
NSLog(@"strongString = %@", strongString);
複製代碼
不可變對象 copy 指針拷貝 淺拷貝 僅此一種狀況
copy | mutableCopy | |
---|---|---|
不可變對象 | 淺拷貝 (指針拷貝) | 深拷貝 |
可變對象 | 深拷貝 | 深拷貝 |
weak表實際上是一個hash表,key 是所指對象的地址,value 是 weak 指針的地址數組,
sideTable是一個結構體,內部主要有引用計數表和弱引用表兩個成員,內存存儲的其實都是對象的地址、引用計數和weak變量的地址,而不是對象自己的數據
alloc
方法封裝init
只是一種工廠設計方案,爲了方便子類重寫:自定義實現,提供一些初始化就伴隨的東西new
封裝了 alloc 和init
一、_objc_rootDealloc
if (fastpath(isa.nonpointer && //無指針指向
!isa.weakly_referenced && //無弱引用
!isa.has_assoc && //無關聯對象
!isa.has_cxx_dtor && //無cxx析構函數
!isa.has_sidetable_rc)) //無散列表引用計數
{
assert(!sidetable_present());
free(this); //直接釋放
}
else {
object_dispose((id)this);//則作其餘操做
}
複製代碼
二、dispose
objc_destructInstance
是否有c++析構函數
是否存在關聯對象
obj->clearDeallocating();
free
複製代碼
一、轉成 cgImage
二、建立上下文對象 contextRef (位圖信息bitmapInfo)
三、將cgimage 畫在 上下文上
四、獲得新的 cgImage
五、uiimage *newImage = CGBitMapContextCreateImage( cgImage);
block的原理是什麼 本質是什麼
本質是一個 oc 對象, 內部也有一個 isa 指針
內部封裝了 block 執行邏輯的函數
複製代碼
結構體對象
int age = 20;
void (^block) (void) = ^ {
NSLog(@"age is %d", age);
};
複製代碼
變量自動捕獲👇
struct __main_block_impl_0 {
struct __block_impl impl; //impl 結構體見👇
struct __main_block_desc_0* Desc;
int age;// 自動變量捕獲
}
複製代碼
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //指向 block 內部實現的函數地址 (見👇)
}
複製代碼
// 封裝了 block 執行邏輯的函數
static void __main_block_func_0 () {
//TODO
}
複製代碼
auto 值傳遞
static 指針傳遞
全局變量 不捕獲 直接訪問
局部變量須要捕獲是由於須要跨函數
訪問
繼承自NSBlock類型
globleBlock 沒有訪問 auto 變量 (訪問 static 和 全局變量仍讓是 globelBlock)
stackBlock 訪問了 auto 變量 (MRC 下能打印出來, ARC下會自動調動 copy ---> mallocBlock)
stackBlock ----> mallocBlock 調用了 copy (棧 --- > 堆上)
複製代碼
__block 可解決 block 內部沒法修改 auto 變量的問題
複製代碼
編譯器會將__block變量包裝成一個對象 __Block_byref_xxx_0 結構體
複製代碼
基本數據類型 int age = 0;
編譯器會將 age 包裝成 __Block_byref_age_0
結構體
1.__main_block_impl_0 結構體內持有 __Block_byref_age_0
2.__Block_byref_age_0 內部持有 __forwarding 指針指向本身
複製代碼
當 block 被 copy 到堆上時,會調用block內部的 copy 函數,copy 函數會調用 __Block_object_assign
一個運行着的程序就是一個進程或者一個任務。每一個進程至少有一個線程,線程就是程序的執行流。建立好一個進程的同時,一個線程便同時開始運行,也就是主線程。每一個進程有本身獨立的虛擬內存空間,線程之間共用進程的內存空間。有些線程執行的任務是一條直線,起點到終點;在 iOS 中,圓型的線程就是經過run loop不停的循環實現的。
1.每一個線程包括主線程都有與之對應的 runloop 對象,線程和 runloop 對象是一一對應的;
2.Runloop 保存在一個全局字典中,線程爲key, runloop爲value CFDictionaryGetValue
3.主線程會默認開啓 runloop , 子線程默認不會開啓,須要手動開啓
4.runloop 在第一次獲取時建立,在線程結束時銷燬
複製代碼
首先由Source1捕捉系統事件,而後包裝成eventqueue,傳遞給Source0處理觸摸事件
複製代碼
Entry :
beforeTimers :
beforeSources
beforeWaiting
afterWaiting
exit
複製代碼
通知 observers:RunLoop 要開始進入 loop 了,(kCFRunLoopEntry)
開啓一個 do while 來保活線程,通知 observers :
通知 Observers:RunLoop 的線程將進入休眠(sleep)狀態 (kCFRunLoopBeforeWaiting)
進入休眠後,會等待 mach_port的消息,以再次喚醒;只有在下面四個事件
喚醒時,通知 Observer: Runloop 的線程剛剛被喚醒了 (kCFRunLoopAfterWaiting)
被喚醒後,開始處理消息
若是是 Timer 時間到的話,就觸發 Timer 的回調;
若是是 dispatch 的話,就執行 block;
若是是 source1 事件的話,就處理這個事件。
消息執行完後,就執行到loop裏的block
根據當前runloop的狀態來判斷是否須要走下一個 loop。當被外部強制中止或loop 超時,就不繼續下一個loop了,不然繼續走下一個loop
mode做用是用來隔離, 將不一樣組的Source0、Source一、timer、Observer 隔離開來,互不影響
主要有
defaultMode : app的默認 mode,一般主線程在這個mode下運行
UITrackingMode : 界面追蹤 mode, 用於scrollview追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
複製代碼
控制線程生命週期(線程保活)
檢測應用卡頓
性能優化
用戶態和內核態之間的相互切換
mach_msg()
用戶態 ----> 內核態 (等待消息)
內核態 ---->用戶態 (處理消息)
內核態:
等待消息
沒有消息就讓線程休眠
有消息就喚醒線程
複製代碼
viewDidLoad
和viewWillAppear
在同一個RunLoop循環中
UIApplicationMain 啓動了 runloop
@autoreleasePool = __AtAutoreleasePool __autoreleasePool
__AtAutoreleasePool 結構體
複製代碼
AutoreleasePool 是 oc 的一種內存回收機制,正常狀況下變量在超出做用域的時候 release,可是若是將變量加入到 pool 中,那麼release 將延遲執行
AutoreleasePool 並無單獨的結構,而是由若干個 AutoreleasePoolPage 以**雙向鏈表**形式組成
1. PAGE_MAX_SIZE :4KB,虛擬內存每一個扇區的大小,內存對齊
2. 內部 thread ,page 當前所在的線程,AutoreleasePool是按線程一一對應的
3. 自己的成員變量佔用56字節,剩下的內存存儲了調用 autorelease 的變量的對象的地址,同時將一個哨兵插入page中
4. pool_boundry 哨兵標記,哨兵其實就是一個空地址,用來區分每個page 的邊界
5. 當一個Page被佔滿後,會新建一個page,並插入哨兵標記
複製代碼
單個自動釋放池的執行過程就是objc_autoreleasePoolPush()
—> [object autorelease]
—> objc_autoreleasePoolPop(void *)
具體實現以下:
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
複製代碼
內部其實是對 AutoreleasePoolPage 的調用
每當自動釋放池調用 objc_autoreleasePoolPush 時,都會把邊界對象放進棧頂,而後返回邊界對象,用於釋放。
AutoreleasePoolPage::push();
調用👇
static inline void *push() {
return autoreleaseFast(POOL_BOUNDARY);
}
複製代碼
autoreleaseFast
👇
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
複製代碼
👆上述方法分三種狀況選擇不一樣的代碼執行:
- 有 hotPage 而且當前 page 不滿,調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
- 有 hotPage 而且當前 page 已滿,調用 autoreleaseFullPage 初始化一個新的頁,調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
- 無 hotPage,調用 autoreleaseNoPage 建立一個 hotPage,調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
最後的都會調用 page->add(obj) 將對象添加到自動釋放池中。 hotPage 能夠理解爲當前正在使用的 AutoreleasePoolPage。
複製代碼
是以棧的形式存在,而且內部對象經過進棧、出棧對應着 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop
當咱們對一個對象發送一條 autorelease 消息時,其實是將這個對象地址加入到 autoreleasePoolPage 的棧頂 next 指針的指向的位置
複製代碼
iOS 在主線程註冊了兩個 observer
__第一個observer __
監聽了 kCFRunloopEntry, 會調用 objc_autoreleasePool_push()
第二個 observer
監聽了 kCFRunloopBeforeWaiting 會調用 objc_autoreleasePool_pop() 、objc_autoreleasePool_push()
監聽了 kCFRunloopExit 事件,會調用 objc_autoreleasePool_pop()
同步、異步 Dispatch_async 和 dispatch_sync 決定了是否開啓新的線程
併發、串行 concurrent 、serial 隊列的類型決定了任務的執行方式
使用 sync 向當前串行隊列中添加任務,會卡住當前的串行隊列(產生死鎖)
High-level lock
自旋鎖再也不安全 等待鎖的線程會處於忙等狀態,一直佔用着CPU的資源
可能會出現優先級反轉的問題
從底層調用來看,等待 os_unfair_lock 鎖的線程處於休眠狀態,並不是忙等
須要銷燬
pthread_cond_t
pthread_cond_wait
pthread_cond_signal
複製代碼
對 pthread_mutex 默認封裝
對 NSConditionLock 和 NSCondition的封裝
wait
signal
gcd 串行隊列
互斥遞歸鎖
@synthronized(obj) obj 傳遞進去 syncData(hashmap)一個 obj 對應一把鎖 (pmutext_lock)
obj對應的遞歸鎖,而後進行加鎖、解鎖操做
進入SyncData的定義,是一個結構體,主要用來表示一個線程data,相似於鏈表結構,有next指向,且封裝了recursive_mutex_t屬性,能夠確認@synchronized確實是一個遞歸互斥鎖
複製代碼
自旋鎖 (不休眠)
預計線程等待鎖的時間很短
CPU資源不緊張
互斥鎖
預計等待鎖的時間較長
有IO操做
CPU資源緊張
複製代碼
atomic 讀寫加鎖 可是 release 不加鎖
pthread_rwlock : 讀寫鎖
等待的鎖 會進入休眠
複製代碼
dispatch_barrier_async:異步柵欄函數
queue 必須是本身手動建立的併發隊列
複製代碼
NSProxy 沒有 init 方法
若是調用 nsproxy 發送消息,他會直接調用
0x00400000
**開始訪問堆區內存時,通常是先經過對象讀取到對象所在的棧區的指針地址,而後經過指針地址訪問堆區
複製代碼
內存從高到底分配 內存是連續的
複製代碼
具體內存佈局👇
小對象是不會進行retain和release操做的 所以不用擔憂過分釋放問題
專門用來處理小對象
,例如NSNumber、NSDate、小NSString等
NSTaggedPointerString類型
,存在常量區
1.NSTaggedPointerString
:標籤指針,是蘋果在64位
環境下對NSString、NSNumber
等對象作的優化
。對於NSString對象來講
字符串是由數字、英文字母組合且長度小於等於9
時,會自動成爲NSTaggedPointerString
類型,存儲在常量區
中文或者其餘特殊符號
時,會直接成爲__NSCFString
類型,存儲在堆區
2.__NSCFString
:是在運行時
建立的NSString子類
,建立後引用計數會加1
,存儲在堆上
3.__NSCFConstantString
:字符串常量
,是一種編譯時常量
,retainCount值很大
,對其操做,不會引發引用計數變化,存儲在字符串常量區
從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲
在沒有使用Tagged Pointer以前, NSNumber等對象須要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
使用Tagged Pointer以後,NSNumber指針裏面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中
當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據
objc_msgSend能識別Tagged Pointer,好比NSNumber的intValue方法,直接從指針提取數據,節省了之前的調用開銷
如何判斷一個指針是否爲Tagged Pointer?
iOS平臺,最高有效位是1(第64bit) 16進制轉爲2進制
isTaggerPointer
pointer & 1
Mac平臺,最低有效位是1
複製代碼
DEMO
崩潰緣由是多條線程
對同一個對象
進行釋放
,致使對象過分釋放
,因此纔會崩潰。
優化64位地址
兩個表
,分別是引用計數表
、弱引用表
Charles 是如何抓包的
微信卡頓三方matrix
建立一個 CFRunLoopObserverContext 觀察者,將建立好的觀察者 observer 添加到主線程runloop 的 common 模式下觀察。而後建立一個持續的子線程專門用來監控主線程的runloop 狀態。
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
複製代碼
一旦發現進入休眠前的 beforeSources 狀態,或者喚醒後的狀態 afterWaiting ,在設置的時間(閾值)內一直沒有變化,便可斷定爲卡頓
定義:在當前屏幕緩衝區外開闢一個緩衝區進行渲染操做
離屏渲染消耗性能的緣由
一、須要建立新的緩衝區
二、離屏渲染的過程當中,須要屢次切換上下文環境,先是從當前屏幕切換到離屏;等到離屏渲染完畢後,將離屏緩衝區的渲染結果顯示到屏幕上,又須要將上下文環境從離屏切換到當前屏幕
複製代碼
離屏渲染產生的緣由
一、光柵化 layer.shouldRasterize = YES;
二、遮罩 layer.mask
三、圓角 可以使用 coreGraphic 繪製圓角解決
四、陰影 layer.shadowxxx 若是設置了 path 就不會觸發離屏渲染
複製代碼
一、CPU
**計算要顯示的內容,包括如下幾個方面:**👇
當 runloop 在 beforewaiting 和 exit 時通知註冊的監聽,而後對圖層進行打包。而後將打包數據發給專門負責渲染的獨立進程 render server
複製代碼
二、Render server
減小在短期內大量圖片的顯示
CPU
進行預處理,而後再提交給GPU
處理,致使額外CPU
資源消耗)AFN
SD
在渲染階段,控件樹(widget)會轉換成對應的渲染對象(RenderObject)樹,在 Rendering 層進行佈局和繪製
複製代碼
2.Relayout Boundary
爲了防止因子節點發生變化而致使的整個控件樹重繪,Flutter 加入了一個機制 relayout boundry
flutter使用邊界標記須要從新繪製和從新佈局的節點,這樣就能夠避免其餘節點被污染或者觸發重建。
就是控件大小不會影響其餘控件時,就不必從新佈局整個控件樹。有了這個機制後,不管子樹發生什麼樣的變化,處理範圍都只在子樹上。
複製代碼
3.佈局的計算
在佈局時 Flutter 深度優先遍歷渲染對象樹。數據流的傳遞方式是從上到下傳遞約束,從下到上傳遞大小。也就是說,父節點會將本身的約束傳遞給子節點,子節點根據接收到的約束來計算本身的大小,而後將本身的尺寸返回給父節點。
顯式動畫是指用戶本身經過beginAnimations:context:和commitAnimations建立的動畫。CABasicAnimation
隱式動畫是指經過UIView的animateWithDuration:animations:方法建立的動畫。
隱式動畫是ios4以後引入sdk的,以前只有顯式動畫。從官方的介紹來看,二者並無什麼差異,甚至蘋果還推薦使用隱式動畫,可是這裏面有一個問題,就是使用隱式動畫後,View會暫時不能接收用戶的觸摸、滑動等手勢。這就形成了當一個列表滾動時,若是對其中的view使用了隱式動畫,就會感受滾動沒法主動中止下來,必須等動畫結束了才能中止。
關鍵幀動畫 CAKeyframeAnimation
20210314 字節一面
isEqual && hash
參考 www.jianshu.com/p/915356e28…
==運算符 與 isEqual
==運算符只是簡單地判斷是不是同一個對象, 而isEqual方法能夠判斷對象是否相同
複製代碼
hash方法只在對象被添加至NSSet和設置爲NSDictionary的key時會調用
成員變量和 setter 方法
hook 實例 例子: KVO
id instancetype
內存佈局
tcp 和 UDP
mvc 和 mvvm
git rebase git merge 區別
算法:全排列