一匠璧、Category(類別揪阶,分類)的使用場景
- 為已經(jīng)存在的類添加方法和屬性
- 把不同的功能組織到不同的
category
- 模擬多繼承(另外模擬多繼承的還有
protocol
)
1)給類擴展:類別或者繼承
category
相比繼承的缺點
1.新的擴展方法還需要父類的實現(xiàn),與原方法重名患朱。若使用類別則會
覆蓋
鲁僚。所謂覆蓋
其實也不是很準確,具體解釋見原理裁厅。
2.category
不能添加實例變量冰沙,具體解釋見原理。
3.如果一個類有多個category
执虹,不同的類別同時定義同一個方法拓挥,則調用哪個不一定,具體解釋見原理袋励。
category
相比inherit
的優(yōu)點
1.
category
可以修改原類基礎上擴展類的方法侥啤,實現(xiàn)私有類 方法擴展以及一些不能修改源碼類的擴展当叭,也就是說不會入侵原類的代碼結構,比繼承更為輕量級盖灸。
2.category
可以將類的實現(xiàn)分散到多個文件蚁鳖。
3.category
可以創(chuàng)建私有方法的前向引用:在類別里聲明一個類的私有方法,不需要實現(xiàn)赁炎,編譯不會報錯醉箕,運行時會調用私有方法。繼承不能繼承私有方法徙垫。
4.category
可以向類添加非正式協(xié)議:因為Objective-C
里面的類大都是NSObject
的子類讥裤,所以在NSObject
的類別里定義函數(shù),所有對象都能使用
2)添加屬性:runtime
直接在category
的.h
中添加這時候會有warning
?? Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category
類別不會自動幫屬性生成getter/setter
方法姻报,也不能生成帶下劃線的實例變量己英。我們可以用runtime
【關聯(lián)對象】去實現(xiàn)category
唯一有的類添加新的屬性并生成getter/settet
方法。
runtime我們提供了以下方法
參數(shù): id object:被關聯(lián)的對象
const void *key:關聯(lián)的key吴旋,要求唯一
id value:關聯(lián)的對象(如dog)
objc_AssociationPolicy policy:內(nèi)存管理的策略
//關聯(lián)對象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
//移除關聯(lián)的對象
void objc_removeAssociatedObjects(id object)
當對象被釋放時损肛,會根據(jù)這個策略來決定是否釋放關聯(lián)的對象,當策略是RETAIN/COPY
時邮府,會釋放(release
)關聯(lián)的對象,當是ASSIGN
溉奕,將不會釋放褂傀。
值得注意的是,我們不需要主動調用removeAssociated
來接觸關聯(lián)的對象加勤,如果需要解除指定的對象仙辟,可以使用setAssociatedObject
置nil
來實現(xiàn)。
舉個??
我為Student+teach.h
類添加了個name
屬性鳄梅,.h
文件如下
#import "Student.h"
@interface Student (teach)
//不會生成添加屬性的getter和setter方法叠国,必須我們手動生成
@property (nonatomic, copy) NSString * name;
@end
.m
文件如下
#import "Student+teach.h"
//導入頭文件
#import <objc/runtime.h>
@implementation Student (teach)
//定義關聯(lián)的key
static const char *key = "name";
//getter方法
- (NSString *)name {
//根據(jù)key獲取關聯(lián)的值
return objc_getAssociatedObject(self, key);
}
//setter方法
- (void)setName:(NSString *)name {
//參數(shù)設置
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
運行測試代碼
Student *xiaoming = [[Student alloc] init];
xiaoming.name = @"xiaoming";
NSLog(@"%@",xiaoming.name);
打印結果
2017-05-23 13:49:52.166 RuntimeTest[3009:178563] xiaoming
注:這里區(qū)分一下屬性和成員變量的區(qū)別
1.我聲明了一個屬性name
。
@property (nonatomic, copy) NSString * name;
會為我生成一個以下劃線為開頭的實例變量_name
戴尸,也不需要寫@synthesize name粟焊;
,會自動還是你歌城getter/setter
方法孙蒙。那么在.m
文件中可以直接使用_name
實例變量项棠,也可以self.name
通過屬性的getter/setter
設置。
2.大括號
@interface MyViewController:UIViewController
{
NSString *name;
}
@end
此時用點語法會提示,編輯器會提示讓你把.改為->挎峦。因為點語法是調用方法香追,為代碼里沒有getter/setter方法。
3.點語法的說明
如果點表達式出現(xiàn)在=
左邊坦胶,則該屬性的setter
的方法將會被調用透典。右邊則為getter
方法晴楔。
4.synthesize
的用法
@synthesize name = myname;
會自動生成實例變量myname
而不是默認的_name
,自動生成getter/setter
方法,所以它的作用是指定實例變量名稱峭咒。
3)模擬多繼承:消息轉發(fā)税弃、delegate和protocol、類別
oc
不支持多線程讹语,由于消息機制消息名字查找發(fā)生在運行時而非編譯時钙皮,多個基類可能導致二義性問題。模擬多線程有以下幾個方法: 消息轉發(fā)顽决,delegate
和protocol
短条,類別。
類別:
如上??才菠。
delegate和protocol:
委托協(xié)助主體完成操作任務茸时,將需要定制化的操作預留給委托對象來自定義實現(xiàn),類似子類化主體赋访。
除此之外可都,可以用作事件監(jiān)聽。
消息轉發(fā):
當向某對象發(fā)送消息但是runtime system
在當前類和父類中都找不到該方法的實現(xiàn)時蚓耽,runtime
不會立即報錯渠牲,而是有以下步驟,
簡述流程:
1.動態(tài)方法解析:向當前類發(fā)送resolveInstanceMethod:的信號步悠,檢查是否像這個類動態(tài)添加了方法签杈。
相關鏈接:http://blog.csdn.net/haishu_zheng/article/details/12873151
2.快速消息發(fā)送:檢查這個類是否實現(xiàn)了forwardingTargetForSelector:方法,是吸納了就調用鼎兽。若返回值對象非空nil或者非self答姥,則向該返回對象重新發(fā)送信息。
利用快速消息轉發(fā)forwardingTargetForSelector:方法重寫谚咬,結合【類別】或者【類型強轉】可以實現(xiàn)多繼承方法
思考問題:如果將其類型轉成`id 鹦付,也可以編譯通過,并實現(xiàn)轉發(fā)择卦∏贸ぃ可是會帶來什么隱患呢?
3.標準信息發(fā)送:runtime發(fā)送methodSignatureForSelector:消息獲取Selector對應的方法簽名秉继。返回值非空則通過forwardInvocation:轉發(fā)消息潘明,返回值為空則向當前對象發(fā)送doesNotRecognizeSelector:消息,程序崩潰退出秕噪。
標準消息轉發(fā)需要重寫 methodSignatureForSelector: 和 forwardInvocation: 兩個方法即可钳降。
二、category的原理和特性
1)原理
runtime
的源碼里可以找到_read_images
函數(shù):
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
...
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
...
}
里面提到了兩個比較重要的函數(shù):
addUnattachedCategoryForClass()
remethodizeClass()
-
addUnattachedCategoryForClass()
主要是構建class和category
的哈希表腌巾,如果不存在則生成遂填,如果存在則重新構建铲觉,主要是class到category
的一個映射關系。 -
remethodizeClass
調用了attachCategories
方法吓坚,這個方法里面把category
里的方法撵幽,屬性和協(xié)議列表都添加到相應的類里面,從本質來說礁击,調用類的方法和分類的方法都是一樣的盐杂。
2)category重名函數(shù)覆蓋
在看源碼的attachLists
方法
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
第一個判斷語句中C函數(shù) memmove(dest, src, len)
和memcpy(des, src, size)
完成了將舊函數(shù)數(shù)組后移空出位置,把新的函數(shù)數(shù)組添加到原數(shù)組空位的過程哆窿。
下一個判斷語句意思為如果list
沒有值链烈,那么直接賦值給它。
最后一個判斷語句是找到多個函數(shù)名一樣的挚躯,進行插入强衡。
綜上所述,
category
里新增的和原來類重名的函數(shù)將會被添加在原函數(shù)列表的前面码荔,并不是覆蓋漩勤。如果想要調用原函數(shù)的方法,只需要便利函數(shù)列表缩搅,調用最后一個來執(zhí)行越败。這就是category
重名函數(shù)覆蓋,并且同個類多個category
重名調用不確定的原因硼瓣。(有些文章指出執(zhí)行順序跟編譯順序有關)
3)Category和Extension的區(qū)別
1究飞、Extension
在編譯期決議,它就是類的一部分巨双,在編譯期和頭文件里的@interface
以及實現(xiàn)文件里的@implement
一起形成一個完整的類噪猾,它伴隨類的產(chǎn)生而產(chǎn)生霉祸,亦隨之一起消亡筑累。Extension
一般用來隱藏類的私有信息,你必須有一個類才能為這個類添加Extension
丝蹭,所以你無法為系統(tǒng)的類比如NSString
添加Extension
慢宗。
2、Category
則完全不一樣奔穿,它是在運行期決議的镜沽。
3、Extension
可以添加屬性贱田、成員變量缅茉,而Category
一般不可以。
總之男摧,就
Category
和Extension
的區(qū)別來看蔬墩,Extension
可以添加實例變量译打,而Category
是無法添加實例變量的。因為Category
在運行期拇颅,對象的內(nèi)存布局已經(jīng)確定奏司,如果添加實例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災難性的樟插。
4)Category不能添加成員變量
因為方法和屬性并不“屬于”類實例韵洋,而成員變量“屬于”類實例。我們所說的“類實例”概念黄锤,指的是一塊內(nèi)存區(qū)域搪缨,包含了isa指針和所有的成員變量。所以假如允許動態(tài)修改類成員變量布局猜扮,已經(jīng)創(chuàng)建出的類實例就不符合類定義了勉吻,變成了無效對象。但方法定義是在objc_class
中管理的旅赢,不管如何增刪類方法齿桃,都不影響類實例的內(nèi)存布局,已經(jīng)創(chuàng)建出的類實例仍然可正常使用煮盼。
第一次發(fā)簡書文章短纵,如果所述有錯誤或者不明確的地方,望大家不吝賜教?? ~
參考:
美團技術文檔: http://tech.meituan.com/DiveIntoCategory.html