Category基本使用
分類和類一樣都是在接口內聲明,在類文件內實現。但是分類不可聲明實例變量,只可聲明屬性和方法,並且分類的實現部分不能包含@synthesize
。分類的接口中含有屬性聲明時,實現部分就要手動定義屬性的訪問方法。這樣是爲了防止隨意訪問同一個類的不同文件中定義的實例變量。
方法可以是實例方法也可爲類方法。
語法:Category聲明
注意:類名必須是已存在的類,不可定義未存在的類。
@interface
類名(分類名)
屬性的聲明
方法的聲明
@end
語法:Category實現
@implementation
類名 (分類名)
屬性的setter定義
屬性的getter定義
方法的定義
@end
分類接口部分遵循原則:
1、分類的接口部分必須引用所屬類的接口文件;
2、分類實現部分必須引用對應的接口文件;
3、使用分類中的方法時候,必須引用此方法所在的頭文件;
例:MyModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyModel : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,assign)CGFloat height;
- (void)work;
@end
NS_ASSUME_NONNULL_END
MyModel.m
#import "MyModel.h"
@implementation MyModel
- (void)work{
NSLog(@"-------MyModel work---------");
}
@end
MyModel+Test.h
#import "MyModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface MyModel (Test)
- (void)walk;
@end
NS_ASSUME_NONNULL_END
MyModel+Test.m
#import "MyModel+Test.h"
@implementation MyModel (Test)
- (void)walk{
NSLog(@"----Test walk--------");
}
@end
給已存在類追加分類
追加新方法
無論是自定義還是系統的類,皆可爲已存在類追加新方法。
雖然分類不可用追加實例變量,但是新追加的方法可以訪問類中屬性和方法。藉此,我們可以爲已有類增加新功能。
當我們在使用繼承來爲已有類增加新功能感覺麻煩時候,可以通過分類來爲正在使用的類增加新功能。
例:通過爲系統的NSDate類來增加新方法,獲取當前時間並返回一個指定格式的時間字符串。
NSDate+Format.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSDate (Format)
+(NSString *)getCurrentDayTimeDetail;
@end
NS_ASSUME_NONNULL_END
NSDate+Format.m
#import "NSDate+Format.h"
@implementation NSDate (Format)
+(NSString *)getCurrentDayTimeDetail {
NSDate *now = [self date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy年MM月dd日 HH:mm:ss"];
[formatter setLocale:[[NSLocale alloc]initWithLocaleIdentifier:@"zh_CN"]];
NSTimeZone *timeZone = [[NSTimeZone alloc]initWithName:@"Asia/Shanghai"];
[formatter setTimeZone:timeZone];
NSString *current = [formatter stringFromDate:now];
return current;
}
@end
覆蓋現有方法
新定義分類中的方法如果和原有方法重名,新定義的方法會覆蓋老的方法。通過此種方式,可以不使用繼承來實現方法覆蓋。但是如果不留意覆蓋了原有方法,也是會引起不可預測的問題,尤其是覆蓋了比較重要的方法,極有可能發生嚴重的問題。如果多個重複名稱方法,就無法知道到底執行了哪個方法。覆蓋並不提示警告,因此在使用過程中,一定要注意避免方法被覆蓋掉。
關聯引用
通過分類,我們可以爲一個類追加新的方法,但是不能追加實例變量。但是,藉助Objective-C 的運行時功能,可以爲已經存在的實例對象增加實例變量。通過這種方式和分類合起來使用,不創建子類,也可以對類進行動態的擴展。關聯引用在運行時中不僅可根據需要爲對象添加關聯,也可以對已添加關聯進行移除。
添加關聯
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy);
object:所屬類,即要增加關聯的對象;
key:關鍵字,用來區分關聯引用,必須使用確定的、不再改變的地址作爲鍵值;
value :引用對象;
policy:用來指定關聯引用的存儲策略。
此方法通過設置value 爲nil,可以刪除key 的關聯。
檢索關聯
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
通過關鍵字key來檢索關聯對象object,如果沒有關聯到任何對象,則返回值爲nil。
移除關聯
Objective-C 運行時移除關聯的方法如下,這個方法會移除object對象的所有關聯,謹慎使用。
已存在代碼可能已經使用了關聯,因此不建議使用此方法,可使用objc_setAssociatedObject
,設置參數爲nil,以此來分別移除關聯。
void objc_removeAssociatedObjects(id _Nonnull object);
關聯策略
OBJC_ASSOCIATION_ASSIGN
:
內存管理時,不給關聯對象發送retain消息,僅僅通過賦值進行關聯。弱引用。
OBJC_ASSOCIATION_RETAIN_NONATOMIC
:
內存管理時,會給關聯對象發送retain消息並持有,如果同樣的key已經關聯了其他對象,則會給其他對象發送release消息。釋放關聯對象的所有者時,會給所有的關聯對象發送release消息。強引用。
OBJC_ASSOCIATION_COPY_NONATOMIC:
在進行對象關聯引用時候會複製一份原對象,並用新複製的對象進行關聯操作。
OBJC_ASSOCIATION_RETAIN:
在對象持有方面和 OBJC_ASSOCIATION_RETAIN_NONATOMIC
一樣,唯一區別是OBJC_ASSOCIATION_RETAIN
是多線程安全的,支持排他性的關聯操作。objc_getAssociatedObject
的操作和OBJC_ASSOCIATION_RETAIN
一樣。
OBJC_ASSOCIATION_COPY:
在對象持有方面和 OBJC_ASSOCIATION_COPY_NONATOMIC
一樣,唯一區別是OBJC_ASSOCIATION_COPY
是多線程安全的,支持排他性的關聯操作。
例:MyModel+Test.h
#import "MyModel.h"
NS_ASSUME_NONNULL_BEGIN
@protocol MyModelProtocol <NSObject>
- (void)myModelProtocolAction;
@end
@interface MyModel (Test)<MyModelProtocol>
//不會自動生成實例變量,在這裏添加屬性,其實是添加的setter和getter方法。
@property (nonatomic,copy)NSString *email;
- (void)walk;
@end
NS_ASSUME_NONNULL_END
MyModel+Test.m
#import "MyModel+Test.h"
#import <objc/runtime.h>
@implementation MyModel (Test)
- (void)setEmail:(NSString *)email{
objc_setAssociatedObject(self, @"email",email, OBJC_ASSOCIATION_COPY);//添加關聯
// objc_setAssociatedObject(self, @"email",nil, OBJC_ASSOCIATION_COPY);//移除關聯
}
- (NSString *)email{
//檢索關聯
return objc_getAssociatedObject(self, @"email");
}
+(void)load{
NSLog(@"----Test load-------");
}
//+ (void)initialize{
// NSLog(@"----Test initialize-------");
//}
//
- (void)walk{
NSLog(@"----Test walk-------%@",self.email);
}
- (void)myModelProtocolAction{
NSLog(@"----myModelProtocolAction-------");
}
@end
底層結構
通過終端命令:clang -rewrite-objc MyModel+Test.m 生成.cpp格式的文件。
打開重寫後生成的.cpp 文件,查找_category_t
,在文件末尾找到如下結構體。
struct _category_t {
const char *name;//類名稱
struct _class_t *cls;//類
const struct _method_list_t *instance_methods;//對象方法列表
const struct _method_list_t *class_methods;//類方法列表
const struct _protocol_list_t *protocols;//協議列表
const struct _prop_list_t *properties;//屬性列表
};
對象方法列表結構體,存儲分類中的所有對象方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"setEmail:", "v24@0:8@16", (void *)_I_MyModel_Test_setEmail_},
{(struct objc_selector *)"email", "@16@0:8", (void *)_I_MyModel_Test_email},
{(struct objc_selector *)"walk", "v16@0:8", (void *)_I_MyModel_Test_walk},
{(struct objc_selector *)"myModelProtocolAction", "v16@0:8", (void *)_I_MyModel_Test_myModelProtocolAction}}
};
類方法結構體,存儲着類的方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_MyModel_Test_load}}
};
協議列表結構體和屬性列表結構體,存儲着協議和屬性,值得注意的是,在編譯後的文件中,並沒有找到像類文件編譯後那樣的_ivar_list_t成員變量結構體和setter和getter方法,這也證明了分類不可以添加成員變量。而屬性的setter和getter 方法,編譯後的是走的我們手動添加的相關的關聯引用的方法。
//協議
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_MyModelProtocol
};
//屬性
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"email","T@\"NSString\",C,N"}}
};
//添加關聯引用
static void _I_MyModel_Test_setEmail_(MyModel * self, SEL _cmd, NSString * _Nonnull email) {
objc_setAssociatedObject(self, (NSString *)&__NSConstantStringImpl__var_folders_lc_jzfd703x2r1czsk56zjspmw40000gn_T_MyModel_Test_00509d_mi_0,email, OBJC_ASSOCIATION_COPY);
}
//檢索關聯引用
static NSString * _Nonnull _I_MyModel_Test_email(MyModel * self, SEL _cmd) {
return objc_getAssociatedObject(self, (NSString *)&__NSConstantStringImpl__var_folders_lc_jzfd703x2r1czsk56zjspmw40000gn_T_MyModel_Test_00509d_mi_1);
}
下面是MyModel的分類的結構體賦值,依次分析各個變量對應的賦值。
第1個是類名,將MyModel 賦值給了name;
第2個cls,將0賦值給cls;
第3個是對象列表,將_OBJC$CATEGORY_INSTANCE_METHODS_MyModel$_Test
變量賦值給對象方法列表_instance_methods
;
第4個是類方法列表,將*_OBJC$CATEGORY_CLASS_METHODS_MyModel$_Test
變量賦值給類方法列表class_methods
;
第5個協議列表,_OBJC_CATEGORY_PROTOCOLS$MyModel$_Test
變量賦值給了protocols
;
最後是屬性列表賦值,將_OBJC_$_PROP_LIST_MyModel_$_Test
變量賦值給了properties
;
static struct _category_t _OBJC_$_CATEGORY_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyModel",
0, // &OBJC_CLASS_$_MyModel,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyModel_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyModel_$_Test,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MyModel_$_Test,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyModel_$_Test,
};
從上面編譯後的代碼分析,我們可以看出,編譯器生成了一個_category_t
結構體,並給結構內定義的變量進行了賦值。_category_t
結構體裏面存儲着分類的對象方法、類方法、協議、和屬性,但是沒有成員變量。這就是_category_t
重寫後的底層結構。
源碼分析
Category運行時加載過程
_objc_init
->map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
在上面的執行過程中,map_images
方法只執行一次,這個過程加載並緩存所有Mach-O鏡像文件,並沒有進行合併分類的相關操作。
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
->attachLists
在這個過程中,才正式合併分類。需要注意的是,load_images
只有在map_images
執行結束後,纔會執行,會執行多次。但是load_images
內的loadAllCategories
方法,只在分類還沒初始化並且_dyld_objc_notify_register
的調用已經完成時,即didInitialAttachCategories
爲false, didCallDyldNotifyRegister
爲true,此時才啓用,此時就不再走realizeClassWithoutSwift
->methodizeClass
->attachToClass
這一部分了,因爲類對象已經存在了,因此改換成loadAllCategories
後面的過程了。
_objc_init
首先在objc(objc4-818.2)開源庫中, objc-os.mm類文件內,查找_objc_init
方法,在此方法內的_dyld_objc_notify_register
函數註冊了3個方法;我們只關注前面兩個重要的函數參數;
map_images
:加載並緩存所有Mach-O鏡像文件;
load_images
:初始化類和分類時需要;
unmap_image
:取消內存映射。
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
//
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
map_images
作用:加載並緩存所有Mach-O鏡像文件,主要操作在map_images_nolock
方法中。程序啓動後只執行一次。
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
map_images_nolock
作用:對所有的鏡像列表執行addHeader
,主要過濾重複的鏡像。 該方法內調用了加載所有Mach-O鏡像文件的_read_images
方法。
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
/**
*代碼過長,省略部分代碼
*/
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
_read_images
在map_images
方法調用過程中,最終會引用到 objc-runtime-new.mm 文件內的_read_images
方法,_read_images
方法的作用是從Mach-O鏡像文件中讀取所有類信息、方法信息、分類信息。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
/**
*省略了部分代碼,只摘取重要代碼
*/
//搜索分類
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
/**
*當其他線程調用新分類代碼之前,此線程完成其修復。分類搜索必須延遲以避免潛在的競爭。
*/
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
}
////對類 cls 執行第一次初始化,不含swift端初始化
realizeClassWithoutSwift(cls, nil);
}
}
}
realizeClassWithoutSwift
作用:實現ClassWithoutSwift,對類 cls 執行第一次初始化,包括分配其讀寫數據。不執行任何 Swift 端初始化。
返回類的真實類結構。鎖定:runtimeLock
讀寫鎖必須被調用者上寫鎖,保證線程安全。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
/**
*省略了部分代碼,只摘取重要代碼
*/
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
//附加分類
// Attach categories
methodizeClass(cls, previously);
return cls;
}
methodizeClass
作用: 修正 cls 的方法列表、協議列表和屬性列表。將 cls 類的所有沒有被 attach 的分類 attach 到 cls 上。即將分類中的方法、屬性、協議添加到 methods、 properties 和 protocols 中。鎖定:runtimeLock
讀寫鎖必須被調用者上寫鎖,保證線程安全。
static void methodizeClass(Class cls, Class previously)
{
/**
*省略了部分代碼,只摘取重要代碼
*/
runtimeLock.assertLocked();
// Install methods and properties that the class implements itself.
//添加類自身實現的方法和屬性
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
//// 如果是根元類
if (cls->isRootMetaclass()) {
// root metaclass
// 對根元類的initialize方法進行交換,即給根元類發送 SEL_initialize 消息,但是走的是 objc_noop_imp方法,裏面不做任何操作
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
//給 cls 類附加分類
if (previously) {//如果已存在類
if (isMeta) {//如果是元類,給cls附加分類,並返回cls 類的沒有被附加的類
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
//當一個類重新定位時,帶有類方法的分類,可以在類自身而不是類之上的元類註冊。通過attachToClass來添加這些。
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
load_images
作用: 當鏡像的狀態變化時,會回調load_images
方法。只在map_images 結束後,分類沒初始化時候啓用。會多次調用。
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
loadAllCategories
作用:加載所有分類的入口
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
load_categories_nolock
作用:加載分類的之前的區別判斷。
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//cls類已實現
if (cls->isRealized()) {
//合併分類的相關信息到類中
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
attachCategories
作用:從分類列表中添加方法列表、屬性和協議到 cls 類中, attachCategories
要求分類列表中是排好序的,新的在前面。
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{
/**
*省略了部分代碼,只摘取重要代碼
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
//判斷是否爲元類
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
//取出某個分類
auto& entry = cats_list[i];
//方法數組 ,如果是元類,返回的是類方法,如果是類,則返回是實例對象方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
//準備 mlists 中的方法列表集合,mcount:列表個數,NO:排除基本方法,fromBundle:是否來自bundle
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
// 將新mlist列表添加到 rwe 中的方法列表數組中
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
//屬性數組
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
// 將新proplist列表添加到 rwe 中的屬性列表數組中
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
//協議數組
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
// 將新protolist列表添加到 rwe 中的協議列表數組中
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
attachLists
作用:將新的list數據與原有的list 數據進行合併。
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;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
void tryFree() {
if (hasArray()) {
for (uint32_t i = 0; i < array()->count; i++) {
try_free(array()->lists[i]);
}
try_free(array());
}
else if (list) {
try_free(list);
}
}
template<typename Other>
void duplicateInto(Other &other) {
if (hasArray()) {
array_t *a = array();
other.setArray((array_t *)memdup(a, a->byteSize()));
for (uint32_t i = 0; i < a->count; i++) {
other.array()->lists[i] = a->lists[i]->duplicate();
}
} else if (list) {
other.list = list->duplicate();
} else {
other.list = nil;
}
}
};
Category之 load
1、先編譯的類,優先調用load
;
2、先調用父類的load
,再調用子類的load
;
3、先調用類load
,再調用分類load
,然後先編譯的分類先調用load
;
4、調用時機最靠前,在main函數運行之前,load
方法就會調用,適合方法交換。
5、不是懶加載,只會在程序調用期間調用一次,謹記,若在類與分類中都實現了 load
方法,兩者都會被調用,方法不會被覆蓋,但是順序不確定。
Category之 initialize
1、若是子類,會先走父類initialize
,再走子類initialize
,如果子類沒有重寫這個方法,父類裏這個方法也會被調用;
2、initialize
是類第一次接收到消息的時候調用,每一個類只會initialize
一次,父類可能不止調用一次;
3、和 load
不同,initialize
方法調用時,所有的類都load
到了內存中;
4、initialize
的運行線程安全,一般只在 initialize
中進行常量的初始化 。
總結:
Category運行時的加載過程,是通過動態的先初始化類和元類的相關信息,然後再將分類的實例方法、類方法、屬性以及協議,合併到類對象和元類對象中。