iOS 關于Category

一 類別的簡介

在開發中有時會用到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

來源:簡書

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 寫在前面 最近終于抽出時間來對這篇文章進行更改了,在這個過程中多謝大家的指導和意見,當時寫這篇文章的時候原本是想記...
    劉光軍_MVP閱讀 13,406評論 42 128
  • 出題者簡介: 孫源(sunnyxx),目前就職于百度,負責百度知道 iOS 客戶端的開發工作,對技術喜歡刨根問底和...
    戈多_于勒閱讀 1,812評論 0 5
  • 我們總這樣,行色匆匆...
    為什么要假裝堅強閱讀 219評論 0 0
  • 不管你有多不開心 我們都有責任先吃好一頓飯 睡好一個覺 打扮好自己 很多煩惱 其實都沒什么大不了 只是你在那個情境...
    安女神醫要考研閱讀 161評論 0 0
  • 凌晨兩點鐘,一覺醒來,竟無眠。 沒有一絲困意,睡前還有點頭痛的,可是只睡了三個小時,竟然精神這樣好。此刻正是睡眠的...
    風之吻Sam閱讀 354評論 0 0