iOS -- 問題雜記

本篇內容不做爲任何題目的解答,僅僅是我的學習記錄,若有錯誤還請指正。算法

iOS基礎必備

1:講講你對noatomic & nonatomic的理解

atomicseter/getter內部實現是用了互斥鎖來保證seter/getter在多線程中的安全,但atomic修飾的對象是自定義的,可能並無加鎖,在多線程中atomic修飾對象並不能保證線程安全。編程

nonatomicsetter/getter方法的實現並無加互斥鎖,因此nonatomic修飾的對象是非線程安全的,同時nonatomicsetter/getter方法也是非線程安全的,但也正由於沒有互斥鎖因此性能要比atomic好。swift

(舉例:當多個線程同時調用同一屬性的讀取方法時,線程1會get到一個肯定的值,可是get的值不可控,多是線程2或者線程3...set以後的值,也多是以前的值)數組

2:被 weak 修飾的對象在被釋放的時候會發生什麼?是如何實現的?知道sideTable 麼?裏面的結構能夠畫出來麼?

weak修飾的對象在被釋放時候會被自動置爲nil緩存

runtime維護了一個weak表,用於存儲指向某個對象的全部weak指針。weak表實際上是一個hash哈希表,Key是所指對象的地址,Valueweak指針的地址數組(這個地址的值是所指對象指針的地址)。安全

一、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。bash

二、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak()的做用是更新指針指向,建立對應的弱引用表。服務器

三、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取全部weak指針地址的數組,而後遍歷這個數組把其中的數據設爲nil,最後把這個entryweak表中刪除,最後清理對象的記錄。網絡

runtime內存空間中,SideTables是一個64個元素長度的hash數組,裏面存儲了SideTableSideTableshash鍵值就是一個對象objaddress。 所以能夠說,一個obj,對應了一個SideTable。可是一個SideTable,會對應多個obj。由於SideTable的數量只有64個,因此會有不少obj共用同一個SideTable。而在一個SideTable中,又有兩個成員,分別是RefcountMap refcnts(引用計數的 hash表,其keyobj的地址,而value,則是obj對象的引用計數)和weak_table_t weak_tableweak引用全局hash表),weak_table則存儲了弱引用obj的指針的地址,其本質是一個以obj地址爲key,弱引用obj的指針的地址做爲valuehash表。hash表的節點類型是weak_entry_t多線程

3:block 用什麼修飾?strong 能夠?

由於block變量默認是聲明爲棧變量的,爲了可以在block的聲明域外使用,因此要把block copy到堆,因此說爲了block屬性聲明和實際的操做一致,blockcopy修飾。

對於strong修飾符:

首先,在OC中的三種類型的block

  • _NSConcreateGlobalBlock 全局的靜態block,不會訪問任何外部變量。
  • _NSConcreateStackBlock 保存在棧中的block,當函數返回時會被銷燬。
  • _NSConcreateMallocBlock 保存在堆區的block,當引用計數爲0時被銷燬。

MRC環境下,block存放在全局區、堆區、棧區,ARC環境下只存放在全局區、堆區(棧區block,在ARC狀況下會自動拷貝到堆區)。

因此在MRC環境下,若是block要訪問外部變量,就必須用copy修飾,而在ARC環境下,若是block訪問了外部變量,系統會自動將棧區block拷貝到堆區,因此也可使用strong。只不過copyMRC遺留下來的,習慣而已

4:block 爲何可以捕獲外界變量? __block作了什麼事?

block中使用到的局部變量,都會在編譯時動態建立的block結構體中建立一個與局部變量名稱同樣的實例變量,該實例變量存儲着外部的局部變量的值,而當執行block時,再將這裏存儲下來的值取出來。

__block所起到的做用就是隻要觀察到該變量被block所持有,就將外部變量在棧中的內存地址拷貝堆中。__block將變量包裝成對象,而後在把捕獲到的變量封裝在block的結構體裏面,block內部存儲的變量爲結構體指針,也就能夠經過指針找到內存地址進而修改變量的值。

5:談談你對事件的傳遞鏈和響應鏈的理解

傳遞鏈:

(1)發生觸摸事件後,系統會利用Runloop將該事件加入到一個由UIApplication管理的隊列事件中.

(2)UIApplication會從事件隊列中取出最前面的事件,並將事件分發下去處理,發送事件給應用程序的主窗口UIWindow.

(3)主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件

(4)遍歷找到合適的視圖控件後,就會調用視圖控件的touches方法來處理事件:touchesBegin...此子控件就是咱們須要找到的第一響應者

響應鏈:

(1)判斷第一響應者可否響應事件,若是第一響應者能進行響應,則事件在響應鏈中的傳遞終止。若是第一響應者不能響應,則將事件傳遞給 nextResponder也就是一般的superview進行事件響應

(2)若是事件繼續上傳至UIWindow而且沒法響應,它將會把事件繼續上報給UIApplication

(3)若是事件繼續上報至UIApplication而且也沒法響應,它將會將事件上報給其Delegate

(4)若是最終事件依舊未被響應則會被系統拋棄

注: 穿透事件,擴大響應區域。

6:談談 KVC 以及 KVO 的理解?

KVC(key-value-coding)鍵值編碼,是一種間接訪問實例變量的方法。提供一種機制來間接訪問對象的屬性。

一、給私有變量賦值。

二、給控件的內部屬性賦值(如自定義UITextFiledclearButton,或placeholder的顏色,通常可利用runtime獲取控件的內部屬性名,Ivar *ivar = class_getInstanceVariable獲取實例成員變量)。

[textField setValue:[UIColor redColor] forKeyPath:@"placeholderLabel.textColor"];
複製代碼

三、結合Runtimemodel和字典的轉換(setValuesForKeysWithDictionaryclass_copyIvarList獲取指定類的Ivar成員列表)

KVO是一種基於KVC實現的觀察者模式。當指定的被觀察的對象的屬性更改了,KVO會以自動或手動方式通知觀察者。

監聽 ScrollViewcontentOffSet屬性

[scrollview addObserver:self forKeyPath:@"contentOffset"  options:NSKeyValueObservingOptionNew context:nil];
複製代碼

7:RunLoop 的做用是什麼?它的內部工做機制瞭解麼?

RunLoop基本做用:

(1).使程序一直運行並接受用戶輸入:

程序一啓動就會開一個主線程,主線程一開起來就會跑一個主線程對應的RunLoopRunLoop保證主線程不會被銷燬,也就保證了程序的持續運行。

(2)決定程序在什麼時候應該處理哪些Event

好比:touches事件,timer事件,selector事件

(3)節省CPU時間 程序運行起來,沒有任何事件源的時候,RunLoop會告訴CPU,要進入休眠狀態,這時CPU就會將其資源釋放出來去作其餘的事情,當有事件源來的時候RunLoop就會被喚醒處理事件源。

8:蘋果是如何實現 autoreleasepool的?

autoreleasepool以一個隊列數組的形式實現,主要經過下列三個函數完成: (1).objc_autoreleasepoolPush (2).objc_autoreleasepoolPop (3).objc_autorelease

經過函數名就能夠知道,對autorelease分別執行push,和pop操做。銷燬對象時執行release操做。

iOS -- Autorelease & AutoreleasePool

9:談談你對 FRP (函數響應式) 的理解

函數響應式編程--rxswift

10:平時開發有沒有玩過 Instrument ?

Instruments裏面工具不少,經常使用的有:

(1).Time Profiler:性能分析,用來檢測應用CPU的使用狀況.能夠看到應用程序中各個方法正在消耗CPU時間。

(2).Zoombies:檢查是否訪問了殭屍對象,可是這個工具只能從上往下檢查,不智能

(3).Allocations:用來檢查內存,寫算法的那批人也用這個來檢查

(4).Leaks:檢查內存,看是否有內存泄漏

(5).Core Animation:評估圖形性能,這個選項檢查了圖片是否被縮放,以及像素是否對齊。被放縮的圖片會被標記爲黃色,像素不對齊則會標註爲紫色。黃色、紫色越多,性能越差。

Runtime

1:什麼是 isa,isa 的做用是什麼?

2:一個實例對象的isa 指向什麼?類對象指向什麼?元類isa 指向什麼?

是一個Class類型的指針.

每一個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從自己查找類方法的實現,若是沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向自己,這樣造成了一個封閉的內循環。

3:objc 中類方法和實例方法有什麼本質區別和聯繫?

類方法:

(1).類方法是屬於類對象的

(2).類方法只能經過類對象調用

(3).類方法中的self是類對象

(4).類方法能夠調用其餘的類方法

(5).類方法中不能訪問成員變量

(6).類方法中不定直接調用對象方法

實例方法:

(1).實例方法是屬於實例對象的

(2).實例方法只能經過實例對象調用

(3).實例方法中的self是實例對象

(4).實例方法中能夠訪問成員變量

(5).實例方法中直接調用實例方法

(6).實例方法中也能夠調用類方法(經過類名)

4:load 和 initialize 的區別?

共同點:

(1).+load+initialize會被自動調用,不能手動調用它們。

(2).子類實現了+load+initialize的話,會隱式調用父類的+load+initialize方法

(3).+load+initialize方法內部使用了鎖,所以它們是線程安全的。

不一樣點:

+load方法是在runtime加載類、分類的時候調用(根據函數地址直接調用,默認執行),每一個類,分類的+load方法都只會調用一次。

先執行父類的+load方法,再執行子類的+load方法,而分類的+load方法會在它的主類的+load方法以後執行。(分類按照編譯順序前後調用)

+initialize方法在第一次給某個類發送消息時調用(經過objc_msgSend調用),而且只會調用一次,是懶加載模式,此時全部的類都已經加載到了內存中,若是這個類一直沒有使用,就不會調用+initialize方法。

先執行父類的+initialize方法,再執行子類的+initialize方法,若是子類沒有實現+initialize方法時,會調用父類的+initialize方法,因此父類的+initialize方法會調用屢次,若是分類實現了+initialize方法,就會'覆蓋掉'類自己的+initialize方法。

+load方法通常是用來交換方法Method Swizzle+initialize方法主要用來對一些不方便在編譯期初始化的對象進行賦值,或者說對一些靜態常量進行初始化操做

5:_objc_msgForward 函數是作什麼的?直接調用會發生什麼問題?

_objc_msgForwardIMP 類型(函數指針),用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward會嘗試作消息轉發。

_objc_msgForward消息轉發作的幾件事:

(1).調用resolveInstanceMethod:方法 (或 resolveClassMethod:)。容許用戶在此時爲該 Class動態添加實現。若是有實現了,則調用並返回YES,那麼從新開始objc_msgSend流程。這一次對象會響應這個選擇器,通常是由於它已經調用過class_addMethod。若是仍沒實現,繼續下面的動做。

(2).調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。若是獲取到,則直接把消息轉發給它,返回非 nil對象。不然返回nil,繼續下面的動做。注意,這裏不要返回self,不然會造成死循環。

(3).調用methodSignatureForSelector:方法,嘗試得到一個方法簽名。若是獲取不到,則直接調用doesNotRecognizeSelector拋出異常。若是能獲取,則返回非nil:建立一個NSlnvocation並傳給forwardInvocation:

(4).調用forwardInvocation:方法,將第3步獲取到的方法簽名包裝成 Invocation傳入,如何處理就在這裏面了,並返回非nil

(5).調用doesNotRecognizeSelector: ,默認的實現是拋出異常。若是第3步沒能得到一個方法簽名,執行該步驟。

一旦調用_objc_msgForward,將跳過查找IMP 的過程,直接觸發「消息轉發」,若是調用了_objc_msgForward,即便這個對象確實已經實現了這個方法,也會告訴objc_msgSend沒有找到這個方法的實現。最多見的場景是:你想獲取某方法所對應的NSInvocation對象。

6:簡述下 Objective-C 中調用方法的過程

先來看一下Method在頭文件中的定義:

typedef struct objc_method *Method;

struct objc_method {
    SEL method_name;
    char * method_types;
    IMP method_imp;
};
複製代碼

Method被定義爲一個objc_method指針,在 objc_method結構體中,包含一個 SEL 和一個IMP ,其中,SEL是一個指向objc_selector 的指針,其實就是一個保存方法名的字符串。IMP是一個「函數指針」,就是用來找到函數地址,而後執行函數。因此,Method創建了SELIMP 的關聯,當對一個對象發送消息時,會經過給出的SEL 去找到IMP,而後執行。

再加上繼承的狀況,能夠總結出:當向一個對象發送消息時,會去這個類的方法緩存裏尋找(cache methodLists)若是有緩存則跳到方法實現,不然繼續在這個類的 methodLists中查找相應的SEL,若是查不到,則經過super_class 指針找到父類,再去父類的緩存列表和方法列表裏查找,層層遞進,直到找到基類爲止。最後仍然找不到,纔會走消息轉發的流程。

7:可否想向編譯後獲得的類中增長實例變量?可否向運行時建立的類中添加實例變量?爲何?

不能向編譯後獲得的類中增長實例變量;能向運行時建立的類中添加實例變量;

由於編譯後的類已經註冊在runtime 中,類結構體中的objc_ivar_list實例變量的鏈表 和instance_size 實例變量的內存大小已經肯定,同時runtime會調用 class_setIvarLayoutclass_setWeakIvarLayout來處理strong weak引用。因此不能向存在的類中添加實例變量;

運行時建立的類是能夠添加實例變量,調用 class_addIvar函數。可是得在調用 objc_allocateClassPair 以後,objc_registerClassPair以前。

8:談談你對切面編程的理解

AOP: Aspect Oriented Programming 面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術 AOPOOP 的延續,函數式編程的一種衍生範型。(利用 Objective-C Runtime 的黑魔法Method Swizzling。)

優點:對業務邏輯的各個部分進行隔離,下降業務邏輯各部分之間的耦合度,提升程序的可重用性,提升了開發的效率。

漫談iOS AOP編程之路

iOS面向切面編程AOP實踐

網絡&多線程

1:HTTP的缺陷是什麼?

(1).通訊過程當中使用的是未加密的明文,內容會被取抓。

(2).對於服務器或者客戶端來講,不會驗證通訊方的身份,所以有可能遭到中間人的假裝。

(3).沒法驗證所發送報文的完整性和安全性,並且報文的內容也有多是中間人篡改以後發送過來的。

2:談談三次握手,四次揮手!爲何是三次握手,四次揮手?

iOS:爲何TCP鏈接要三次握手,四次揮手

3:socket 鏈接和 Http 鏈接的區別

HTTP協議是基於TCP鏈接的,是應用層協議,主要解決如何包裝數據。HTTP鏈接是一種短鏈接,客戶端向服務器發送一次請求,服務器響應後鏈接斷開,節省資源,並且服務器不能主動給客戶端響應。

Socket是對TCP/IP協議的封裝,Socket自己並非協議,而是一個調用接口(API),經過Socket,咱們才能使用TCP/IP協議。Socket鏈接是一種長鏈接,客戶端跟服務器端直接使用Socket進行鏈接,沒有規定鏈接後斷開,所以客戶端和服務器段保持鏈接通道,雙方能夠主動發送數據。Socket默認鏈接超時時間是30秒,默認大小是8K(理解爲一個數據包大小)。

4:HTTPS,安全層除了SSL還有,最新的? 參數握手時首先客戶端要發什麼額外參數

5:HTTPS是什麼?握手過程,SSL原理,非對稱加密瞭解多少

HTTPS解析

6:何時POP網路,有了 Alamofire 封裝網絡 URLSession爲何還要用Moya ?

POP:面向協議編程 面向協議 = 協議 + 擴展 + 繼承 經過協議、擴展作功能劃分,下降模塊間的耦合,加強代碼的可擴展性。iOS中有一個不足之處就是多重繼承,而協議正好可以解決多重繼承的問題。在Swift中結構體變的更增強大了,不只能定義屬性,還能定義方法,還能多重繼承協議,這是OC所不提供的。

若是已經使用Alamofire進行抽象URLSession,爲何不使用某些方法來抽象URL,參數等的實質呢,所以,Moya的基本思想是,咱們須要一些網絡抽象層,以充分封裝實際直接調用Alamofire的層。

7:如何實現 dispatch_once

dispatch_once能保證任務只會被執行一次,即便同時多線程調用也是線程安全的。經常使用於建立單例、swizzeld method等功能。

@implementation ZHClass

+ (id)sharedInstance {
    static ZHClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
複製代碼

8:可否寫一個讀寫鎖?談談具體的分析

信號量方式:

- (void)viewDidLoad {
	[super viewDidLoad];
	// Do any additional setup after loading the view.
	self.semaphore = dispatch_semaphore_create(1);
	
	for (NSInteger i = 0; i < 10; i ++) {
		[[[NSThread alloc]initWithTarget:self selector:@selector(read) object:nil]start];
		[[[NSThread alloc]initWithTarget:self selector:@selector(write) object:nil]start];
	}
}

- (void)read{
	dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
	NSLog(@"%s",__func__);
	dispatch_semaphore_signal(self.semaphore);
}
- (void)write{
	dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
	NSLog(@"%s",__func__);
	dispatch_semaphore_signal(self.semaphore);
}
複製代碼

pthread_rwlock_t 方式:

@property (nonatomic,assign) pthread_rwlock_t rwlock;

//初始化讀寫鎖
pthread_rwlock_init(&_rwlock, NULL);
	
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 3; i ++) {
		dispatch_async(queue, ^{
			[[[NSThread alloc]initWithTarget:self selector:@selector(readPthreadRWLock) object:nil]start];
			[[[NSThread alloc]initWithTarget:self selector:@selector(writePthreadRWLock) object:nil]start];
		});
	}
	
//讀
- (void)readPthreadRWLock{
    pthread_rwlock_rdlock(&_rwlock);
    NSLog(@"讀文件");
    sleep(1);
    pthread_rwlock_unlock(&_rwlock);
}
// 寫
- (void)writePthreadRWLock{
    pthread_rwlock_wrlock(&_rwlock);
    NSLog(@" 寫入文件");
    sleep(1);
    pthread_rwlock_unlock(&_rwlock);
}

//銷燬鎖
- (void)dealloc{
	pthread_rwlock_destroy(&_rwlock);
}
複製代碼

dispatch_barrier方式:

//異步隊列
self.testqueue = dispatch_queue_create("rw.thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i ++) {
	dispatch_async(queue, ^{
		[self readBarryier];
		[self readBarryier];
		[self readBarryier];
		[self writeBarrier];
	});
}

- (void)readBarryier{
	dispatch_async(self.testqueue, ^{
		NSLog(@"讀文件 %@",[NSThread currentThread]);
		sleep(2);
	});
}
- (void)writeBarrier{
	dispatch_barrier_async(self.testqueue, ^{
		NSLog(@"寫入文件 %@",[NSThread currentThread]);
		sleep(1);
	});
}
複製代碼

9:何時會出現死鎖?如何避免?

死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。是操做系統層面的一個錯誤,是進程死鎖的簡稱。

死鎖的產生知足一些特定條件:

(1).互斥條件:進程對於所分配到的資源具備排它性,即一個資源只能被一個進程佔用,直到被該進程釋放。

(2).請求和保持條件:一個進程因請求被佔用資源而發生阻塞時,對已得到的資源保持不放。

(3).不剝奪條件:任何一個資源在沒被該進程釋放以前,任何其餘進程都沒法對他剝奪佔用。

(4).循環等待條件:當發生死鎖時,所等待的進程一定會造成一個環路(相似於死循環),形成永久阻塞。

10:有哪幾種鎖?各自的原理?它們之間的區別是什麼?最好能夠結合使用場景來講

(1).NSLock實現了最基本的互斥鎖,遵循了 NSLocking協議,經過lockunlock 來進行鎖定和解鎖。

(2).NSRecursiveLock遞歸鎖,能夠被一個線程屢次得到,而不會引發死鎖。它記錄了成功得到鎖的次數,每一次成功的得到鎖,必須有一個配套的釋放鎖和其對應,這樣纔不會引發死鎖。只有當全部的鎖被釋放以後,其餘線程才能夠得到鎖

(3).NSCondition 是一種特殊類型的鎖,經過它能夠實現不一樣線程的調度。一個線程被某一個條件所阻塞,直到另外一個線程知足該條件從而發送信號給該線程使得該線程能夠正確的執行。

(4).NSConditionLock 對象所定義的互斥鎖能夠在使得在某個條件下進行鎖定和解鎖。它和 NSCondition 很像,但實現方式是不一樣的。

(5).pthread_mutex,互斥鎖是一種超級易用的互斥鎖,使用的時候,只須要初始化一個pthread_mutex_tpthread_mutex_lock來鎖定 pthread_mutex_unlock 來解鎖,當使用完成後,記得調用 pthread_mutex_destroy 來銷燬鎖。

(6).pthread_rwlock,讀寫鎖,在對文件進行操做的時候,寫操做是排他的,一旦有多個線程對同一個文件進行寫操做,後果不可估量,但讀是能夠的,多個線程讀取時沒有問題的。

當讀寫鎖被一個線程以讀模式佔用的時候,寫操做的其餘線程會被阻塞,讀操做的其餘線程還能夠繼續進行。 當讀寫鎖被一個線程以寫模式佔用的時候,寫操做的其餘線程會被阻塞,讀操做的其餘線程也被阻塞。

(7).dispatch_semaphore,信號量機制實現鎖,等待信號,和發送信號,當有多個線程進行訪問的時候,只要有一個得到了信號,其餘線程的就必須等待該信號釋放。

(8).@synchronized,一個便捷的建立互斥鎖的方式,它作了其餘互斥鎖所作的全部的事情。

應當針對不一樣的操做使用不一樣的鎖:

當進行文件讀寫的時候,使用pthread_rwlock 較好,文件讀寫一般會消耗大量資源,而使用互斥鎖同時讀文件的時候會阻塞其餘讀文件線程,而 pthread_rwlock不會。 當性能要求較高時候,可使用pthread_mutex或者 dispath_semaphore,因爲OSSpinLock 不能很好的保證線程安全,而在只有在iOS10中才有 os_unfair_lock ,因此,前兩個是比較好的選擇。既能夠保證速度,又能夠保證線程安全。 對於NSLock及其子類,速度來講NSLock < NSCondition < NSRecursiveLock < NSConditionLock

持續更新中..........

相關文章
相關標籤/搜索