重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
在數學和邏輯學中,單例定義為” 有且僅有一個元素的集合 “,在無論什么情況下,獲取到的都是同一個值。在程序中,單例模式保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。這個方法應該是類方法,阻止所有想要生成對象的訪問,避免一個全局使用的類頻繁地創建和銷毀。
創新互聯公司堅持“要么做到,要么別承諾”的工作理念,服務領域包括:網站設計制作、網站設計、企業官網、英文網站、手機端網站、網站推廣等服務,滿足客戶于互聯網時代的港北網站設計、移動媒體設計的需求,幫助企業找到有效的互聯網解決方案。努力成為您成熟可靠的網絡建設合作伙伴!
static uniqueInstance 是 Singleton 中的唯一實例, static sharedInstance 將它返回給客戶端。通常, shareInstance 會檢查 uniqueInstance 是否已經被實例化,如果沒有,會生成一個實例然后返回 uniqueInstance 。
沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。
只要應用程序需要集中式的類來協調其服務,這個類就應該生成單一的實例,而不是多個實例。
KVO 的全稱是 Key-Value Observing,俗稱“鍵值觀察/監聽”,是蘋果提供的一套事件通知機制,允許一個對象觀察/監聽另一個對象指定屬性值的改變。當被觀察對象屬性值發生改變時,會觸發 KVO 的監聽方法來通知觀察者。KVO 是在 MVC 應用程序中的各層之間進行通信的一種特別有用的技術。
KVO 和 NSNotification 都是 iOS 中觀察者模式的一種實現。
KVO 可以監聽單個屬性的變化,也可以監聽集合對象的變化。監聽集合對象變化時,需要通過 KVC 的 mutableArrayValueForKey: 等可變代理方法獲得集合代理對象,并使用代理對象進行操作,當代理對象的內部對象發生改變時,會觸發 KVO 的監聽方法。集合對象包含 NSArray 和 NSSet 。
先創建一個類,作為要監聽的對象。
監聽實現
KVO 主要用來做鍵值觀察操作,想要一個值發生改變后通知另一個對象,則用 KVO 實現最為合適。斯坦福大學的 iOS 教程中有一個很經典的案例,通過 KVO 在 Model 和 Controller 之間進行通信。如圖所示:
KVO 觸發分為自動觸發和手動觸發兩種方式。
如果是監聽對象特定屬性值的改變,通過以下方式改變屬性值會觸發 KVO:
如果是監聽集合對象的改變,需要通過 KVC 的 mutableArrayValueForKey: 等方法獲得代理對象,并使用代理對象進行操作,當代理對象的內部對象發生改變時,會觸發 KVO。集合對象包含 NSArray 和 NSSet 。
普通對象屬性或是成員變量使用:
NSArray 對象使用:
NSSet 對象使用:
observationInfo 屬性是 NSKeyValueObserving.h 文件中系統通過分類給 NSObject 添加的屬性,所以所有繼承于 NSObject 的對象都含有該屬性;
可以通過 observationInfo 屬性查看被觀察對象的全部觀察信息,包括 observer 、 keyPath 、 options 、 context 等。
注冊方法 addObserver:forKeyPath:options:context: 中的 context 可以傳入任意數據,并且可以在監聽方法中接收到這個數據。
context 作用:標簽-區分,可以更精確的確定被觀察對象屬性,用于繼承、 多監聽;也可以用來傳值。
KVO 只有一個監聽回調方法 observeValueForKeyPath:ofObject:change:context: ,我們通常情況下可以在注冊方法中指定 context 為 NULL ,并在監聽方法中通過 object 和 keyPath 來判斷觸發 KVO 的來源。
但是如果存在繼承的情況,比如現在有 Person 類和它的兩個子類 Teacher 類和 Student 類,person、teacher 和 student 實例對象都對象的 name 屬性進行觀察。問題:
當 name 發生改變時,應該由誰來處理呢?
如果都由 person 來處理,那么在 Person 類的監聽方法中又該怎么判斷是自己的事務還是子類對象的事務呢?
這時候通過使用 context 就可以很好地解決這個問題,在注冊方法中為 context 設置一個獨一無二的值,然后在監聽方法中對 context 值進行檢驗即可。
蘋果的推薦用法:用 context 來精確的確定被觀察對象屬性,使用唯一命名的靜態變量的地址作為 context 的值。可以為整個類設置一個 context ,然后在監聽方法中通過 object 和 keyPath 來確定被觀察屬性,這樣存在繼承的情況就可以通過 context 來判斷;也可以為每個被觀察對象屬性設置不同的 context ,這樣使用 context 就可以精確的確定被觀察對象屬性。
context 優點:嵌套少、性能高、更安全、擴展性強。
context 注意點:
KVO 可以監聽單個屬性的變化,也可以監聽集合對象的變化。監聽集合對象變化時,需要通過 KVC 的 mutableArrayValueForKey: 等方法獲得代理對象,并使用代理對象進行操作,當代理對象的內部對象發生改變時,會觸發 KVO 的監聽方法。集合對象包含 NSArray 和 NSSet 。(注意:如果直接對集合對象進行操作改變,不會觸發 KVO。)
可以在被觀察對象的類中重寫 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 方法來控制 KVO 的自動觸發。
如果我們只允許外界觀察 person 的 name 屬性,可以在 Person 類如下操作。這樣外界就只能觀察 name 屬性,即使外界注冊了對 person 對象其它屬性的監聽,那么在屬性發生改變時也不會觸發 KVO。
也可以實現遵循命名規則為 + (BOOL)automaticallyNotifiesObserversOfKey 的方法來單一控制屬性的 KVO 自動觸發,Key 為屬性名(首字母大寫)。
使用場景:
使用 KVO 監聽成員變量值的改變;
在某些需要控制監聽過程的場景下。比如:為了盡量減少不必要的觸發通知操作,或者當多個更改同時具備的時候才調用屬性改變的監聽方法。
由于 KVO 的本質,重寫 setter 方法來達到可以通知所有觀察者對象的目的,所以只有通過 setter 方法或 KVC 方法去修改屬性變量值的時候,才會觸發 KVO,直接修改成員變量不會觸發 KVO。
當我們要使用 KVO 監聽成員變量值改變的時候,可以通過在為成員變量賦值的前后手動調用 willChangeValueForKey: 和 didChangeValueForKey: 兩個方法來手動觸發 KVO,如:
NSKeyValueObservingOptionPrior (分別在值改變前后觸發方法,即一次修改有兩次觸發)的兩次觸發分別在 willChangeValueForKey: 和 didChangeValueForKey: 的時候進行的。
如果注冊方法中 options 傳入 NSKeyValueObservingOptionPrior ,那么可以通過只調用 willChangeValueForKey: 來觸發改變前的那次 KVO,可以用于在屬性值即將更改前做一些操作。
有時候我們可能會有這樣的需求,KVO 監聽的屬性值修改前后相等的時候,不觸發 KVO 的監聽方法,可以結合 KVO 的自動觸發控制和手動觸發來實現。
例如:對 person 對象的 name 屬性注冊了 KVO 監聽,我們希望在對 name 屬性賦值時做一個判斷,如果新值和舊值相等,則不觸發 KVO,可以在 Person 類中如下這樣實現,將 name 屬性值改變的 KVO 觸發方式由自動觸發改為手動觸發。
有些情況下我們想手動觀察集合屬性,下面以觀察數組為例。
關鍵方法:
需要注意的是,根據 KVC 的 NSMutableArray 搜索模式:
有些情況下,一個屬性的改變依賴于別的一個或多個屬性的改變,也就是說當別的屬性改了,這個屬性也會跟著改變。
比如我們想要對 Download 類中的 downloadProgress 屬性進行 KVO 監聽,該屬性的改變依賴于 writtenData 和 totalData 屬性的改變。觀察者監聽了 downloadProgress ,當 writtenData 和 totalData 屬性值改變時,觀察者也應該被通知。以下有兩種方法可以解決這個問題。
以上兩個方法可以同時存在,且都會調用,但是最終結果會以 keyPathsForValuesAffectingValueForKey: 為準。
以上方法在觀察集合屬性時就不管用了。例如,假如你有一個 Department 類,它有一個裝有 Employee 類的實例對象的數組,Employee 類有 salary 屬性。你希望 Department 類有一個 totalSalary 屬性來計算所有員工的薪水,也就是在這個關系中 Department 的 totalSalary 依賴于所有 Employee 實例對象的 salary 屬性。以下有兩種方法可以解決這個問題。
有時候我們難以避免多次注冊和移除相同的 KVO,或者移除了一個未注冊的觀察者,從而產生可能會導致 Crash 的風險。
三種解決方案: 黑科技防止多次添加刪除KVO出現的問題
我們在對象添加監聽之前分別打印對象類型
我們看到,添加監聽后,使用 object_getClass 方法獲取model類型時獲取到的是 NSKVONotifying_DJModel 。
這里就產生了幾個問題:
從打印結果可以看出, NSKVONotifying_DJModel 是 DJModel 的子類,說明我們添加了監聽之后動態創建了一個 DJModel 的子類 NSKVONotifying_DJModel ,并將對象 DJModel 的類型更改為了 NSKVONotifying_DJModel 。
我們從源碼看出,實例對象調用 class 方法會返回 isa 指針,類對象調用 class 方法會返回自己,通過 object_getClass 方法獲取對象的類型也會返回 isa 指針。從源碼上看model對象添加監聽之后使用 class 和使用 object_getClass 方法獲取到的類型應該是一樣的,但是這里卻不同,我們猜測在添加了監聽之后在 NSKVONotifying_DJModel 中重寫了 class 方法。
我們打印一下添加監聽前后 class 方法的 IMP 地址來確認是否重寫了 class 方法。
從打印結果可以看出,添加監聽之后 class 方法的地址改變了,這驗證了我們之前的猜想, NSKVONotifying_DJModel 類中重寫了 class 方法。
我們監聽對象時調用了 set 方法,我們對監聽前后的 set 方法單獨分析。
我們再添加監聽前后分別打印 setName 方法的 IMP 地址。
通過打印結果可以看出 setName 方法也在 NSKVONotifying_DJModel 中被重寫了,我們再使用lldb來看下 setName 具體是什么
第一個地址打印的是添加監聽前 setName 方法的 IMP 地址,第二個打印的是添加監聽后 setName 方法的 IMP 地址。
這里看出添加監聽前 setName 對應的具體方法就是 setName ,但是添加監聽后, setName 對應的雞頭方法卻變成了 _NSSetObjectValueAndNotify 函數。
下面我們就來研究一下 _NSSetObjectValueAndNotify 函數。
從上面與KVO相關的方法中我們可以看出,每一種數據類型都對應了一個 setXXXValueAndNotify 函數。
不過這些函數的具體實現沒有公布,所以內部構造這里還是不清楚。
但是我們知道,在調用 `setXXXValueAndNotify 函數的過程中會調用另外兩個方法。
測試后得出了以下幾個結論:
我們還可以利用這兩個方法手動觸發 observeValueForKeyPath 方法:
所以我們判斷在 _NSSetObjectValueAndNotify 函數內部,在調用原來的 set 方法之前插入了 willChangeValueForKey 方法,在調用原來的 set 方法之后插入了 didChangeValueForKey 方法,并根據初始化時的枚舉值決定調用 observeValueForKeyPath 的時機。
(1)添加監聽時,會動態創建一個監聽對象類型的子類,并將監聽對象的 isa 指針指向新的子類。
(2)子類中重寫了 class 和監聽屬性的 set 方法。
(3)重寫 class 方法是為了不將動態創建的類型暴露出來。
(4)重寫 set 方法是將 set 方法的具體實現替換成了與屬性類型相關的 __NSSetXXXValueAndNotify 函數。
(5)在 __NSSetXXXValueAndNotify 函數內部在 set 方法前后分別插入了 willChangeValueForKey 和 didChangeValueForKey 這兩個方法。
(6)根據添加監聽時的枚舉值決定調用 observeValueForKeyPath 的具體時機。
代理模式是一種消息傳遞方式,一個完整的代理模式包括:委托對象、代理對象和協議。
協議:用來指定代理雙方可以做什么,必須做什么。
委托對象:根據指定的協議,指定代理去完成什么功能。
代理對象:根據指定的協議,完成委托方需要實現的功能。
從上圖中可以看到三方之間的關系,在實際應用中通過協議來規定代理雙方的行為,協議中的內容一般都是方法列表,當然也可以定義屬性。
協議是公共的定義,如果只是某個類使用,我們常做的就是寫在某個類中。如果是多個類都是用同一個協議,建議創建一個Protocol文件,在這個文件中定義協議。遵循的協議可以被繼承,例如我們常用的 UITableView ,由于繼承自 UIScrollView 的緣故,所以也將 UIScrollViewDelegate 繼承了過來,我們可以通過代理方法獲取 UITableView 偏移量等狀態參數。
協議只能定義公用的一套接口,類似于一個約束代理雙方的作用。但不能提供具體的實現方法,實現方法需要代理對象去實現。協議可以繼承其他協議,并且可以繼承多個協議,在iOS中對象是不支持多繼承的,而協議可以多繼承。
協議有兩個修飾符 @optional 和 @required ,創建一個協議如果沒有聲明,默認是 @required 狀態的。這兩個修飾符只是約定代理是否強制需要遵守協議,如果 @required 狀態的方法代理沒有遵守,會報一個黃色的警告,只是起一個約束的作用,沒有其他功能。
無論是 @optional 還是 @required ,在委托方調用代理方法時都需要做一個判斷,判斷代理是否實現當前方法,否則會導致崩潰。
在iOS中代理的本質就是代理對象內存的傳遞和操作,我們在委托類設置代理對象后,實際上只是用一個id類型的指針將代理對象進行了一個弱引用。委托方讓代理方執行操作,實際上是在委托類中向這個id類型指針指向的對象發送消息,而這個id類型指針指向的對象,就是代理對象。
通過上面這張圖我們發現,其實委托方的代理屬性本質上就是代理對象自身,設置委托代理就是代理屬性指針指向代理對象,相當于代理對象只是在委托方中調用自己的方法,如果方法沒有實現就會導致崩潰。從崩潰的信息上來看,就可以看出來是代理方沒有實現協議中的方法導致的崩潰。
而協議只是一種語法,是聲明委托方中的代理屬性可以調用協議中聲明的方法,而協議中方法的實現還是有代理方完成,而協議方和委托方都不知道代理方有沒有完成,也不需要知道怎么完成。
由于代理對象使用強引用指針,引用創建的委托方對象,并且成為委托對象的代理。這就會導致委托對象的delegate屬性強引用代理對象,導致循環引用的問題,最終兩個對象都無法正常釋放。
我們將委托對象的delegate屬性,設置為弱引用屬性。
weak 和 assign 是一種“非擁有關系”的指針,通過這兩種修飾符修飾的指針變量,都不會改變被引用對象的引用計數。但是在一個對象被釋放后, weak 會自動將指針指向 nil ,而 assign 則不會。在iOS中,向 nil 發送消息時不會導致崩潰的,所以 assign 就會導致野指針的錯誤 unrecognized selector sent to instance 。
所以我們如果修飾代理屬性,還是用 weak 修飾,比較安全。
代理模式:完成委托方的任務,需要聲明代理對象和指定代理,相對于block,在需要傳遞參數的傳值時優先考慮代理。
代理是一對一的關系(1個對象只能通知1個對象發生了什么事)。
應用場景:不同類之間的傳值與回調。
觀察者模式(通知機制,KVO機制):觀察者模式本質上是一種發布-訂閱模型,用以消除具有不同行為的對象之間的耦合,通過這一模式,不同對象可以協同工作。
通知是一對多的關系(1個通知可以發送給多個通知接受對象)。
應用場景:監聽設備狀態,自定義鍵盤時監聽鍵盤的彈出和隱藏。
單例模式:可以保證App在程序運行中,一個類只有唯一個實例。
系統中的單例:UIApplication(應用程序實例)、NSNotificationCenter(消息中心)、NSFileManager(文件管理)、NSUserDefaults(應用程序設置)、NSURLCache(請求緩存)等。
應用場景:功能集中管理的模塊可以封裝為單例,方便外界調用。
策略模式:定義了一系列的算法,并將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立于使用它的客戶而獨立變化。
MVC(Model View Controller):
model:數據層
view:視圖層,負責頁面展示
Controller:控制視圖層與數據層的關聯,
MVVM(model view viewModel):
Model: 數據層,不包含邏輯
View:與用戶直接交互,只需處理觸發事件后的轉發,和告訴他如何顯示
ViewModel:跟蹤view的事件,處理model層傳遞的數據;公開方法、屬性,讓view保持最新的狀態
iOS開發就是為裝有iOS系統的設備完成應用軟件或游戲軟件的開發,ios開發的設計模式有代理模式、觀察者模式、MVC模式、單例模式、策略模式和工廠模式。