1. 程式人生 > >OC學習之Runtime之關聯物件

OC學習之Runtime之關聯物件

堅持 成長 每日一篇

前言

在Runtime中有一個我們經常忽略的特性就是關聯物件特性。
關聯物件類似於成員變數,不同的是關聯物件是在類建立例項後新增進去的。所以不能通過Runtime的獲取成員變數的方法獲取相關資訊。
注意:對關聯函式的操作只能通過下面的C函式進行操作。

// 設定關聯物件void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );   
// 獲取關聯物件id objc_getAssociatedObject ( id
object, const void *key ); //移除關聯物件void objc_removeAssociatedObjects ( id object );

在對於系統的類如果我們想個類新增屬性,由於我們不能給類別裡面新增成員變數,只能通過建立該類的子類來實現。
Objective-C針對這一問題,提供了一個解決方案:即關聯物件(Associated Object)。
我們可以把關聯物件想象成一個Objective-C物件(如字典),這個物件通過給定的key連線到類的一個例項上。不過由於使用的是C介面,所以key是一個void指標(const void *)。我們還需要指定一個記憶體管理策略,以告訴Runtime如何管理這個物件的記憶體。這個記憶體管理的策略可以由以下值指定:

enum {
    OBJC_ASSOCIATION_ASSIGN = 0, //弱引用,物件銷燬不會造成關聯物件的引用計數變化
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //強引用,不支援多執行緒安全
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //深拷貝,不支援多執行緒安全
    OBJC_ASSOCIATION_RETAIN = 01401, //強引用支援執行緒安全
    OBJC_ASSOCIATION_COPY = 01403  //深拷貝,支援執行緒安全
};

例子

建立一個UIView的分類,我們要給分類新增一個單擊手勢回撥程式碼塊。
UIView+Tap.h

#import <UIKit/UIKit.h>

@interface UIView (Tap)
- (void)setTapActionWithBlock:(void (^)(void))block;
@end

UIView+Tap.m

#import "UIView+Tap.h"
#import <objc/runtime.h>
#define keyForAction "action"
#define keyForRecognizer "Recognizer"
@implementation UIView (Tap)
- (void)setTapActionWithBlock:(void (^)(void))block
{
    //如果這裡不使用關聯物件,你可能需要不停移除手勢,然後再建立手勢
    UITapGestureRecognizer *gesture = objc_getAssociatedObject(self,keyForRecognizer);
    if (!gesture)
    {
        gesture = [[UITapGestureRecognizer alloc] initWithTarget:self      action:@selector(actionForTapGesture:)];
        [self addGestureRecognizer:gesture];
        objc_setAssociatedObject(self, keyForRecognizer, gesture,  OBJC_ASSOCIATION_RETAIN);
    }
    //這裡可以成功的新增一個關聯物件給後面的手勢回撥函式使用,而不需要通過全域性變數或成員變數。
    objc_setAssociatedObject(self, keyForAction, block, OBJC_ASSOCIATION_COPY);
}
//實現回撥函式
- (void)actionForTapGesture:(UITapGestureRecognizer *)gesture
{
    if (gesture.state == UIGestureRecognizerStateRecognized)
    {
        //我們只能通過objc_getAssociatedObject來取出關聯物件
        void(^action)(void) = objc_getAssociatedObject(self, keyForAction);
        if (action)
        { 
            action(); 
        } 
    }
}
@end

在控制器裡面測試

- (void)viewDidLoad {
    [super viewDidLoad];
    //這樣實現了不需要建立分類,很好的拓展了已有的UIView類。
    [self.view setTapActionWithBlock:^{
        NSLog(@"is tap");
    }];
}

執行程式我們就會看到當我們點選控制檢視時候輸出

2015-09-15 09:59:21.869 test[5410:381831] is tap
2015-09-15 09:59:23.005 test[5410:381831] is tap
2015-09-15 09:59:23.545 test[5410:381831] is tap

總結:關聯物件是一個非常實用的Runtime特性。可以很好的幫我們解決開發中遇到很多如傳值,擴充套件物件等問題。