文章也同時在個人博客 http://kimihe.com/更新
引言
最近在補(bǔ)習(xí)《Effective Objective-C 2.0》。其中涉及到部分OC runtime的知識,runtime是OC的一個重要特性谈宛,掌握它對于理解OC有著巨大的幫助秽誊,也能夠提升我們的開發(fā)能力每聪。
OC的runtime可以將我們的程序在運(yùn)行期進(jìn)行一系列控制忍法,表象看起來如同我們可以動態(tài)修改正在運(yùn)行的程序面粮。我將其視為OC語法的封裝 -> C高級API的轉(zhuǎn)化魔法灾挨,因?yàn)樵谧罱K邑退,我們的程序還是會被翻譯成是C(以及C++)的樣式。在C的層面上劳澄,我們或許能夠忽略runtime這種說法地技,畢竟C幾乎可以做任何事情。
可以說秒拔,如果你了解runtime的設(shè)計(jì)理念莫矗,你將可以親手用C實(shí)現(xiàn)一個類,包含一些最基本的面相對象的特性砂缩。runtime也是開源的作谚,有興趣不妨學(xué)習(xí)一下。
我們平時在學(xué)習(xí)OC時庵芭,可能都聽說過Category的作用妹懒,可以拆分代碼,增加方法等等双吆。不過在一般意義下無法增加實(shí)例變量眨唬,這可能會阻礙我們實(shí)現(xiàn)一些有趣的idea会前,但是runtime可以非常簡單地跨過這個限制,將我們帶入OC特性的更深層次匾竿。
本文將通過一個非常簡單的Demo瓦宜,來演示runtime中關(guān)聯(lián)對象的作用。我們修改UIAlertView搂橙,給它增加一個傳入ActionBlock的方法歉提,我們可以直接在ActionBlock中編寫邏輯代碼,避免了在相關(guān)protocol的方法中進(jìn)行操作的繁雜区转。雖然UIAlertView在iOS9.0后被UIAlertController代替,但卻非常適合用于教學(xué)演示版扩。
好書推薦
《Effective Objective-C 2.0》是一本非常適合深入學(xué)習(xí)OC內(nèi)涵的書废离,很多開發(fā)中的疑惑都可以在書中找到較為科學(xué)的解答。不過礁芦,這本書需要一定的iOS開發(fā)積累蜻韭,可能不適合初學(xué)者。
代碼地址
https://github.com/kimihe/KMiOSLib/tree/master/ObjcRuntimeResearch/UIAlertViewMagic
原理詳解
思路
我們的目的很明確柿扣,給UIAlertView增加可以傳入邏輯代碼塊的ActionBlock接口肖方,這樣我們就不需要把這些代碼寫在相關(guān)protocol的方法中了,使代碼更加集中未状,便于理解和管理俯画。我們的思路如下:
- 通過Category來擴(kuò)充這個UIAlertView類。
- 遵循“勿在分類中聲明屬性”這一原則司草,不聲明ActionBlock的@property艰垂,而是直接提供一個setup方法。
- 通過runtime的objc_associatedObject來“變相”給分類增加實(shí)例變量埋虹,即把我們要增加的實(shí)例變量(ActionBlock)關(guān)聯(lián)到UIAlerView分類
實(shí)現(xiàn)
我們新增一個分類UIAlertView+ActionBlock猜憎,在頭文件中聲明我們的ActionBlock類型,并增加一個傳block的方法搔课。
#import <UIKit/UIKit.h>
typedef void(^ActionBlock)(UIAlertView*, NSInteger);
@interface UIAlertView (ActionBlock) <UIAlertViewDelegate>
/**
設(shè)置一個ActionBlock
@param action 需要傳入的block
*/
- (void)setupActionBlock:(ActionBlock)action;
@end
在實(shí)現(xiàn)文件中我們只需要實(shí)現(xiàn)兩個模塊胰柑,一是完成關(guān)聯(lián)對象,二是在何時的時機(jī)調(diào)用外部傳入的ActionBlock爬泥。
完成關(guān)聯(lián)對象
在我們添加的方法中柬讨,利用runtime完成關(guān)聯(lián)對象,變相存儲外部傳入的block急灭,如下:
static const void *KMAlertViewKey = "KMAlertViewKey";
...
- (void)setupActionBlock:(ActionBlock)action
{
objc_setAssociatedObject(self, KMAlertViewKey, action, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.delegate = self;
}
...
代碼就只有兩行姐浮,第一行是設(shè)置關(guān)聯(lián)對象,把傳入的block(action)關(guān)聯(lián)到self(UIAlerView分類)葬馋,其中的KMAlertViewKey是一個唯一區(qū)分標(biāo)識符卖鲤,類似于字典的鍵肾扰,以此來尋找我們關(guān)聯(lián)的對象。OBJC_ASSOCIATION_COPY_NONATOMIC
是內(nèi)存管理語義蛋逾,我們強(qiáng)調(diào):block通過copy存放在堆上集晚。
第二行重新覆蓋delegate,將其引導(dǎo)至本實(shí)現(xiàn)文件中区匣,從而實(shí)現(xiàn)protocol的方法偷拔,并適時進(jìn)行ActionBlock的調(diào)用。
調(diào)用ActionBlock
經(jīng)過上述的delegate重定向亏钩,我們可以攔截相關(guān)方法莲绰,并在其中調(diào)用ActionBlock,完成外部傳入的邏輯代碼姑丑,如下:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
ActionBlock action = objc_getAssociatedObject(self, KMAlertViewKey);
action(alertView, buttonIndex);
}
和設(shè)置關(guān)聯(lián)對象一樣蛤签,代碼的第一行,我們?nèi)〕龇诸愱P(guān)聯(lián)的ActionBlock栅哀。然后在代碼的第二行進(jìn)行調(diào)用震肮。
使用演示
在ViewController.m中進(jìn)行使用,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Title" message:@"Message" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alert setupActionBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
NSLog(@"You did click button index %ld", (long)buttonIndex);
}];
[alert show];
}
注意留拾,我們在生成一個alert時會聲明其delegate戳晌,但是當(dāng)執(zhí)行到后續(xù)的setupActionBlock:
方法時,我們其實(shí)會覆蓋這個delegate痴柔,將其引導(dǎo)至新增的分類中沦偎,所以在使用時,我們可以完全不編寫相關(guān)的protocol方法竞帽,而是直接在block中傳入邏輯代碼扛施。
運(yùn)行一下,點(diǎn)擊alert的OK按鈕屹篓,控制臺輸出符合預(yù)期:
2016-12-26 16:05:53.776 UIAlertViewMagic[2082:70283] You did click button index 1
總結(jié)
本文通過給UIAlertView增加一個actionBlock回調(diào)疙渣,演示了OC runtime的“關(guān)聯(lián)對象”(objc_associatedObject)的功能。算是對OC runtime的涉獵堆巧。
要點(diǎn)
- iOS9.0后妄荔,你應(yīng)該使用UIAlertController,棄用UIAlerView谍肤!
- 代碼主要講解了如何使用runtime的objc_associatedObject啦租,模擬部分UIAlertController的功能,使用了category來增加一個方法荒揣,但在實(shí)際開發(fā)中篷角,還是建議通過繼承使用子類來實(shí)現(xiàn)。畢竟category中增加實(shí)例變量屬于一種trick技巧系任。
- 我們需要一個內(nèi)部block來存儲外部傳進(jìn)來的action恳蹲,但是category無法在一般意義上增加實(shí)例變量虐块, 因此可以使用runtime魔法實(shí)現(xiàn)。
- 我們依舊遵照“勿在分類中聲明屬性”這一原則嘉蕾,不聲明void (^block)(UIAlertView*, NSInteger)的@property贺奠,直接提供一個setup方法。