設計模式分析比較?
1、單例設計模式:在項目中,單例是必不可少的。比如UIApplication、NSUserDefaults就是蘋果提供的單例。在項目中經常會將用戶數據管理封裝成一個單例類,因此用戶的信息需要全局使用。單例確實給我們帶來的便利,但是它也會有代價的。單例一旦創建,整個App使用過程都不會釋放,這會占用內存,因此不可濫用單例。首先,單例寫法有好幾種,通常的寫法是基于線程安全的寫法,結合dispatch_once來使用,保證單例對象只會被創建一次。如果不小心銷毀了單例,再調用單例生成方法是不會再創建的。其次,由于單例是約定俗成的,因此在實際開發中通常不會去重寫內存管理方法。
2、MVC設計模式:現在絕大部分項目都是基于MVC設計模式的,現在有一部分開發者采用MVVM、MVP等模式。
3、通知(NSNotification)模式:通知在開發中是必不可少的,對于跨模塊的類交互,需要使用通知;對于多對多的關系,使用通知更好實現。
4、工廠設計模式:在我的項目中使用了大量的工廠設計模式,特別是生成控件的API,都已經封裝成一套,全部是擴展的類方法,可簡化很多的代碼。
5、KVC/KVO設計模式:有的時候需要監聽某個類的屬性值的變化而做出相應的改變,這時候會使用KVC/KVO設計模式。在項目中,我需要監聽model中的某個屬性值的變化,當變化時,需要更新UI顯示,這時候使用KVC/KVO設計模式就很方便了。
通知中心?
本質可變數組,首先創建單例并開辟空間負責管理所有的通知,接受已經注冊的通知對象,這個對象的屬性包括觀察者,方法,名字,傳遞的對象!每當注冊一個新的通知時,必須通過NSNotificationCenter這個類創建一個defaultCenter通知中心管理對象通過addObserver、postNotification、removeObserver來管理所有已經注冊的通知!內部的實現原理除了傳參就是可變數組的遍歷,移除通知時傳入參數1(通知名字)和參數2(傳遞的對象值),原理就是Block方式遍歷可變數組,只不過判斷時不僅要判斷名字,更要滿足觀察者的對比進行比較。
通知中心的本質就是兩個類,一個通知數據模型類,一個單例通知中心類,通知數據模型類的屬性有發送通知的方法、觀察者(控制器)、注冊通知的名字、通知傳遞的對象。通知數據模型類的方法主要有帶所有屬性輸入參數的初始化方法。這個方法主要是為了在普通通知類實例化對象的時候就能給所有的屬性進行賦值操作。那么還有一個通知中心單例類,這里面首先有一個可變數組全局變量用來用來添加所有注冊的通知。接下來就是一系列方法的聲明和實現,聲明包括創建單例的類方法、向通知中心注冊通知的對象方法、輸入注冊名字和傳遞對象發送通知的方法、輸入觀察者和注冊名字刪除通知的方法。首先就是實現創建單例的類方法,使用static初始化一個通知中心的對象為空,設置static dispatch_once_t onceToken和dispatch_once(&onceToken,^{});語句讓實例化的通知中心對象只開辟一次內存空間。而且最重要的是這里開辟內存空間的方式有一些不一樣。采用的是父類調用allocWithZone:NULL來開辟內存空間。當然init初始化方法需要重寫,重寫的意義就在于為前期聲明的全局數組變量開辟內存空間。然后就是實現添加注冊的方法,說白了就是添加一個通知版的數據模型對象到可變數組里。實現移除數組里通知的方法,本質就是遍歷這個裝滿通知版數據模型對象的可變數組,判斷里面的每一個數據模型對象的屬性是否與方法輸入的參數相等。現在就是關鍵了,如何實現發送通知的方法,同樣Block遍歷數組,找到所有與輸入名字相同的通知版數據模型對象,現在就開始正式的調用注冊通知里的那個方法了,將輸入的參數傳遞給數據模型里的其它屬性。包括方法和對象。然后使用數據模型的對象的觀察者屬性(這本質上是控制器對象),調用performSelector方法將方法和對象數據傳遞出去。
發送通知?
就發送通知時的屬性對象值賦值給可變數組中的對象的object屬性!同時調用出數組對象的方法 SEL sel = noti.selector;現在開始執行可變數組中對象的方法屬性(調用對象的方法函數)[noti.selector performSelector:sel withObject:noti]
單例?
單例是整個Cocoa中被廣泛使用的核心設計模式之一,作為一個iOS開發者,我們經常和單例打交道,比如UIApplication、NSFileManager、NSNotificationCenter、UIDevice等等。Xcode甚至有一個默認的"Dispatch Once"代碼片段,可以使我們非常簡單地在代碼中創建一個單例。單例,由于其具有全局和多狀態的特性,導致隱式地在兩個看起來完全不相關的模塊之間建立了耦合。單例里最需要注意的一個問題就是要考慮到多線程的影響,由于分線程與主線程的速度差異很大,也就是說主線程的進行過程中不止一次創建分線程去判斷單例類對象的內存空間是否已經被開辟。第一種方法就是通過上鎖保證在異步創建單例也能保證唯一性@synchronized(self),第二種方法使用static dispatch_once_t onceToken來保證代碼只被執行一次。
如果想要在多個控制器使用一個屬性的值,那么并不需要用戶在程序中設置全局變量,需要在哪里使用單例類的數據,那么就在哪里創建一個單例類對象,然后進行讀或者寫操作。就是把這個整個工程都需要的用到的屬性值當成是單例類對象的屬性,如此哪里要使用到這個屬性里的值,只需要實例化一個單例對象調用出相應的屬性即可。
KVO觀察者?
如果類1想要隨時監聽類2的屬性是否發生變化,并把類2變化后的屬性值傳遞到類1里面,只需要三步就可以實現,第一步:在類1跳轉到類2之前,先給類2安裝一個攝像頭,這個觀察者就是類1的對象self,而且Key只能指向對象的屬性,而KeyPath則能指向屬性的屬性。注冊了觀察者之后,自然再實現KVO里的類1觀察者監聽到類2的KeyPath屬性值發生變化會回調的方法。這個回調方法幾乎傳遞過來了整個類2的對象,所以對于KeyPath(點語法)觀察的屬性輕輕松松獲取到現在的值。當然最后需要在視圖消失時調用的dealloc方法里通過類2的對象調用移除觀察者的方法。還有注意一點就是,KVO監聽只能在一個線程中,只能觀察一個對象的屬性而不能觀察集合。
KVO的使用場景與通知的使用場景有什么不同?
1、KVO一次性只能實現兩個控制器之間的傳值,而通知能夠實現將一個控制器的值傳遞到多個控制器
2、KVO的傳遞的值只能時被監聽的控制器的某一個屬性,而通知可以傳遞一個控制器的任何數據類型的對象
block的本質?
1、指向結構體的指針,調用block之前,Block里面的變量是一個值,在調用block之后,就會通過block里面的方法對變量的值進行改變,也就是說,可以在特定的判斷下執行特定的方法。
block為什么要用copy修飾?
1、我們都知道堆里面的東西不會被系統自動回收,因此必須將一個Block放在堆里面。而默認情況下任何block被創建時都在棧內存空間里面,而棧里面的內存隨時可能被系統自動回收,更會在大括號結束之后被全部釋放。那我們如何才能將block放在堆內存空間里面呢?只要對block做一次copy操作就可以將block指針指向堆內存空間里面。這樣block就不會被自動釋放了,可以長期持有block。這樣就能保證只要對象還在,那么對象的block屬性就會還在。
為什么不能用MRC中的retain或ARC中的strong?
1、確實retain和strong已經能夠保證對象的屬性不會被自動釋放,而且只要對象還在,那么屬性就一定存在。可是retain和strong僅僅是增加了屬性對象引用計數,根本不會改變屬性對象的內存地址。只有copy 方法才能改變內存地址。將屬性從默認的棧地址改變到堆地址里。
Block使用過程中會出現什么問題?如何解決?
1、block屬性必須使用copy來修飾,既然是copy那就是強引用,然后就會出現一個內存泄露的情況,內存泄露就是對象在堆內存空間的內存得不到釋放。
2、當初所說的循環引用就是簡單的對象1強引用對象2,對象2強引用對象1,MRC里一個retain,另一個assign。ARC里一端是strong,另一端使用weak。雖然這里不是對象1直接強引用對象2,但是因為block使用copy修飾符又作為對象1的屬性就間接地強引用了對象2。解決方案當然不能改變block的修飾符,改的只能是如何讓對象2不強引用對象1,方法就是在用alloc方法創建了強指針之后,再聲明一個弱指針將強指針的內存地址賦值給弱指針,如此便可以使得對象2不再強引用對象1。
KVC的本質是什么?這是一個問題。跟KVO的區別又是什么?KVC有與普通意義上的傳值有什么聯系?
對于任何一個類的實例化的對象來說,每一個對象肯定都有許多的屬性,當然對于一個對象來說,在沒有給屬性賦值以前,這些屬性都是nil.那么現在能想到的賦值方式就是通過等于號直接進行賦值了。那么KVC就為我們提供了一種給對象的屬性賦值的全新方法!不需在用對象點語法加屬性的方式通過等于號來進行賦值,而是直接以方法[對象 setValue:@"" forKey:@“屬性”/forKeyPath:@"屬性”]來進行屬性的賦值。至于forKey和forKeyPath的區別,在KVO里面就已經說過了!還有過去來說,一想到獲取對象的屬性值肯定首先會想到對象點語法屬性,而現在因為有了KVC則提供了一種全新的獲取對象屬性值的全新方法。即[對象 valueForKey/KeyPath/UndefinedKey:@"屬性”],其實如果發明KVC的意義就這么點功能的話實在是沒有什么存在的價值,KVC最大的價值還是在于KVC給對象的屬性批量賦值的功能。這次才是KVC的最大貢獻,至于單個給某一個屬性賦值或提取某一單個屬性的值完全不是KVC對于這個世界的貢獻,批量給對象的屬性賦值才是存在的意義,通過方法[有著批量屬性的對象 setValuesForKeysWithDictionary:字典]實現給對象的屬性批量賦值的前提條件就是對象里的屬性名稱必須與字典里的鍵一模一樣,原因就在于傳進來的是一個字典,字典決定取哪一個鍵完全是由屬性的名稱來決定,格式通常就是字典[@“屬性名”]來提取字典里的值來賦值給對象里的屬性,那么經常會出現一個問題就是,屬性里有的名稱字典里并沒有,如果只是造成對象里的這個屬性賦值不成共也就罷了,問題是這竟然會導致程序崩潰!唯一的解決方案就是在對象的.m文件里添加方法-(void)setValue:(id)value forUndefinedKey:(NSString *)key{}。還有一個情況就是在逐個提取對象里屬性的值時,會出現一種情況就是提前想要提取的屬性在對象里并不存在,這就造成了崩潰,你可能會問,為什么在用點語法來提取對象時并不會發生崩潰,因為原因就是用點語法提取數據的時候,如果對象沒有這個屬性,就根本連顯示都沒有呀!而通過KVC的[對象 valueForKey/KeyPath/UndefinedKey:@"屬性”]的方法則無法進行提前判斷這個對象里到底有沒有這個屬性。唯一能做的就是事后諸葛亮,程序崩潰讓你崩潰,你自然就知道了!
-(id)valueForUndefinedKey:(NSString *)key{ return nil; }
KVO使用方法?
當我點擊按鈕時,會將類2的對象的屬性值傳遞給類1的對象的屬性值。這就相當于使用KVO來進行反向傳值,我在使用KVO進行反向傳值時遇到了一個很嚴重的問題,設置監聽通知時必須注意一個問題就是設置觀察者時務必注意一個問題,千萬不要多創建一個觀察者對象,這樣盡管反向傳值時發現監聽發生了,而且也調用了那個監聽到屬性值發生改變時就會調用的方法,而且你會發現盡管這個方法就是我們設置的那個觀察者控制器里的方法,但要知道這此時的觀察者其實是一個全新的觀察者!所以原本的觀察者控制器的屬性值全部變為空。所以你會發現盡管程序進入了那個監聽路徑的內容里面,但是奇怪的是你會發現原本創建的UI視圖的屬性值全部變為空!所以這其中的問題在那兒呢?關鍵就在于當控制器1通過導航控制器Push到控制器2時,在控制器2里面注冊監聽,相當于在self(控制器2)上注冊監聽,將控制器1設置為觀察者,用來監聽控制器2里面的KeyPath所對應的值是否發生了變化。那么問題來了,一定要注意設置觀察者的時候必須考慮好現在正在設置的觀察者是否是新創建的控制器1。然后還有一個問題,就是在監聽路徑的時候,務必注意控制器的屬性KeyPath必須使用點語法,不能使用下劃線!
那么現在需要整理一下這個思路,首先就是注冊監聽,會出現四個參數,參數一也就是說是誰調用通知的這種方法,誰調用這個注冊監聽的方法就相當于誰是被監聽的對象。舉個例子,如果控制器2的對象調用這個方法,就相當于在控制器2里面安裝了一個攝像頭用來監聽控制器2里面的所有屬性值的變化!參數2就是設置觀察者,表示已經安裝在控制器2里面的攝像頭最終將數據的變化情況傳給誰?通常這里填self。也就是說,盡量把KVO的注冊和調用以及銷毀放在觀察者的這個控制器里。那么問題來了,為什么我總是會有有一種錯覺就是,要想實現監聽功能,必須和被監聽的控制器里發生聯系呢?就有一種錯覺就是,總感覺要想實現決定何時調用監聽方法就必須讓這個KVO里面的某一個方法必須與被監聽的控制器發生某種聯系。可是,事實上,并非如此,因為有了參數三:KeyPath,這個參數直接讓被監聽的控制器精確到一個屬性,準確的說,精確到某一個路徑才更正確。精確到屬性只是相當于精確到key,而keyPath就相當于能夠精確到屬性里面的屬性,反正keyPath遠比key強大!當然既然設計傳值,其實就少不了SEL方法,因為通常來說,反向傳值,會用到幾個常見的方法:方法1就是協議代理,就是通過調用函數時候。兩個控制器都會出現同一句包含參數的函數,這樣就實現了傳值,而且可以在函數的具體實現里對傳入的參數進行處理。方法2就是通過Block來進行傳值,同樣也是在block的具體實現里對調用block時傳入的參數進行編輯和使用!其實從這個角度來講,KVO的監聽傳值更像是一個協議呀!
1、[被監聽的控制器 addObserver:觀察者控制器 forKeyPath:@“被監聽的屬
2、- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
(被監聽控制器 *)object.被監聽屬性;}
3、-(void)dealloc{
[被監聽的控制器 removeObserver:so forKeyPath:@“被監聽的屬性"]}