NSObject中Category的运行流程解析

原创文章转载请注明出处,谢谢

之前都没有很细致的去了解过Category的运行原理,今天正好是周末所以就仔细了解了一下,网上其实有不少好文都详细介绍了Category的原理,不过我还是有自己的一点理解需要讲的。

Category是装饰者模式的一种体现,主要用于在不改变原有类的前提下,动态的给这个类添加一些方法,同时可以将一个类的实现拆分成多个独立的源文件,方便管理。但是不建议在Category中添加属性,这里说是不建议的意思是其实可以在Category里添加属性的,不过需要一些处理,我在后面会解释。

首先我们定义了AClass的一个Category ACategory:

// AClass.h
@protocol AClass_protocol 

- (void)protocolMethods_ACategory;

@end

@interface AClass : NSObject

@end

@interface AClass(ACategory)

@property (nonatomic) NSString *address;

- (void)instanceMethods_ACategory;

+ (void)classMethods_ACategory;

@end

// AClass.m
@implementation AClass

@end

@implementation AClass (ACategory)

@dynamic address;

- (void)instanceMethods_ACategory {
    NSLog(@"instanceMethods_ACategory");
}

+ (void)classMethods_ACategory {
    NSLog(@"classMethods_ACategory");
}

- (void)protocolMethods_ACategory {
    NSLog(@"protocolMethods_ACategory");
}

@end

我们在runtime的源码里可以看到Category的定义:

typedef struct category_t {
    const char *name; //类的名字
    struct class_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,clang -rewrite-objc AClass.m,我们可以得到AClass.cpp,里面包含了如下的内容:

 static struct _category_t _OBJC_$_CATEGORY_AClass_$_ACategory __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "AClass",
    0, // &OBJC_CLASS_$_AClass,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_AClass_$_ACategory,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_AClass_$_ACategory,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_AClass_$_ACategory,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_AClass_$_ACategory,
};
static void OBJC_CATEGORY_SETUP_$_AClass_$_ACategory(void ) {
    _OBJC_$_CATEGORY_AClass_$_ACategory.cls = &OBJC_CLASS_$_AClass;
}

我们之前定义的Category最终会被编译成这个样子,上面的四个Struct都可以找到对应定义:

// _OBJC_$_CATEGORY_INSTANCE_METHODS_AClass_$_ACategory
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_AClass_$_ACategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"instanceMethods_ACategory", "v16@0:8", (void *)_I_AClass_ACategory_instanceMethods_ACategory},
    {(struct objc_selector *)"protocolMethods_ACategory", "v16@0:8", (void *)_I_AClass_ACategory_protocolMethods_ACategory}}
};

// _OBJC_$_CATEGORY_CLASS_METHODS_AClass_$_ACategory
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_AClass_$_ACategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"classMethods_ACategory", "v16@0:8", (void *)_C_AClass_ACategory_classMethods_ACategory}}
};

// _OBJC_CATEGORY_PROTOCOLS_$_AClass_$_ACategory
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_AClass_$_ACategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_AClass_protocol
};

// _OBJC_$_PROP_LIST_AClass_$_ACategory
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_AClass_$_ACategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"address","T@\"NSString\",D,N"}}
};

我们再来看一下两个Category的时候编译出来的category_t结构,其实就是多了一个Category添加到数组里:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_AClass_$_ACategory1,
    &_OBJC_$_CATEGORY_AClass_$_ACategory2,
};

好了,到现在为止我们正式得到了在runtime之前的Category的结构,接下来就是加载Category了,这里我就不讲整个过程了,网上有很多讲的,具体说一下Category和Class中同名方法的执行顺序的问题:

  • Category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果Category和原来类都有methodA,那么Category附加完成之后,类的方法列表里会有两个methodA。
  • Category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的Category的方法会“覆盖”掉原来类的同名方法,这是因为runtime时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法就会停止。

会造成上面这个结果的原因主要是因为下面这个函数:

static void 
attachMethodLists(class_t *cls, method_list_t **addedLists, int addedCount, 
                  BOOL methodsFromBundle, BOOL *inoutVtablesAffected)
{
    rwlock_assert_writing(&runtimeLock);

    // Don't scan redundantly
    BOOL scanForCustomRR = !UseGC && !cls->hasCustomRR();

    // Method list array is NULL-terminated.
    // Some elements of lists are NULL; we must filter them out.

    method_list_t **oldLists = cls->data()->methods;
    int oldCount = 0;
    if (oldLists) {
        while (oldLists[oldCount]) oldCount++;
    }
        
    int newCount = oldCount + 1;  // including NULL terminator
    for (int i = 0; i < addedCount; i++) {
        if (addedLists[i]) newCount++;  // only non-NULL entries get added
    }

    method_list_t **newLists = (method_list_t **)
        _malloc_internal(newCount * sizeof(*newLists));

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    newCount = 0;
    int i;
    for (i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        if (!mlist) continue;

        // Fixup selectors if necessary
        if (!isMethodListFixedUp(mlist)) {
            mlist = fixupMethodList(mlist, methodsFromBundle);
        }

        // Scan for vtable updates
        if (inoutVtablesAffected  &&  !*inoutVtablesAffected) {
            uint32_t m;
            for (m = 0; m < mlist->count; m++) {
                SEL sel = method_list_nth(mlist, m)->name;
                if (vtable_containsSelector(sel)) {
                    *inoutVtablesAffected = YES;
                    break;
                }
            }
        }

        // Scan for method implementations tracked by the class's flags
        if (scanForCustomRR) {
            uint32_t m;
            for (m = 0; m < mlist->count; m++) {
                SEL sel = method_list_nth(mlist, m)->name;
                if (isRRSelector(sel)) {
                    cls->setHasCustomRR();
                    scanForCustomRR = NO;
                    break;
                }
            }
        }
        
        // Fill method list array
        newLists[newCount++] = mlist;
    }

    // Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }
    if (oldLists) free(oldLists);

    // NULL-terminate
    newLists[newCount++] = NULL;
    cls->data()->methods = newLists;
}

这个函数的主要思想是重新创建一个方法列表,然后先添加newList,然后再添加oldList,另外由于class的优先级要高于category,所以category的同名方法肯定是优于class中执行的,而相同category中的同名方法就要看编译顺序了。关于cateory中load方法我已经在之前有提到过了,所以就不讲了。

好说到这里,关于Category的运行流程已经讲完了,剩下来的就是关于在Category中定义属性的问题了,其实是不建议在Category中定义属性的,Effective Object C2.0中的26条也说了这件事,勿在分类中声明属性;由于属性在Category中不能自动生成get和set方法,所以我们需要先把存取方法声明成@dynamic,然后在runtime的时候去实现它的get和set方法,通过objc_getAssociatedObject和objc_setAssociatedObject来完成,这样就可以实现在Category中定义属性了。但是这是个不建议的做法,容易在内存管理上出现一些难排查的问题。

你可能感兴趣的:(NSObject中Category的运行流程解析)