一.ObjC的屬性特性
1). assign:賦值特性,setter方法將入?yún)①x值給實例變量;常用于設置純量類型變量(如int,float,bool)時。
2). retain:保留特性,setter方法將入?yún)⑾缺A?,再賦值,入?yún)⒌囊糜嫈?shù)會+1;常用于修飾大部分OC對象。
3).unsafe_unretained(ARC下):非安全且非持有特性,與assign等價;可以被用來修飾對象,但容易引發(fā)野指針問題而導致Crash,不常用。
4).weak(ARC下):非持有特性,setter方法設置新值時僅將入?yún)①x值給實例變量,特性與assign類似,但所指對象遭到銷毀時會指向nil,從而避免出現(xiàn)野指針,故常用于修飾可能出現(xiàn)循環(huán)引用的對象(如代理模式中的delegate指針)。
5).strong (ARC下):持有特性,setter方法將入?yún)⑾缺A?,再賦值,入?yún)⒌囊糜嫈?shù)會+1,與retain類似;常用于修飾大部分OC對象。
6).copy:拷貝特性,setter方法將入?yún)椭埔环?,復制的對象和原對象的引用計?shù)不變;如果希望屬性變量不隨入?yún)⒆兓兓?,則常用該屬性修飾,如NSString和Block對象。
7).nonatomic:非原子特性,決定編譯器生成的setter和getter是否是原子操作,若具備該特性,則不使用同步鎖,atomic表示多線程安全,app開發(fā)一般使用nonatomic。
延伸問題:
strong和retain有什么區(qū)別?
在使用strong或retain設置新值時,保留、釋放、賦值三個操作的順序應當如何?
copy和strong有什么區(qū)別?為什么NSString和Block對象要使用copy?
自定義對象如何實現(xiàn)copy特性?
二.簡述iOS開發(fā)中常用的架構模式、設計模式
常用架構模式
架構模式的出現(xiàn)是為了管理復雜的應用程序,這樣可以在特定時間內專門關注一個方面。例如你可以在不依賴業(yè)務邏輯的情況下專注于視圖設計。同時也讓應用程序的測試更加容易,簡化了分組開發(fā),使得不同的開發(fā)人員可同時開發(fā)視圖、控制器邏輯和業(yè)務邏輯。我們經(jīng)常說的MVC、MVP、MVVM架構便屬于此范疇內。但無論是何種架構模式,都共同遵循“高內聚低耦合”思想,開發(fā)中需要根據(jù)實際情況采用合適的架構思路。
- MVC:字面拆分開即Model(模型)、 View (視圖) 、Controller(控制器)的架構層次,三者各司其職。iOS下的MVC,Model主要負責數(shù)據(jù)的解析和存取,僅僅是一個簡單的數(shù)據(jù)模型,往往不包含其他業(yè)務邏輯,View依賴Model呈現(xiàn)給用戶不同數(shù)據(jù)并傳遞用戶事件,Controller負責響應用戶事件,同時充當View和Model之間的媒介,此外還負責業(yè)務邏輯,如管理其他對象的生命周期、網(wǎng)絡請求、通知等等。
- MVP:蘋果推崇的MVC模式,因為UIView和UIViewController本身已經(jīng)耦合,這就引發(fā)了一個難以規(guī)避的問題:Controller將變得異常臃腫。因此當界面足夠復雜時,為Controller減重成了首要考慮的問題。MVP(Model-View-Presenter)可以較好的解決此問題。將Controller和View合并成View,Model負責網(wǎng)絡請求,數(shù)據(jù)庫操作,數(shù)據(jù)封裝等業(yè)務邏輯,新增Presenter模塊負責View和Model橋接工作以及View的響應事件等業(yè)務邏輯。與MVC相比,Controller原本的職責被分配到Presenter和Model中,它只需要管理View的生命周期即可。在輕Model的MVP中,Model的職責會大大減少,Presenter的任務會加重。
- MVVM: 移動開發(fā)往往熱衷于輕Model的架構模式,因為模型層很少直接和數(shù)據(jù)源直接交互,中間還有復雜的服務層。 出于業(yè)務考慮,有時候Model層的改變可能需要直接引起View層的變化,View層的改變也需要改變Model層,Model不僅僅是簡單的數(shù)據(jù)模型,兩者之間可能存在復雜的綁定關系,加上Model的數(shù)據(jù)可能會在多個頁面使用,因此還要考慮Model的復用,將這些綁定、復用邏輯全都放ViewModel中而取代Presenter,即MVVM(Model-View-ViewModel)
常用設計模式
設計模式可以通俗的理解為實現(xiàn)/解決某些問題,而形成的解決方案規(guī)范,可以增加代碼的可重用性,讓代碼能更容易理解和可靠。我們通常說所的代理模式、迭代器模式、策略模式就屬于該范疇。對各種設計模式的了解可以幫助我們更快的解決編程過程中遇到的問題。
- 委托模式:代理+協(xié)議的組合,實現(xiàn)1對1的反向傳值操作。
- 觀察者模式: 當被觀察者的某一屬性發(fā)生改變時,所有依賴于它的觀察者都能夠響應。觀察者模式不需要向被觀察者添加額外的代碼,因此能夠較美地將目標對象與觀察者對象解耦。iOS中的KVO,通知都是基于觀察者模式。
- 簡單工廠模式(非23種設計模式):通過一個工廠類方法,批量的根據(jù)已有模板生產目標對象。
- 工廠方法模式:通過多個工廠類方法,批量的根據(jù)已有模板生產目標對象。
- 抽象工廠模式:通過一個工廠類方法,批量的根據(jù)已有模版生產工廠對象,再由工廠對象創(chuàng)建目標對象。
- 單例模式:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。比如NSUserDefaults、UIApplication、NSBundle、NSFileManager、NSNotificationCenter、NSRunLoop等
- 迭代器模式::提供一種方法順序的訪問一個聚合對象中各個元素,而又不暴露該對象的內部表示。
- 策略模式:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。本模式使得算法的變化可獨立于使用它的客戶。
- ...
延伸問題:
MVC架構模式,如何為C層減重?
使用代理協(xié)議與使用Block進行對象間通信,各有什么特點?
三種工廠模式有什么區(qū)別?oc中使用的類族屬于哪種工廠模式?
設計模式有哪三大類型?上述設計模式分別屬于哪一類?
三.簡述NSRunLoop
NSRunLoop是蘋果封裝的一個消息循環(huán)類。每一個NSRunLoop實例對象,可以看作一個While循環(huán)。退出該循環(huán)的條件是收到某條指令消息,否則程序將一直保持運行。當app運行后,主線程就處于一個NSRunLoop之中,因此主線程可以持續(xù)的接收用戶事件,而殺死app則可以視為退出While循環(huán)的指令。當然,實際情況要復雜得多,但明確的是,RunLoop是為線程服務,與線程密不可分,一一對應。對于RunLoop,需要明確以下幾點:
- 主線程的RunLoop默認啟動。程序運行后,在入口main函數(shù)中,會為主線程設置一個NSRunLoop對象。
- 對其它線程來說,RunLoop默認不啟動,如果子線程需要一直處于循環(huán)狀態(tài)則可以手動配置和啟動,如果子線程僅僅執(zhí)行一個長時間的異步任務則不需要。
- RunLoop需要處理多種消息源,因此有多種模式用于處理各種消息。常用的有kCFRunLoopDefaultMode、UITrackingRunLoopMode、UIInitializationRunLoopMode:、NSRunLoopCommonModes
延伸問題:
為什么定時器需要加入到RunLoop之中?
NSRunLoop幾種模式的區(qū)別?有哪些使用場景?
NSRunLoop的生命周期?
四.簡述RunTime
Runtime 即運行時,是一套底層的 C 語言 API。也是Objective-C作為動態(tài)語言的核心技術。能夠在程序運行期,動態(tài)的創(chuàng)建類,遍歷一個類中所有的成員變量、屬性、方法,動態(tài)的修改某個類的屬性和方法,開發(fā)中主要體現(xiàn)在以下場景:
- Category
- Method-Swizzing
- 獲取對象私有屬性
- 字典轉模型
- KVC
- KVO
- 反射機制
- 消息機制
- ...
五.簡述iOS消息機制
在Objc底層,所有方法都是普通的C語言函數(shù),Objc中的方法又稱作消息,但消息究竟對應哪個C函數(shù)則完全于運行期決定,甚至可以在運行期動態(tài)改變。消息機制由消息傳遞機制和消息轉發(fā)機制兩部分組成。
- 消息傳遞機制:
id returnValue = [someObject messageName:parameter]
//someObject是接受者(receiver),messageName是選擇子(selector),選擇子和參數(shù)合起來稱為消息(message)。
//編譯器看到此消息后,將其轉換為如下標準的c函數(shù),此函數(shù)乃是消息傳遞機制中的核心函數(shù)
void objc_msgSend(id self, SEL cmd, ...)
objc_msgSend函數(shù)會依據(jù)接受者與選擇子的類型繼續(xù)調用適當?shù)姆椒ǎ摲椒〞诮邮苷咚鶎俚念愔兴褜て洹胺椒斜怼?。找到則跳到真正的實現(xiàn)代碼,否則,就沿著繼承體系繼續(xù)向上查找,這一流程叫做消息傳遞。消息傳遞結束仍找不到具體實現(xiàn)代碼,則執(zhí)行消息轉發(fā)操作。
- 消息轉發(fā)機制:消息轉發(fā)是在消息傳遞后,依然找不到實現(xiàn)代碼時啟動的一套補救措施。開發(fā)者可以通過實現(xiàn)重寫NSObject類的以下方法,利用Runtime API在運行期新增方法做一些特殊處理。
//能否動態(tài)添加實例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
//能否動態(tài)添加類方法
+ (BOOL)resolveClassMethod:(SEL)selector
//能否由其他接受者處理此消息
- (id)forwardingTargetForSelector:(SEL)selector
//最終轉發(fā)消息的方法,如果此方法失敗,則會跑拋出異常Crash,即常見的unrecognized selector send to instance xxx
- (void)forwardInvocation:(NSInvocation *)invocatio
延伸問題:
是否針對每條消息,都會經(jīng)歷如此復雜的一套機制?
六.簡述多線程編程
iOS中常用的多線程技術主要有GCD(Grand Central Dispatch)、NSThread、NSOperation三種,他們都可以用來處理多線程任務。其中以GCD最為簡單方便,也是蘋果主要推薦的方式。此處對GCD做一些簡單介紹。
GCD的核心概念:
- 任務:Block中所要執(zhí)行的代碼。
- 隊列:用于存放任務的線性表,采用先進先出原則。有串行隊列和并發(fā)隊列兩種。
- 串行隊列:每次只有一個任務被執(zhí)行。任務一個接著一個地執(zhí)行。(只開啟一個線程,一個任務執(zhí)行完畢后,再執(zhí)行下一個任務)
- 并發(fā)隊列:可以讓多個任務并發(fā)(同時)執(zhí)行。(可以開啟多個線程,并且同時執(zhí)行任務)
- 主隊列:系統(tǒng)提供的默認串行隊列
- 全局并發(fā)隊列:系統(tǒng)提供的默認并發(fā)隊列
- 同步執(zhí)行(sync):同步添加任務到指定隊列,會等待隊列前面的任務執(zhí)行完畢,再繼續(xù)執(zhí)行,只能在當前線程中執(zhí)行,不具備開啟新線程能力。
- 異步執(zhí)行(async):異步添加任務到指定的隊列,不做任何等待,繼續(xù)執(zhí)行任務,可以在新的線程中執(zhí)行任務,具備開啟新線程的能力。
從以上概念可以得出結論:一個任務是否在新線程中執(zhí)行,由兩方面決定:同步還是異步,串行還是并發(fā)。
GCD常用的API:
// 串行隊列的創(chuàng)建方法
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列的創(chuàng)建方法
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局并發(fā)隊列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 同步執(zhí)行任務創(chuàng)建方法
dispatch_sync(queue, ^{
/// 這里放同步執(zhí)行任務代碼,queue可以是任何隊列
});
// 異步執(zhí)行任務創(chuàng)建方法
dispatch_async(queue, ^{
/// 這里放異步執(zhí)行任務代碼,queue必須要是并行隊列
});
//柵欄分組方法
dispatch_barrier_async(queue, ^{
/// 柵欄追加任務,queue必須是并行隊列
});
//主隊列延時調用方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
/// 2.0秒后追加任務到主隊列
});
// 只執(zhí)行一次方法
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/// 只執(zhí)行1次的代碼(這里面默認是線程安全的)
});
延伸問題:
GCD什么時候會出現(xiàn)死鎖?如何解決?
GCD信號量的用處?
七.簡述iOS的鎖機制
多線程開發(fā)中,當至少有兩個線程同時訪問同一個變量,而且至少其中有一個是寫操作時,就發(fā)生了數(shù)據(jù)競爭(Data race)。所以這是就要利用一些同步機制來確保數(shù)據(jù)的準確性,鎖就是同步機制中的一種。
鎖的基本概念:
- 臨界區(qū):指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。
-
自旋鎖:是用于多線程同步的一種鎖,線程反復檢查鎖變量是否可用。由于線程在這一過程中保持執(zhí)行,因此是一種
忙等待
。一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖。 自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的。 - 互斥鎖(Mutex):是一種用于多線程編程中,防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。該目的通過將代碼切片成一個一個的臨界區(qū)而達成。
- 讀寫鎖:是計算機程序的并發(fā)控制的一種同步機制,也稱“共享-互斥鎖”、多讀者-單寫者鎖) 用于解決多線程對公共資源讀寫問題。讀操作可并發(fā)重入,寫操作是互斥的。 讀寫鎖通常用互斥鎖、條件變量、信號量實現(xiàn)。
-
信號量(semaphore):是一種更高級的同步機制,
互斥鎖
可以說是semaphore在僅取值0/1時的特例。信號量
可以有更多的取值空間,用來實現(xiàn)更加復雜的同步,而不單單是線程間互斥。 - 條件鎖:條件變量,當進程的某些資源要求不滿足時就進入休眠,也就是鎖住了。當資源被分配到了,條件鎖打開,進程繼續(xù)運行。
- 遞歸鎖:同一個線程可以加鎖N次而不會引發(fā)死鎖的同步機制
iOS框架中的十一種常用鎖:
- NSLock:互斥鎖,是Foundation框架中以對象形式暴露給開發(fā)者
- pthread_mutex:互斥鎖
- @synchronized:互斥鎖
- OSSpinLock:自旋鎖,在某一些場景下已經(jīng)不安全了,使用較少
- os_unfair_lock:自選鎖,蘋果官方推薦的替換OSSpinLock的方案(iOS10+)
- pthread_rwlock:讀寫鎖
- NSRecursiveLock:遞歸鎖
- pthread_mutex(recursive)::遞歸鎖,pthread_mutex的變形
- NSCondition:條件鎖,需遵循NSLocking協(xié)議
- NSConditionLock:條件鎖,基本同上
- dispatch_semaphore:信號量,GCD中的線程同步機制
八.簡述分類(Category)和擴展(Extension)
Category:在運行期為類動態(tài)的添加方法。也可以添加屬性,但系統(tǒng)不會生成實例變量。需自己實現(xiàn)setter和getter,并使用Runtime API動態(tài)添加成員變量。
Extension:在編譯期為類添加成員變量(屬性)。通常在.m文件內實現(xiàn),因此又叫匿名分類,但和Category的實現(xiàn)原理有本質區(qū)別。
九.簡述Method-wizzing
Method-Swizzing是OC對AOP編程的實現(xiàn),AOP(面向切面編程)指的是通過‘預編譯’和‘運行期’實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術。OC的Runtime庫提供了一個叫method_exchangeImplementations的api,正如其名,這個方法可以將兩個方法互換,因為OC調用函數(shù)是通過消息機制,在執(zhí)行最終的函數(shù)之前,會通過SEL(方法序列)對應函數(shù)指針I(yè)MP,因此將兩個SEL交換,變可以將系統(tǒng)方法和自己的方法進行交換。
延伸問題:
分類、繼承、Method-Swizzing相比,各有何特點?