寫在前面
第一次寫博文,旨在分享在學(xué)習(xí)的過程中發(fā)現(xiàn)并解決問題的經(jīng)驗和一些看法。最近在學(xué)習(xí)runtime的相關(guān)知識,看到關(guān)聯(lián)對象這一點時,覺得挺有意思的。PS:這篇文章的點子來自《Effective Objective-C 2.0》。
關(guān)于UIAlertView
在iOS8以前,我們需要彈出一個AlertView并且自定義響應(yīng)的事件時候,都需要遵守<UIAlertViewDelegate>,然后在回調(diào)方法中根據(jù)索引去處理點擊之后的邏輯。
久而久之,真心覺得這是一個非常繁瑣而無趣的過程。并且,如果在一個頁面中,有多個alertView需要彈出的時候,那么你還需要對每一個判斷是哪個alertView。那么,有沒有什么好的辦法可以簡化這些繁瑣的過程呢?最好的方式就是傳入一個block作為事件的回調(diào)。iOS8已經(jīng)有了UIAlertController,并且可以通過給其添加UIAlertAction的方式來指定每個選項點擊事件。action也是采取了block的方式。
+ (instancetype)actionWithTitle:(NSString *)title style:(UIAlertActionStyle)style handler:(void (^)(UIAlertAction *action))handler;
我們也可以通過給UIView添加分類(Category)的方式來達到類似的效果。
UIVIew+MyAlertView
添加兩個方法(為了方便,可以自己選擇用哪種),并且定義響應(yīng)的block。
第一個方法中,為什么要把action放在那么尷尬的位置?因為alertView的變參(OtherButtonTitles,...)只能作為方法的最后一個參數(shù)。
我們來看兩個方法的實現(xiàn)。
這里有個小問題,就是我們自定義的方法,傳進來的other這個可變的參數(shù),在我們創(chuàng)建一個alertView的時候,只能取到第一個參數(shù)。什么意思呢?比如你在調(diào)用的時候傳入@"A",@"B",nil,如果你直接使用了這個參數(shù),那么你的alertView只能顯示"A"這個按鈕。具體的原因我找了好久也沒有找到(如果你知道,請告訴我,thank you)。所以我采用了遍歷這個變參,通過alertView自身的addButtonWithTitle:方法來添加按鈕。
有關(guān)可變參數(shù)的知識,可以參考wikipedia中的解釋
接下來的就是runtime中的關(guān)聯(lián)對象(associative)了。我們要在alertView的代理方法中,執(zhí)行block,所以我們需要把block保存起來(雖然可以用全局變量的來做)。但是我們知道,oc的分類(也叫類別)是無法擴展屬性的(I don't konw why)。那么我們則可用在運行時給他關(guān)聯(lián)上。
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
-- object是關(guān)聯(lián)到誰身上(self)。
-- void *key就是一個鍵,用來找到關(guān)聯(lián)的對象,和NSDictionary的key是一樣的,需要傳入一個void *指針。
-- value是要關(guān)聯(lián)的對象(這里是block)。
-- 最后一個是對這個對象(block)的內(nèi)存管理方針。取值和oc中的屬性修飾符是像對應(yīng)的,比如 OBJC_ASSOCIATION_COPY=> copy。
與之相對應(yīng)方法objc_getAssociatedObject(id object,void *key)則就是從字典中取值一般。
方法二除了更簡潔,總的來說于一無差。
我們在使用的時候呢,則會比較方便了,不必寫代理方法、不必判斷,而且代碼邏輯不必分散。
細(xì)心的你可能已經(jīng)注意到了這種方式的隱患,就是block的保留循環(huán)(retain cycle)問題。
self通過關(guān)聯(lián)對象保留了action塊,如果在在action塊中引用了self的成員變量(假定為對象),比如說對這個對象賦值,那么action這個block則會通過這個成員變量間接又引用self,那么保留循環(huán)就產(chǎn)生了。
我們這個比較好解決,因為我們在點擊完了alertView之后,就不必在持有這個action塊了,所以在callBack執(zhí)行之后(見方法二圖),就把關(guān)聯(lián)的對象給remove掉了。為什么不用objc_removeAssociatedObjects?蘋果說這個方法的目的是為了讓被關(guān)聯(lián)者,回到最初的狀態(tài)。如果你僅僅需要remove某個關(guān)聯(lián)的對象,用set方法設(shè)置成nil就行了。
總的來說,關(guān)聯(lián)對象還是挺有意思的。擴展開來,比如說你要給任何的視圖添加tap手勢、滑動手勢等,都可以使用這種方式。不過需要注意的就是保留循環(huán)的問題。
歡迎各位指正糾錯,與君共勉。