互联网科技

iOS RunTime之五:Category

作者:金沙国际官网    发布时间:2020-05-04 23:27     浏览次数 :127

[返回]

CategoryObjective-C 2.0 之后加上的特色,平时大家利用 Category 的风貌重要能够动态地为曾经存在的类扩充新的属性和章程。那样做的补益正是:

如何在 Category 怎样调用本类方法

骨子里,如若叁个类的分类重写了这么些类的法子后,那么这一个类的这一个方法将失效,起效果的将会是分类的丰硕重写方法,在分拣重写的时候 Xcode 也会付出相应警报。

Category is implementing a method which will also be implemented by its primary class

ViewController

@interface ViewController : UIViewController- test;@end@implementation ViewController- test { NSLog(@"ViewController");}

ViewController + GetSelf

@interface ViewController - test;@end@implementation ViewController - test { NSLog(@"ViewController category ");}@end

输出结果:

2019-02-17 22:37:12.972196+0800 CategoryDemo[10142:78261] ViewController category 

实际上,Category 并不曾隐瞒主类的同名方法,只是 Category 的诀窍排在方法列表前面,而主类的章程被移到了办法列表的末尾。于是,大家得以在 Category 方法里,利用 Runtime 提供的 API,从事艺术工作术列表里拿回原方法,进而调用。示例:

@interface ViewController - test;@end@implementation ViewController - test { NSLog(@"ViewController category "); [[CategoryManager shared] invokeOriginalMethod:self selector:_cmd];}@end@interface CategoryManager : NSObject+ (instancetype)shared;- invokeOriginalMethod:target selector:selector;@end@implementation CategoryManager+ (instancetype)shared { static CategoryManager *shareManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareManager = [[CategoryManager alloc] init]; }); return shareManager;}- invokeOriginalMethod:target selector:selector { // Get the class method list uint count; Method *methodList = class_copyMethodList([target class], &count); // Print to console for (int i = 0; i < count; i++) { Method method = methodList[i]; NSLog(@"Category catch selector : %d %@", i, NSStringFromSelector(method_getName; } // Call original method . Note here take the last same name method as the original method for (int i = count - 1 ; i >= 0; i--) { Method method = methodList[i]; SEL name = method_getName; IMP implementation = method_getImplementation; if (name == selector) { // id (id, SEL, ...) implementation)(target, name); break; } } free(methodList);}@end

遍历 ViewController 类的主意列表,列表里最终二个同名的诀窍,正是原方法。原理【load 和 Initialize 加载进度】

一、category简介

category是OC2.0从今以后加上的言语特色,category的功力至关心重视即便为早就存在的类增添方法。初次除外,apple还推荐了category的其它四个应用处境https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html

  • 能够把类的落实分开在多少个例外的公文之中。那样做有多少个平价,
    aState of Qatar可以减去单个文件的体量
    b卡塔尔(قطر‎能够把分化的功能团体到不一致的category里
    c卡塔尔(قطر‎可以由五个开垦者协同实现四个类
    d卡塔尔国能够按需加载想要的category 等等。
  • 扬言私有方法
  • 能够减去肥壮的代码。
  • 能够把差别的功用拆开,方便未来的护卫。

Category 中不可能动态增加成员变量?

解答:

众多个人在面试的时候都会被问到 Category,既然允许用 Category 给类扩大方法和属性,那为啥不相同意扩展成员变量?张开 objcjs3311com金沙网站, 源代码,在objc-runtime-new.h中大家可以开采:

struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; method_list_t *methodsForMeta(bool isMeta) { if  return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta) { if  return nil; // classProperties; else return instanceProperties; }};

注意:

  • name:是指 class_name 而不是 category_name
  • cls:要扩张的类对象,编写翻译期间是不会定义的,而是在运维时经过 * name 对应到对应的类对象。
  • instanceMethods:category 中颇负给类加多的实例方法的列表。
  • classMethods:category 中具有增多的类措施的列表。
  • protocols:category 达成的兼具合同的列表。
  • instanceProperties:category 中加上的全部属性。

从 category 的定义也能够看来 category 能够加上实例方法,类形式,以至能够达成合同,增添属性,无法增多实例变量。

另外在 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.

大概的意趣说,这么些函数只可以在“构建多个类的经过中”调用。当编写翻译类的时候,编写翻译器生成了八个实例变量内部存款和储蓄器布局 ivar layout,来报告运营时去那边访问类的实例变量们,一旦完成类定义,就无法再加多成员变量了。经过编写翻译的类在程序运转后就被 runtime 加载,没有机缘调用 addIvar。程序在运作时动态营造的类需求在调用 objc_registerClassPair 之后才足以被使用,雷同没有机遇再加多成员变量。

js3311com金沙网站 1Paste_Image.png

从运转结果中看出,你不可能为三个类动态的增进成员变量,能够给类动态增添方法和性质。

因为方法和总体性并不“归于”类实例,而成员变量“归属”类实例。咱俩所说的“类实例”概念,指的是一块内部存款和储蓄器区域,富含了 isa 指针和颇负的积极分子变量。所以若是允许动态改良类成员变量结构,已经创设出的类实例就不相符类定义了,产生了没用对象。但方法定义是在 objc_class 中处理的,不管怎样增加和删除类方法,都不影响类实例的内部存款和储蓄器布局,已经创设出的类实例依然可平常使用。

同理:

js3311com金沙网站 2Paste_Image.png

某多个类的归类是在 runTime 时,被动态的增加到类的构造中。想了然分类是怎么着加载的请看 iOS Run提姆e之六:Category

二、category和extension的比较

extension看起来疑似叁个无名的category,可是extension和知名字的category大致统统是五个东西。
extension在编写翻译期决议,它是类的一部分,在编写翻译期和头文件的@interface以至贯彻公文里的@implement一齐产生多少个完好的类,它伴随着类的发生而发生,也随着一齐消失。extension日常用来隐讳类的私家音讯,你不得不有三个类的源码才干为四个类加多extension,所以你不可能为系统的类譬喻NSString增添extension。https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

不过category则完全分歧等,它是在运维期决议的。

就category和extension的分别来看,大家得以推导出叁个白日衣绣的真实情状,extension能够增多实例变量,而category是力所不及增多实例变量的(因为在运维期,对象的内部存款和储蓄器布局已经规定,如若增添实例变量就能破坏类的个中布局,那对编写翻译型语言来讲是凄惨的)。

以下源码来自于opensource.apple.com的objc4-750.tar.gz

Category 中动态增进的本性

笔者们精通在 Category 里面是爱莫能助为 Category 加多实例变量的。不过我们多数时候需求在 Category 中增多和指标关系的值,此时能够求助关联对象来完成。

@interface ViewController (Attribute)@property (nonatomic, copy) NSString *name;@endstatic char *attributeNameKey;@implementation ViewController (Attribute)- setName:(NSString *)name { objc_setAssociatedObject(self, &attributeNameKey, name, OBJC_ASSOCIATION_COPY);}- (NSString *)name { return objc_getAssociatedObject(self, &attributeNameKey);}@end

能够信赖关联对象来为叁个类动态增多属性,不过涉及对象又是存在什么样地方啊? 如何存款和储蓄? 对象销毁时候什么管理涉及对象啊?

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value  outside the lock. ObjcAssociation old_association; id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations; disguised_ptr_t disguised_object = DISGUISE; if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find; if (j != refs->end { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { [key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; [key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find; if (j != refs->end { old_association = j->second; refs->erase; } } } } // release the old value (outside of the lock). if (old_association.hasValue ReleaseValue()(old_association);}

能够看出全部的涉及对象都由 AssociationsManager 管理,而 AssociationsManager 定义如下:

class AssociationsManager { // associative references: object pointer -> PtrPtrHashMap. static AssociationsHashMap *_map;public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &associations() { if (_map == NULL) _map = new AssociationsHashMap(); return *_map; }};

AssociationsManager 里面是由一个静态 AssociationsHashMap 来存款和储蓄全部的涉及对象的。这一定于把具备指标的关联对象都存在一个大局 map 里面。而 mapkey 是这些指标的指针地址(大肆多少个不等指标的指针地址一定是例外的),而那个 mapvalue 又是此外一个 AssociationsHashMap,里面保存了涉及对象的 kv 对。

void *objc_destructInstance { if  { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if  object_cxxDestruct; if  _object_remove_assocations; obj->clearDeallocating(); } return obj;}

runtime 的销毁对象函数 objc_destructInstance 里面会咬定这一个目的有未有提到对象,若是有,会调用 _object_remove_assocations 做涉嫌对象的清理专门的学业。

三、category真面目

咱俩理解,全体的OC类和对象,在runtime层都以用struct表示的,category也不例外,在runtime层,category用布局体objc_category(在runtime.h中得以找到此概念),它满含了
1State of Qatar、分类的名字(category_name)
2)、类(class_name)
3)、category中持有给类增加的实例方法的列表(instance_methods)
4卡塔尔、category中颇负增添的类措施的列表(class_methods)
5State of Qatar、category达成的具备公约的列表(protocols)

struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
} 

从category的概念也得以看看category能够增多实例方法,类措施,甚至足以兑现合同,不过不得以加上实例变量。

现行,去写三个category看一下category。
MyClass.h

#import <Foundation/Foundation.h>

@interface MyClass : NSObject

- (void)printName;

@end

@interface MyClass(MyAddition)

@property (nonatomic, copy) NSString *name;
- (void)printName;

@end

MyClass.m

#import "MyClass.h"

@interface MyClass ()

@end

@implementation MyClass

- (void)printName {
    NSLog(@"MyClascc");
}

@end

@implementation MyClass(MyAddition)

- (void)printName {
    NSLog(@"MyAdditon");
}

@end

我们运用clang -rewrite-objc MyClass.m
咱俩会拿走三个10万行的.cpp文件。大家能够在文书的末梢,找到一些代码片段:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_INSTANCE_METHODS_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_printName}}
};

static struct _class_ro_t _OBJC_METACLASS_RO_$_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1, sizeof(struct _class_t), sizeof(struct _class_t), 
    (unsigned int)0, 
    0, 
    "MyClass",
    0, 
    0, 
    0, 
    0, 
    0, 
};

static struct _class_ro_t _OBJC_CLASS_RO_$_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, sizeof(struct MyClass_IMPL), sizeof(struct MyClass_IMPL), 
    (unsigned int)0, 
    0, 
    "MyClass",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_MyClass,
    0, 
    0, 
    0, 
    0, 
};

extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_MyClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_MyClass,
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_MyClass,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_MyClass,
};
static void OBJC_CLASS_SETUP_$_MyClass(void ) {
    OBJC_METACLASS_$_MyClass.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_MyClass.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_MyClass.cache = &_objc_empty_cache;
    OBJC_CLASS_$_MyClass.isa = &OBJC_METACLASS_$_MyClass;
    OBJC_CLASS_$_MyClass.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_MyClass.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
    (void *)&OBJC_CLASS_SETUP_$_MyClass,
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}}
};

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_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"name","T@"NSString",C,N"}}
};

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;

static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "MyClass",
    0, // &OBJC_CLASS_$_MyClass,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
    _OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
    (void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
    &OBJC_CLASS_$_MyClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

大家能够看出,
1卡塔尔(قطر‎、首先编写翻译器生成了实例方法列表OBJC$_CATEGORY_INSTANCE_METHODSMyClass$_MyAddition(96806行)和属性列表OBJC$_PROP_LISTMyClass$MyAddition(96816行),两个的命名都依照了公私前缀+类名+category名字的命有名的模特式,何况实例方法列表里面填充的难为大家在MyAddition那一个category里面写的办法printName,而属性列表里面填充的也多亏大家在MyAddition里丰裕的name属性。还或者有三个索要专心到的真情正是category的名字用来给各类列表以至背后的category布局体自个儿命名,而且有static来修饰,所以在同二个编写翻译单元里大家的category名无法重复,不然会产出编写翻译错误。
2卡塔尔(قطر‎、最终,编写翻译器在DATA段下的objc_catlist section里保存了多少个大大小小为1的category_t的数组L_OBJC_LABELCATEGORY
$(96843行卡塔尔(قطر‎(当然,如若有多少个category,会变卦对应长度的数组),用于周转期category的加载。
到此地,编译器的专门的学业就接达成了。

runtime.h中查看定义中:

CategoryExtension 的区别

  • Extension 在编写翻译期决议,它就是类的一有个别,在编写翻译期和头文件里的 @interface 以致贯彻文件里的 @implement 一齐产生多少个完完全全的类,它伴随类的发生而产生,亦随之一齐死灭。Extension 日常用来隐讳类的私家音讯,你必须有三个类本事为那么些类增多 Extension,所以你不可能为系统的类比如 NSString 添加 Extension
  • Category 则完全不相似,它是在运维期决议的。
  • Extension 能够增进成员变量,而 Category 平日不得以。

总之,就 CategoryExtension 的不相同来看,Extension 能够增多成员变量,而 Category 是无法加多成员变量的。因为 Category 在运行期,对象的内部存款和储蓄器布局已经规定,假使增多实例变量就能破坏类的中间布局。

比如有认为上述小编讲的歇斯底里的地点接待建议,大家多多交换联络。

四、category和章程覆盖

1卡塔尔(قطر‎、category的方法没有“完全替换掉”原本类已经部分艺术,也正是说借使category和原先类都有methodA,那么category附加达成未来,类的点子列表里会有八个methodA
2卡塔尔(قطر‎、category的方法被放到了新议程列表的前头,而本来类的办法被内置了新格局列表的末尾,那也正是大家平时所说的category的方法会“覆盖”掉原本类的同名方法,那是因为运维时在探寻方法的时候是本着方法列表的一一查找的,它假若一找到呼应名字的不二诀要,就能够告一段落,殊不知前边恐怕还只怕有同样名字的情势。
据此,category其实并不是一丝一毫替换掉原本类的同名方法,只是category在格局列表的前头而已,所以我们只要本着方法列表找到最后三个对应名字的方式,就足以调用原本类的方法

Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];

if (currentClass) {
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    IMP lastImp = NULL;
    SEL lastSel = NULL;
    for (NSInteger i = 0; i < methodCount; i++) {
        Method method = methodList[i];
        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
                                        encoding:NSUTF8StringEncoding];
        if ([@"printName" isEqualToString:methodName]) {
            lastImp = method_getImplementation(method);
            lastSel = method_getName(method);
        }
    }
    typedef void (*fn)(id,SEL);

    if (lastImp != NULL) {
        fn f = (fn)lastImp;
        f(my,lastSel);
    }
    free(methodList);
}
#if __OBJC2__typedef struct method_t *Method;typedef struct ivar_t *Ivar;typedef struct category_t *Category;typedef struct property_t *objc_property_t;#elsetypedef struct old_method *Method;typedef struct old_ivar *Ivar;typedef struct old_category *Category;typedef struct old_property *objc_property_t;#endif

五、category和+load方法

在类和category中都能够有+load方法,那么有八个难点:
1卡塔尔(قطر‎、在类的+load方法调用的时候,大家得以调用category中扬言的方式么?
2State of Qatar、这几个个+load方法,调用顺序是怎么着的吧?

js3311com金沙网站 3

图片.png

小编们的代码里有MyClass和MyClass的五个category (Category1和Category2),MyClass和多个category都增添了+load方法,并且Category1和Category2都写了MyClass的printName方法。
在Xcode中式茶食击Edit Scheme,增添如下七个情形变量(能够在进行load方法以至加载category的时候打印log音信):

js3311com金沙网站 4

图片.png

运营项目,大家会看见调整台打字与印刷超多东西出来,我们只找到我们想要的新闻

objc[72331]: LOAD: class 'MyClass' scheduled for +load
objc[72331]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[72331]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[72331]: LOAD: +[MyClass load]

2017-11-17 13:07:27.811599+0800 category原理[72331:46049847] Myclass--load
objc[72331]: LOAD: +[MyClass(Category2) load]

2017-11-17 13:07:27.812288+0800 category原理[72331:46049847] category2--load
objc[72331]: LOAD: +[MyClass(Category1) load]

2017-11-17 13:07:27.812414+0800 category原理[72331:46049847] category1--load

因而,对于地方七个难点,答案是很肯定的:
1State of Qatar、能够调用,因为附加category到类的职业会先于+load方法的实践
2卡塔尔(قطر‎、+load的举办各类是先类,后category,而category的+load施行顺序是基于编写翻译顺序决定的。
脚下的编译顺序是如此的:

js3311com金沙网站 5

图片.png

大家调解三个Category1和Category2的编写翻译顺序,运行,我们能够看出调节台的出口顺序变了:

js3311com金沙网站 6

图片.png

objc[72379]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[72379]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[72379]: LOAD: +[MyClass load]

2017-11-17 13:12:55.590671+0800 category原理[72379:46066562] Myclass--load
objc[72379]: LOAD: +[MyClass(Category1) load]

2017-11-17 13:12:55.591265+0800 category原理[72379:46066562] category1--load
objc[72379]: LOAD: +[MyClass(Category2) load]

2017-11-17 13:12:55.591402+0800 category原理[72379:46066562] category2--load

即使如此对于+load的举行顺序是那样,不过对于“覆盖”掉的章程,则会先找到最终一个编译的category里的呼应措施。

打开 objc 源代码,在objc-runtime-new.h中大家得以开采:

六、category和关联对象

大家通晓在category里面是无法为category增加实例变量的。不过我们有的是时候需求在category中增多和对象关系的值,当时能够求助关联对象来促成。
MyClass+Category.h:

@interface MyClass(MyAddition)

@property (nonatomic, copy) NSString *name;
- (void)printName;

@end

MyClass+Category.m:

#import "MyClass+Category.h"
#import <objc/runtime.h>

@implementation MyClass (Category)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

@end
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; method_list_t *methodsForMeta(bool isMeta) { if  return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta) { if  return nil; // classProperties; else return instanceProperties; }};

注意:

  • name:是指 class_name 而不是 category_name
  • cls:要增添的类对象,编写翻译时期是不会定义的,而是在运营时通过 * name 对应到对应的类对象。
  • instanceMethods:category 中有所给类增添的实例方法的列表。
  • classMethods:category 中颇有增加的类格局的列表。
  • protocols:category 完毕的具有合同的列表。
  • instanceProperties:category 中丰富的具有属性。

从 category 的概念也能够看到 category 可以加上实例方法,类方式,以致足以兑现契约,增加属性,无法增加实例变量。

js3311com金沙网站 7Paste_Image.png

使用 clang 的一声令下去探访 category 到底会成为何:

clang -rewrite-objc Person+Student.m

注意: