Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Objective-C Runtime 解析(四)—— 在Category中添加屬性

Objective-C Runtime 解析(四)—— 在Category中添加屬性

編輯:關於Android編程

在OC中,我們可以通過Category 對已有的類進行擴展,這得益於OC的Runtime機制,讓類可以‘動態’的添加方法以及實現。
但是,在Category中我們無法向已有的類中添加屬性,這是因為OC中記錄當前類屬性的ivars無法動態改變的緣故。
那麼,我們真的就無法通過Category向已有的類添加屬性了嗎?看本文標題就知道,還是有辦法可以實現的。

Category的限制

讓我們先回憶一下,當我們使用@property關鍵字聲明屬性時,OC都為我們做了什麼。
如下代碼

@interface UIView (testCategory)
@property(nonatomic, strong) UIView *firstView;
@property(nonatomic, assign) BOOL isShown;
@end

我在UIView的Category中聲明了兩個屬性,firstView與isShown。
一般情況下,.h中聲明屬性後,我們就可在類的實例中使用這些屬性了。
為了能夠正確使用屬性,OC會默認為我們完成以下工作

在.m中,編譯器通過@synthesize關鍵字,將我們聲明的屬性轉換為了對應的實例變量。並默認在屬性名稱前添加‘_’來在類中標識對應的實例變量。 根據我們在@property中指定的訪問限制說明(readwrite,readonly),編譯器會自動生成默認的setter/getter方法(其本質仍是對帶下劃線的實例變量的操作)。(如果是readonly,則只會生成getter方法)

由property生成的實例變量,會在類實例創建時(alloc)被分配內存,類實例銷毀時釋放內存。

現在我們再來看看上面OC為了支持property所做的的工作。其中@synthesize與生成setter/getter方法,均是在編譯期完成的,而Category則屬於Runtime時期加載,自然編譯器就不會為我們做@synthesize與生成對應setter/getter方法的工作了,因此我們也就不能夠在Category中添加屬性。

所以,當我使用UIView (testCategory)類實例調用firstView屬性時,程序運行時會因為unrecognized selector 異常退出。(因為編譯器並沒有為我們生成對應的accessor方法)

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *myView = [[UIView alloc] init];
    myView.firstView = [[UIView alloc] init]; // unrecognized selector crash!!

}

異常錯誤
這裡寫圖片描述

好,既然編譯器沒有自動生成accessor方法,那我自己寫可以嗎?

這裡寫圖片描述

如圖中所示,因為編譯器不會為我們生成對應的_fristView實例變量,因此accessZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcre9t6jW0LvhzOHKvs60yfnD97Hkwb9fZmlyc3RWaWV3tO3O86OstbzWwrHg0uvO3reozai5/aGjPC9wPg0KPHA+ztK7ucrHsrvLwNDEo6zO0tKq08NAc3ludGhlc2l6ZcC0uObL37Hg0uvG99KqvfjQ0HByb3BlcnR5LSZndDvKtcD9seTBv7XE16q7u6O6PC9wPg0KPHA+PGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160906/2016090609283725.png" title="\" />

OK,Xcode編輯器說的很明確,不能夠在Category中使用@synthesize關鍵字。原因很簡單,因為@synthesize是在編譯期完成的,對於Runtime時期才加載的Category,自然是沒有意義的。
服了吧,難道我們真的不能夠在Category中添加屬性嗎?下面,我們就介紹兩種在Category中添加property的方法。

僅聲明@property

在上面的示例代碼中,我們發現,僅僅在.h中聲明屬性,是不會有任何編譯錯誤的,而且可以編譯通過。
只是在使用屬性的時候,由於沒有實現相應的accessor方法,才會引發unrecognized selector異常。
那麼,我們可以不借助編譯器,而是在Category的.m文件中手動實現accessor方法。之前說過,在Category中直接用下劃線屬性名稱的方式是無法獲取對應的實例變量的。我們這裡就不創建新的實例變量,而是對當前類已有的屬性做一個封裝,這裡面對已有屬性做一些操作,實際上是一個函數,但卻可以通過屬性的方式進行調用。
示例代碼如下:

#import 

@interface UIView (testCategory)
@property(nonatomic, strong) UIView *firstView;
//@property(nonatomic, assign) BOOL isShown;
@end



#import "UIView+testCategory.h"


@implementation UIView (testCategory)

-(void) setFirstView:(UIView *)firstView
{
    [self addSubview:firstView];
    [self bringSubviewToFront:firstView];
}

-(UIView *) firstView
{
    return self.subviews.firstObject;
}
@end

在UIView testCategory中我聲明了一個UIView *property叫firstView,同時手工實現了其accessor方法,由於在accessor方法中我並沒有使用任何新的實例變量,而是對UIView的subviews操作做了一些封裝,因此編譯器並不會報任何錯誤。

但是,在代碼中,我們卻可以像屬性一樣調用firstView:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *myView = [[UIView alloc] init];
    myView.firstView = [[UIView alloc] init];

}

這種方式實際上是通過property的形式調用函數方法,並沒有在類中添加新的屬性。下面一種方法,則可以將id變量像屬性一樣與類實例關聯起來,並像屬性一樣調用。

Associated Objects

OC的Runtime為我們提供了能夠將id變量通過key的方式與當前類實例關聯起來。
當我們想要建立關聯時,需要引入runtime頭文件:

關聯方法則包括如下三個C函數:

objc_setAssociatedObject objc_getAssociatedObject

objc_removeAssociatedObjects

我們想通過Category向類添加屬性,則在.h中先聲明屬性:

#import 

@interface UIView (testCategory)
@property(nonatomic, strong) UIView *firstView;
//@property(nonatomic, assign) BOOL isShown;
@end

在.m中編寫對應的accessor方法:

#import 
#import "UIView+testCategory.h"


@implementation UIView (testCategory)

-(void) setFirstView:(UIView *)firstView
{
    objc_setAssociatedObject(self, @selector(firstView), firstView, OBJC_ASSOCIATION_RETAIN);
}

-(UIView *) firstView
{
    return objc_getAssociatedObject(self, @selector(firstView));
}
@end

其中objc_setAssociatedObject, objc_getAssociatedObject的聲明為:

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */

id objc_getAssociatedObject(id object, const void *key)

這裡要說明的只有一點,關於建立關聯的key值,key值可以是任何類型,但是要保證其唯一性,const,並且在setter和getter中均能夠被訪問。通常我們使用一個static const char * 來作為key。但是在OC中,由於@selector恰好具有同樣的特性,因此這裡我們將@selector(firstView)作為了key值。

在代碼中,我們可以這樣調用我們的屬性firstView:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *myView = [[UIView alloc] init];
    myView.firstView = [[UIView alloc] init];
    myView.firstView.tag = 12;
    NSLog(@"The view tag is %ld", myView.firstView.tag);

}

輸出:
這裡寫圖片描述

OK,我們已經成功的在Category中為UIView類“添加”了新的屬性firstView。

需要注意的是,通過associate方法管理對象,其關聯的對象是id類型,即必須是NSObject類及其子類對象。對於像BOOL這樣的一般變量,是無法關聯的。

另外,對應於屬性的strong,copy,weak,atomic,在關聯對象也有對應的關聯policy,見說明:

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

對於OBJC_ASSOCIATION_ASSIGN,雖然是weak引用,但其並不會像property的weak那樣釋放後自動為nil,而是一個野指針。這裡要注意不要引發BAD ACCESS異常。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved