編輯:關於Android編程
在OC中,我們可以通過Category 對已有的類進行擴展,這得益於OC的Runtime機制,讓類可以‘動態’的添加方法以及實現。
但是,在Category中我們無法向已有的類中添加屬性,這是因為OC中記錄當前類屬性的ivars無法動態改變的緣故。
那麼,我們真的就無法通過Category向已有的類添加屬性了嗎?看本文標題就知道,還是有辦法可以實現的。
讓我們先回憶一下,當我們使用@property關鍵字聲明屬性時,OC都為我們做了什麼。
如下代碼
@interface UIView (testCategory) @property(nonatomic, strong) UIView *firstView; @property(nonatomic, assign) BOOL isShown; @end
我在UIView的Category中聲明了兩個屬性,firstView與isShown。
一般情況下,.h中聲明屬性後,我們就可在類的實例中使用這些屬性了。
為了能夠正確使用屬性,OC會默認為我們完成以下工作
由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的方法。
在上面的示例代碼中,我們發現,僅僅在.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變量像屬性一樣與類實例關聯起來,並像屬性一樣調用。
OC的Runtime為我們提供了能夠將id變量通過key的方式與當前類實例關聯起來。
當我們想要建立關聯時,需要引入runtime頭文件:
關聯方法則包括如下三個C函數:
objc_setAssociatedObject objc_getAssociatedObjectobjc_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異常。
在Android Volley分析(一)——結構中主要分析了Volley的基本組件和框架結構,組件主要是定義的接口,也就是說我們可以實現這些接口來定制自己的Volley版
隨著最近魅族魅藍e正式發布,再次加劇了魅族和小米之間在千元機之間的對決。許多網友將魅藍e和前不久剛剛發布的紅米pro進行對比了,那麼到底魅藍e和紅米pro哪
剛剛接手一個備份系統浏覽器書簽的模塊,現在把代碼貼出來,另外有幾點疑問請路過的大神指教 1、根據官方api應該是有以下幾個字段是可以獲取的 但是除了
常用的Dialog有確認對話框,單選按鈕對話框,多選按鈕對話框,復選按鈕對話框另外還有自定義的對話框AlertDialog的常用方法setTitle:為對話框設置標題se