InterView

編譯流程

Objective C/C/C++ 使用的編譯器前端是Clang,Swift是swift,後端都是LLVM.前端

  1. 源文件
  2. 預處理階段:宏的展開,頭文件的導入
  3. 編譯階段 ①詞法分析 ②語法分析(抽象語法樹AST ) ③靜態分析 (AST -> IR) ④生成中間代碼IR
  4. 生成彙編代碼
  5. 生成目標文件 (機器代碼)mach-o
  6. 運行時連接 : 連接須要的動態和靜態庫,生成可執行文件
  7. 綁定:Bind 可執行文件 (針對不一樣架構,生成對應的可執行文件)

二、3爲編譯器前端處理ios

靜態庫 && 動態庫

靜態庫:連接時,靜態庫會被完整地複製到可執行文件中,被屢次使用就有多份冗餘拷貝c++

系統動態庫:連接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存git

底層原理

OC對象

一個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

使用 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 與 mutableCopy

不可變對象 copy 指針拷貝 淺拷貝 僅此一種狀況

copy mutableCopy
不可變對象 淺拷貝 (指針拷貝) 深拷貝
可變對象 深拷貝 深拷貝

weak

weak表實際上是一個hash表,key 是所指對象的地址,value 是 weak 指針的地址數組,

sideTable是一個結構體,內部主要有引用計數表和弱引用表兩個成員,內存存儲的其實都是對象的地址、引用計數和weak變量的地址,而不是對象自己的數據

alloc init new

  • 對象的開闢內存交由 alloc 方法封裝
  • init 只是一種工廠設計方案,爲了方便子類重寫:自定義實現,提供一些初始化就伴隨的東西
  • new 封裝了 alloc 和init

dealloc 流程

一、_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
複製代碼

UIImage 解碼

一、轉成 cgImage

二、建立上下文對象 contextRef (位圖信息bitmapInfo)

三、將cgimage 畫在 上下文上

四、獲得新的 cgImage

五、uiimage *newImage = CGBitMapContextCreateImage( cgImage);

Block

block的原理是什麼 本質是什麼

本質是一個 oc 對象, 內部也有一個 isa 指針
內部封裝了 block 執行邏輯的函數
複製代碼

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 指針傳遞

全局變量 不捕獲 直接訪問

局部變量須要捕獲是由於須要跨函數訪問

Block 類型

繼承自NSBlock類型

globleBlock 沒有訪問 auto 變量 (訪問 static 和 全局變量仍讓是 globelBlock)

​ stackBlock 訪問了 auto 變量 (MRC 下能打印出來, ARC下會自動調動 copy ---> mallocBlock)

​ stackBlock  ----> mallocBlock 調用了 copy (棧 --- > 堆上)  
複製代碼

__block的做用

__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 內存管理

當 block 被 copy 到堆上時,會調用block內部的 copy 函數,copy 函數會調用 __Block_object_assign

Runtime

Runloop

blog.csdn.net/u014600626/…

面試題

Runloop 內部實現邏輯

Runloop 和線程的關係

一個運行着的程序就是一個進程或者一個任務。每一個進程至少有一個線程,線程就是程序的執行流。建立好一個進程的同時,一個線程便同時開始運行,也就是主線程。每一個進程有本身獨立的虛擬內存空間,線程之間共用進程的內存空間。有些線程執行的任務是一條直線,起點到終點;在 iOS 中,圓型的線程就是經過run loop不停的循環實現的。
1.每一個線程包括主線程都有與之對應的 runloop 對象,線程和 runloop 對象是一一對應的;
2.Runloop 保存在一個全局字典中,線程爲key, runloop爲value CFDictionaryGetValue
3.主線程會默認開啓 runloop , 子線程默認不會開啓,須要手動開啓
4.runloop 在第一次獲取時建立,在線程結束時銷燬
複製代碼

timer 和 Runloop 的關係

Runloop 是怎麼相應用戶操做的,具體操做流程是什麼

首先由Source1捕捉系統事件,而後包裝成eventqueue,傳遞給Source0處理觸摸事件
複製代碼

Runloop的幾種狀態

Entry : 
beforeTimers : 
beforeSources
beforeWaiting
afterWaiting
exit
複製代碼

Runloop 原理

  1. 通知 observers:RunLoop 要開始進入 loop 了,(kCFRunLoopEntry)

  2. 開啓一個 do while 來保活線程,通知 observers :

    • runloop 會觸發 timers, source0 回調,(kCFRunLoopBeforeTimers kCFRunLoopBeforeSources)
    • 接着執行加入的 block
    • 接下來觸發 Source0 回調,若是有 Source1是 ready 狀態的話,就會跳到 handle_msg 去處理消息,
  3. 通知 Observers:RunLoop 的線程將進入休眠(sleep)狀態 (kCFRunLoopBeforeWaiting)

  4. 進入休眠後,會等待 mach_port的消息,以再次喚醒;只有在下面四個事件

    • 基於 port 的 source 事件
    • timer 時間到
    • runloop 超時
    • 被調用者喚醒
  5. 喚醒時,通知 Observer: Runloop 的線程剛剛被喚醒了 (kCFRunLoopAfterWaiting)

  6. 被喚醒後,開始處理消息

    • 若是是 Timer 時間到的話,就觸發 Timer 的回調;

    • 若是是 dispatch 的話,就執行 block;

    • 若是是 source1 事件的話,就處理這個事件。

      消息執行完後,就執行到loop裏的block

  7. 根據當前runloop的狀態來判斷是否須要走下一個 loop。當被外部強制中止或loop 超時,就不繼續下一個loop了,不然繼續走下一個loop

Runloop 的mode做用是什麼

mode做用是用來隔離, 將不一樣組的Source0、Source一、timer、Observer 隔離開來,互不影響
主要有 
defaultMode : app的默認 mode,一般主線程在這個mode下運行
UITrackingMode : 界面追蹤 mode, 用於scrollview追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
複製代碼

Runloop 在實際開發中的做用

控制線程生命週期(線程保活)

檢測應用卡頓

性能優化

Runloop 休眠的實現原理

用戶態和內核態之間的相互切換

mach_msg()

用戶態 ----> 內核態 (等待消息)

內核態 ---->用戶態 (處理消息)

內核態:
等待消息
沒有消息就讓線程休眠
有消息就喚醒線程
複製代碼

other

viewDidLoadviewWillAppear在同一個RunLoop循環中

UIApplicationMain 啓動了 runloop

AutoReleasePool

AutoreleasePool的實現原理

@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

每當自動釋放池調用 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。
複製代碼

AutoreleasePoolPage

是以棧的形式存在,而且內部對象經過進棧、出棧對應着 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop
  
當咱們對一個對象發送一條 autorelease 消息時,其實是將這個對象地址加入到 autoreleasePoolPage 的棧頂 next 指針的指向的位置
複製代碼

Runloop 與 Autorelease

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 向當前串行隊列中添加任務,會卡住當前的串行隊列(產生死鎖)

OSSpinLock (自旋鎖)

High-level lock

自旋鎖再也不安全 等待鎖的線程會處於忙等狀態,一直佔用着CPU的資源

可能會出現優先級反轉的問題

os_unfair_lock

從底層調用來看,等待 os_unfair_lock 鎖的線程處於休眠狀態,並不是忙等

pthread_mutex (互斥鎖)

須要銷燬

互斥鎖 normal
遞歸鎖

條件鎖
pthread_cond_t

pthread_cond_wait

pthread_cond_signal
複製代碼

NSLock

對 pthread_mutex 默認封裝

NSRecursiveLock

NSCondition

對 NSConditionLock 和 NSCondition的封裝

wait

signal

SerialQueue

gcd 串行隊列

Semphore

@synthronized

互斥遞歸鎖

@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 發送消息,他會直接調用

內存佈局

保留區

  • 預留給系統處理nil等
  • **0x00400000**開始

代碼段

  • 編譯以後的代碼

數據段

  • 字符串變量
  • 全局變量
  • 靜態變量

  • alloc
訪問堆區內存時,通常是先經過對象讀取到對象所在的棧區的指針地址,而後經過指針地址訪問堆區
複製代碼

  • 局部變量
  • 函數參數
內存從高到底分配 內存是連續的
複製代碼

內核區

  • 系統用來進行內核處理操做的區域

具體內存佈局👇

內存管理方案

ARC

MRC

Tagged Pointer

  • 小對象是不會進行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

崩潰緣由是多條線程同一個對象進行釋放,致使對象過分釋放,因此纔會崩潰。

nonpointer_isa

  • 非指針類型的isa,主要是用來優化64位地址

SideTables

  • 散列表,在散列表中主要有兩個表,分別是引用計數表弱引用表

組件化

組件化通訊

?Beehive

?Bifrost

?Aspect

? fishhook

?設計模式

?插件化

Charles 是如何抓包的

算法

鏈表

二叉樹

?二分查找

性能優化

卡頓監測

YYFPSLabel

Runloop 監測

微信卡頓三方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

  • 圖層樹:反序列化後獲得圖層樹
  • 渲染樹:圖層樹轉化後獲得渲染樹
  • GPU:渲染樹經過 openGL接口提供給GPU,六大階段

界面優化

CPU方面
  • tableview 提早計算cell 高度
  • 圖片預解碼 sdwebimage 建立上下文環境(CGBitmapContext),從 bitmap 直接獲得圖片
  • 文本的處理 coretext
  • 儘可能避免使用透明 view
GPU 方面
  • 儘可能減小在短期內大量圖片的顯示
  • 圖片紋理尺寸4096 *4096 (當圖片超過這個尺寸時,會先由CPU進行預處理,而後再提交給GPU處理,致使額外CPU資源消耗)
  • 避免離屏渲染
  • 視圖層級

冷啓動

  1. dyld
  2. 動態庫加載
  3. runtime
  4. 加載分類
  5. main

源碼

AFN

juejin.cn/post/693906…

SD

面試

Flutter

  1. flutter 是怎麼渲染UI的
  2. Flutter 的視圖結構的抽象分爲三部分
  • widget
  • element
  • renderObject
在渲染階段,控件樹(widget)會轉換成對應的渲染對象(RenderObject)樹,在 Rendering 層進行佈局和繪製
複製代碼

2.Relayout Boundary

爲了防止因子節點發生變化而致使的整個控件樹重繪,Flutter 加入了一個機制 relayout boundry
flutter使用邊界標記須要從新繪製和從新佈局的節點,這樣就能夠避免其餘節點被污染或者觸發重建。
就是控件大小不會影響其餘控件時,就不必從新佈局整個控件樹。有了這個機制後,不管子樹發生什麼樣的變化,處理範圍都只在子樹上。
複製代碼

3.佈局的計算

在佈局時 Flutter 深度優先遍歷渲染對象樹。數據流的傳遞方式是從上到下傳遞約束,從下到上傳遞大小。也就是說,父節點會將本身的約束傳遞給子節點,子節點根據接收到的約束來計算本身的大小,而後將本身的尺寸返回給父節點。

什麼是隱式動畫

顯式動畫是指用戶本身經過beginAnimations:context:和commitAnimations建立的動畫。CABasicAnimation

隱式動畫是指經過UIView的animateWithDuration:animations:方法建立的動畫。

隱式動畫是ios4以後引入sdk的,以前只有顯式動畫。從官方的介紹來看,二者並無什麼差異,甚至蘋果還推薦使用隱式動畫,可是這裏面有一個問題,就是使用隱式動畫後,View會暫時不能接收用戶的觸摸、滑動等手勢。這就形成了當一個列表滾動時,若是對其中的view使用了隱式動畫,就會感受滾動沒法主動中止下來,必須等動畫結束了才能中止。

關鍵幀動畫 CAKeyframeAnimation

?Tableview 複用原理

20210314 字節一面

  1. isEqual && hash

    參考 www.jianshu.com/p/915356e28…

    ==運算符 與 isEqual
    ==運算符只是簡單地判斷是不是同一個對象, 而isEqual方法能夠判斷對象是否相同
    複製代碼

    hash方法只在對象被添加至NSSet和設置爲NSDictionary的key時會調用

  2. 成員變量和 setter 方法

  3. hook 實例 例子: KVO

  4. id instancetype

    1. id 在編譯時不能判斷對象的真實類型 instancetype 能夠判斷
    2. 若是init方法的返回值是instancetype,那麼將返回值賦值給一個其它的對象會報一個警告
    3. id能夠用來定義變量, 能夠做爲返回值, 能夠做爲形參;instancetype只能用於做爲返回值
  5. 內存佈局

  6. tcp 和 UDP

    1. 基於鏈接與無鏈接;
    2. 對系統資源的要求(TCP較多,UDP少);
    3. UDP程序結構較簡單;
    4. 流模式與數據報模式 ;
    5. TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。
  7. mvc 和 mvvm

  8. git rebase git merge 區別

    1. git pull = git fetch + git merge
    2. git merge 與 git rebase 結果上沒有本質區別 只是生成的節點不一樣 (rebase 一條線)
  9. 算法:全排列

相關文章
相關標籤/搜索