一 類別的簡介
在開發中有時會用到Category,類別有三個作用:
(1)可以將類的實現分散到多個不同文件或多個不同框架中,方便代碼管理。也可以對框架提供類的擴展(因為框架類沒有源碼,不能修改)。
(2)創建對私有方法的前向引用:如果其他類中的方法未實現,在你訪問其他類的私有方法時編譯器報錯這時使用類別,在類別中聲明這些方法(不必提供方法實現),編譯器就不會再產生警告
(3)向對象添加非正式協議:創建一個NSObject的類別稱為“創建一個非正式協議”,因為可以作為任何類的委托對象使用。有兩個方面的局限性:(1)無法向類中添加新的實例變量,類別沒有位置容納實例變量。(2)名稱沖突,即當類別中的方法與原始類方法名稱沖突時,類別具有更高的優先級。類別方法將完全取代初始方法從而無法再使用初始方法。這個類似于方法的重載,但是這里是直接覆蓋了原方法。
但是我們又經常聽說在Category里面無法添加屬性,那么額。。??催@里:
哦呦,屬性哎!!?。∈裁垂恚坎皇遣荒芴砑訉傩詥?????這個問題真心煩人啊,怎么回事兒呢?以前看的他們說的Category不能添加屬性難道不對嗎?(⊙o⊙)…相信很多童鞋會有和我一樣的疑問。。。
首先解釋一下屬性和成員變量的區別吧:
二 屬性和成員變量的區別
@property (nonatomic, strong) UIButton *myButton;
我們聲明了一個屬性,因為現在我們用的編譯器已經是LLVM了,所以不再需要為屬性聲明實例變量了。如果LLVM發現一個沒有匹配實例變量的屬性,它將為你生成以下劃線開頭的實例變量_myButton,不需要自己手動再去寫實例變量。而且也不需要在.m文件中寫@synthesize myButton;也會自動為你生成setter,getter方法。@synthesize的作用就是讓編譯器為你自動生成setter與getter方法。那么在.m文件中可以直接的使用_myButton實例變量,也可以通過屬性self.myButton.兩者都是一樣的。
注意:
這里的self.myButton其實是調用的myButton屬性的getter/setter方法。
假如在Objective-C中我們
@interface MyViewController:UIViewController
{
NSString *name;
}
@end
.m文件中,self.name這樣的表達式是錯誤的。Xcode會提示你使用->,改成self->name就可以了。因為oc中點表達式是表示調用方法,而上面的代碼中沒有name這個方法。
Objective-C中的點語法說明:
如果點表達式出現在 "=" 左邊,該屬性名稱的setter方法將被調用。如果點表達式出現在右邊,該屬性名稱的getter方法將被調用。所以在oc中點表達式其實就是調用對象的setter和getter方法的一種快捷方式。
@synthesize還有一個作用,可以指定與屬性對應的實例變量,例如@synthesize myButton = xxxx;那么self.myButton其實是操作的實例變量xxxx,而不是_myButton了。
在實際項目中,我們一般在.m中這樣寫:@synthesize myButton;這樣寫了之后,那么編譯器會自動生成myButton的實例變量,以及相應的getter和setter方法。注意:_myButton這個實例變量是不存在的,因為自動生成的實例變量為myButton而不是_myButton,所以現在@synthesize的作用就相當于指定實例變量;
如果.m文件中寫了@synthesize myButton;那么生成的實例變量就是myButton;如果沒寫@synthesize myButton;那么生成的實例變量就是_myButton。所以跟以前的用法還是有點細微的區別。
三 Category中屬性Property
我們經常看見在類別中這樣寫:
@property (nonatomic, assign) CGFloat x;
這里添加的屬性,實現我們在外部的調用,其實在這種情況下是不會自動生成實例變量的,我們只是通過重寫setter和getter方法來對self.frame進行操作,并不是針對的我們聲明的比如x,我們使用的時候getter方法中返回的是 self.frame.origin.x
屏幕快照 2016-03-15 21.21.14.png
為什么不能添加成員變量呢?
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針。它的定義如下:
typedef struct objc_class *Class;
objc_class結構體的定義如下:
struct objc_class {
Class isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 父類
const char *name? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 類名
long version? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 類的版本信息,默認為0
long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 類信息,供運行期使用的一些位標識
long instance_size? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 該類的實例變量大小
struct objc_ivar_list *ivars? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 該類的成員變量鏈表
struct objc_method_list **methodLists? OBJC2_UNAVAILABLE;? // 方法定義的鏈表
struct objc_cache *cache? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 方法緩存
struct objc_protocol_list *protocols? ? OBJC2_UNAVAILABLE;? // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class結構體中,ivars是objc_ivar_list(成員變量列表)指針;methodLists是指向objc_method_list指針的指針。在Runtime中,objc_class結構體大小是固定的,不可能往這個結構體中添加數據,只能修改。所以ivars指向的是一個固定區域,只能修改成員變量值,不能增加成員變量個數。methodList是一個二維數組,所以可以修改*methodLists的值來增加成員方法,雖沒辦法擴展methodLists指向的內存區域,卻可以改變這個內存區域的值(存儲的是指針)。因此,可以動態添加方法,不能添加成員變量。
在Objective-C提供的runtime函數中,確實有一個class_addIvar()函數用于給類添加成員變量,但是文檔中特別說明:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
意思是說,這個函數只能在“構建一個類的過程中”調用。一旦完成類定義,就不能再添加成員變量了。經過編譯的類在程序啟動后就runtime加載,沒有機會調用addIvar。程序在運行時動態構建的類需要在調用objc_allocateClassPair之后,objc_registerClassPair之前才可以被使用,同樣沒有機會再添加成員變量。
Category不能添加成員變量(instance variables),那到底能不能添加屬性(property)呢?
這個我們要從Category的結構體開始分析:
typedef struct category_t {
const char *name;? //類的名字
classref_t cls;? //類
struct method_list_t *instanceMethods;? //category中所有給類添加的實例方法的列表
struct method_list_t *classMethods;? //category中所有添加的類方法的列表
struct protocol_list_t *protocols;? //category實現的所有協議的列表
struct property_list_t *instanceProperties;? //category中添加的所有屬性
} category_t;
從Category的定義也可以看出Category的可為(可以添加實例方法,類方法,甚至可以實現協議,添加屬性)和不可為(無法添加實例變量)。
那我們為什么經常聽說說類別不能添加屬性呢?實際上,Category實際上允許添加屬性的,同樣可以使用@property,但是不會生成_變量(帶下劃線的成員變量),也不會生成添加屬性的getter和setter方法,所以,盡管添加了屬性,也無法使用點語法調用getter和setter方法。那我們想要實現我們平時的屬性所具有的功能應該怎么樣做呢?
其實我們可以使用runtime去做,使用runtime去實現Category為已有的類添加新的屬性并生成getter和setter方法。不要忘記了Objective-C是動態語言。方法是通過runtime.h中objc_getAssociatedObject / objc_setAssociatedObject來訪問和生成關聯對象。這兩個方法可以讓一個對象和另一個對象關聯,就是說一個對象可以保持對另一個對象的引用,并獲取那個對象。
//NSObject+IndieBandName.h
@interface NSObject (IndieBandName)
@property (nonatomic, strong) NSString *indieBandName;
@end
上面是頭文件聲明,下面的實現的.m文件:
// NSObject+IndieBandName.m
#import "NSObject+Extension.h"
#import
static const void *IndieBandNameKey = &IndieBandNameKey;
@implementation NSObject (IndieBandName)
@dynamic indieBandName;
- (NSString *)indieBandName {
return objc_getAssociatedObject(self, IndieBandNameKey);
}
- (void)setIndieBandName:(NSString *)indieBandName {
objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
通過runtime的兩種方法就可以為類別添加一個實例變量了。
四 Category與Extension
1、Extension的基本用法
Extension的創建方法與Category一樣,只要在原來選擇Category選擇Extension即可,比如我們為Person創建一個名為MyExtension的Extension,則最終會生成一個Person_MyExtension.h文件:
//? Person_MyExtension.h
#import "Person.h"
@interface Person ()
@end
但要注意的是和Category不同的是它不會生成Person_MyExtension.m文件。之后我們可以在Person_MyExtension.h中直接添加成員變量、屬性和方法,如下:
//? Person_MyExtension.h
#import "Person.h"
@interface Person ()
{
NSString * _address;
}
@property (nonatomic) NSInteger age;
-(NSString*)WhereAmI;
@end
他常用的形式不是創建一個單獨的文件,而是在實現文件中添加私有的成員變量、屬性和方法。例如:
//? Person.m
#import "Person.h"
/////////Extension start///////////
@interface Person ()
{
NSString * _address;
}
@property (nonatomic) NSInteger age;
-(NSString*)WhereAmI;
@end
/////////Extension end///////////
@implementation Person
-(NSString*)WhereAmI{
return @"誰知道你在哪里";
}
@end
2、Extension與Category區別
Extension
在編譯器決議,是類的一部分,在編譯器和頭文件的@interface和實現文件里的@implement一起形成了一個完整的類。
伴隨著類的產生而產生,也隨著類的消失而消失。
Extension一般用來隱藏類的私有消息,你必須有一個類的源碼才能添加一個類的Extension,所以對于系統一些類,如NSString,就無法添加類擴展
Category
是運行期決議的
類擴展可以添加實例變量,分類不能添加實例變量
原因:因為在運行期,對象的內存布局已經確定,如果添加實例變量會破壞類的內部布局,這對編譯性語言是災難性的。
作者:劉光軍_Shine
鏈接:http://www.lxweimin.com/p/535d1574cb86
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。