Category
1、什么是Category?
category是Objective-C 2.0之后添加的語言特性,別人口中的分類晋渺、類別其實都是指的category。category的主要作用是為已經(jīng)存在的類添加方法脓斩。除此之外木西,apple還推薦了category的另外兩個使用場景。
可以把類的實現(xiàn)分開在幾個不同的文件里面随静。這樣做有幾個顯而易見的好處八千。
- 可以減少單個文件的體積
- 可以把不同的功能組織到不同的category里
- 可以由多個開發(fā)者共同完成一個類
- 可以按需加載想要的category
- 聲明私有方法
apple 的SDK中就大面積的使用了category這一特性。比如UIKit中的UIView燎猛。apple把不同的功能API進行了分類恋捆,這些分類包括UIViewGeometry、UIViewHierarchy重绷、UIViewRendering等沸停。
不過除了apple推薦的使用場景,廣大開發(fā)者腦洞大開昭卓,還衍生出了category的其他幾個使用場景:
- 模擬多繼承(另外可以模擬多繼承的還有protocol)
- 把framework的私有方法公開
2愤钾、category特點
- category只能給某個已有的類擴充方法,不能擴充成員變量候醒。
- category中也可以添加屬性能颁,只不過@property只會生成setter和getter的聲明,不會生成setter和getter的實現(xiàn)以及成員變量倒淫。
- 如果category中的方法和類中原有方法同名伙菊,運行時會優(yōu)先調用category中的方法。也就是敌土,category中的方法會覆蓋掉類中原有的方法镜硕。所以開發(fā)中盡量保證不要讓分類中的方法和原有類中的方法名相同。避免出現(xiàn)這種情況的解決方案是給分類的方法名統(tǒng)一添加前綴纯赎。比如category_谦疾。
- 如果多個category中存在同名的方法,運行時到底調用哪個方法由編譯器決定犬金,最后一個參與編譯的方法會被調用念恍。
3、調用優(yōu)先級
分類(category) > 本類 > 父類晚顷。即峰伙,優(yōu)先調用cateory中的方法,然后調用本類方法该默,最后調用父類方法瞳氓。
注意:category是在運行時加載的,不是在編譯時栓袖。
4匣摘、為什么category不能添加成員變量店诗?
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; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class結構體中赠叼,ivars是objc_ivar_list(成員變量列表)指針擦囊;methodLists是指向objc_method_list指針的指針。在Runtime中嘴办,objc_class結構體大小是固定的瞬场,不可能往這個結構體中添加數(shù)據(jù),只能修改涧郊。所以ivars指向的是一個固定區(qū)域贯被,只能修改成員變量值,不能增加成員變量個數(shù)妆艘。methodList是一個二維數(shù)組刃榨,所以可以修改*methodLists的值來增加成員方法,雖沒辦法擴展methodLists指向的內存區(qū)域双仍,卻可以改變這個內存區(qū)域的值(存儲的是指針)枢希。因此,可以動態(tài)添加方法朱沃,不能添加成員變量苞轿。
5、category中能添加屬性嗎逗物?
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實現(xiàn)的所有協(xié)議的列表
struct property_list_t *instanceProperties; //category中添加的所有屬性
} category_t;
從Category的定義也可以看出Category的可為(可以添加實例方法翎卓,類方法契邀,甚至可以實現(xiàn)協(xié)議,添加屬性)和不可為(無法添加實例變量)失暴。
但是為什么網(wǎng)上很多人都說Category不能添加屬性呢坯门?
實際上,Category實際上允許添加屬性的逗扒,同樣可以使用@property古戴,但是不會生成_變量(帶下劃線的成員變量),也不會生成添加屬性的getter和setter方法的實現(xiàn)矩肩,所以现恼,盡管添加了屬性,也無法使用點語法調用getter和setter方法(實際上,點語法是可以寫的叉袍,只不過在運行時調用到這個方法時候會報方法找不到的錯誤始锚,如下圖)。但實際上可以使用runtime去實現(xiàn)Category為已有的類添加新的屬性并生成getter和setter方法喳逛。
注意的有兩點:
1)疼蛾、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA艺配,那么category附加完成之后,類的方法列表里會有兩個methodA衍慎。
2)转唉、category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面稳捆,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法赠法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法乔夯,就會罷休砖织,殊不知后面可能還有一樣名字的方法。
Extension
1末荐、 什么是extension
extension被開發(fā)者稱之為擴展侧纯、延展、匿名分類甲脏。extension看起來很像一個匿名的category眶熬,但是extension和category幾乎完全是兩個東西。和category不同的是extension不但可以聲明方法块请,還可以聲明屬性娜氏、成員變量。extension一般用于聲明私有方法墩新,私有屬性贸弥,私有成員變量。
2海渊、 extension的存在形式
category是擁有.h文件和.m文件的東西绵疲。但是extension不然。extension只存在于一個.h文件中臣疑,或者extension只能寄生于一個類的.m文件中最岗。比如,viewController.m文件中通常寄生這么個東西朝捆,其實這就是一個extension:
@interface ViewController ()
@end
當然我們也可以創(chuàng)建一個單獨的extension文件
注意:extension常用的形式并不是以一個單獨的.h文件存在般渡,而是寄生在類的.m文件中。
三)category和extension的區(qū)別
就category和extension的區(qū)別來看,我們可以推導出一個明顯的事實驯用,extension可以添加實例變量脸秽,而category是無法添加實例變量的(因為在運行期,對象的內存布局已經(jīng)確定蝴乔,如果添加實例變量就會破壞類的內部布局记餐,這對編譯型語言來說是災難性的)。
- extension在編譯期決議薇正,它就是類的一部分片酝,但是category則完全不一樣,它是在運行期決議的挖腰。extension在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類雕沿,它、extension伴隨類的產生而產生猴仑,亦隨之一起消亡审轮。
- extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension辽俗,所以你無法為系統(tǒng)的類比如NSString添加extension疾渣,除非創(chuàng)建子類再添加extension。而category不需要有類的源碼崖飘,我們可以給系統(tǒng)提供的類添加category榴捡。
- extension可以添加實例變量,而category不可以朱浴。
- extension和category都可以添加屬性薄疚,但是category的屬性不能生成成員變量和getter、setter方法的實現(xiàn)赊琳。
關聯(lián)對象
關聯(lián)對象是指某個OC對象通過一個唯一的key連接到一個類的實例上街夭。
舉個例子:xiaoming是Person類的一個實例,他的dog(一個OC對象)通過一根繩子(key)被他牽著散步躏筏,這可以說xiaoming和dog是關聯(lián)起來的板丽,當然xiaoming可以牽著多個dog。
設置關聯(lián)對象
void objc_setAssociatedObject(id object,const void*key, id value, objc_AssociationPolicy policy)
獲取關聯(lián)對象
void objc_getAssociatedObject(id object, const void *key)
移除關聯(lián)對象
void objc_removeAssociatedObjects(id object)
參數(shù)
id object 被關聯(lián)的對象(如xiaoming)
const void*key 關聯(lián)的key
id value 關聯(lián)對象(如dog)
objc_AssociationPolicy policy 內存管理策略
當對象被釋放時趁尼,會根據(jù)這個策略來決定是否釋放關聯(lián)的對象埃碱,當策略是RETAIN/COPY時,會釋放(release)關聯(lián)的對象酥泞,當是ASSIGN砚殿,將不會釋放。
值得注意的是芝囤,我們不需要主動調用removeAssociated來接觸關聯(lián)的對象似炎,如果需要解除指定的對象辛萍,可以使用setAssociatedObject置nil來實現(xiàn)。
應用實例(Category添加屬性并生成getter和setter方法)
如何給NSArray添加一個屬性(不能使用繼承)羡藐?
我們現(xiàn)在為NSArray增加一個blog屬性:
NSArray+MyCategory.h文件:
#import <Foundation/Foundation.h>
@interface NSArray (MyCategory)
// 不會生成添加屬性的getter和setter方法贩毕,必須手動生成
@property(nonatomic,copy) NSString *blog;
@end
NSArray+MyCategory.m
#import "NSArray+MyCategory.h"
#import <objc/runtime.h>
@implementation NSArray (MyCategory)
// 定義關聯(lián)key
static const char *key = "blog";
/**
blog的getter和setter方法
*/
- (NSString *)blog{
// 根據(jù)關聯(lián)的key,獲取關聯(lián)的值
return objc_getAssociatedObject(self, key);
}
- (void)setBlog:(NSString *)blog{
objc_setAssociatedObject(self, key, blog, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
調用
NSArray *myarray = [[NSArray alloc] init];
myarray.blog = @"123";
NSLog(@"%@",myarray.blog);