Objective-C 分类和扩展

分类(category)

分类不是类

  • 动态给类添加方法;
  •  或把类中方法按照逻辑分开,优化代码结构;
  • 给类添加方法不会影响子类;
  • 把Framework私有方法公开;
  • 模拟多继承;(protocol 也是解决多继承问题)

使用

Test类的.h和.m

  • 基本使用
#import <Foundation/Foundation.h>

@interface Test : NSObject

@property (nonatomic, copy) NSString *name;

-(void)testMethod;

@end

#import "Test.h"
#import "Test+Category.h"

@implementation Test

-(void)testMethod{
    [self categoryMethod];
}

@end

Test分类的.h 和.m

.h文件
Test 的分类 
#import "Test.h"

//格式  类名 (分类名) 分类名一般和分类的功能相关
@interface Test (Category)

//分类中方法
-(void)categoryMethod;

@end
.m 文件
#import "Test+Category.h"

@implementation Test (Category)

-(void)categoryMethod{
    self.name = @"name";
    [self testMethod];
}

@end

类中引入分类的头文件可以调用分类中的方法, 分类中可以使用类中的属性和方法(仅能使用.h中的属性和方法)。当然可以通过- (id)performSelector:(SEL)aSelector;调用私有方法;

  • 添加"成员变量"

分类中可以通过runtime来添加绑定"成员变量"

static const char *K_MEMBER_VARIABLE = "KMemberVriable";//命名规则 特殊前缀+属性名

-(void)setMemberVribale:(NSString *)memberVribale{
    objc_setAssociatedObject(self, K_MEMBER_VARIABLE, memberVribale, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)memberVribale{
    return objc_getAssociatedObject(self, K_MEMBER_VARIABLE);
}

通过

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,         
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
    OBJC_ASSOCIATION_RETAIN = 01401,       
    OBJC_ASSOCIATION_COPY = 01403          
};
OBJC_ASSOCIATION_ASSIGN 相当于 @property (nonatomic, weak)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 相当于 @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC 相当于 @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN 相当于 @property (atomic, strong)
OBJC_ASSOCIATION_COPY 相当于 @property (atomic, copy)
根据对象的特点去选择

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
//第一个参数 和那个对象关联, 第二个参数 Key(通过Key进行存取), 第三个参数 存入的值, 
//第四个参数 关联策略
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
//第一个参数 和那个对象关联, 第二个参数 Key(通过Key进行存取)

以上就是基本使用。

Category源码

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; //属性 注意property_list_t和ivar_list_t(成员变量)不同
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
    ...
};

可以看到在category的结构体中,包含我们常用的实例方法,类方法,代理和属性,并没有包括成员变量列表,这也解释了,为什么不能使用成员变量;

分类中的属性和方法是怎么添加到类中的呢?

runtime被加载后第一个执行的方法是_objc_init()

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();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

查看里面面的map_images -> map_images_nolock -> _read_images ,在_read_images  function中的一段

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);
                }
            }
        }
    }

源码注释里写的很清楚,

  1. 注册category和category的target class 对应function  addUnattachedCategoryForClass
  2. 重新创建target class 的method remethodizeClass

重点在remethodizeClass

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

在看 attachCategories function

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

上面代码按照顺序看很容易理解,把category的methodList, proplist, protolist 全部添加到class_rw_t的对应的列表中。

具体举例 methods.attachLists function

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]));
        }
    }

从 

memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

可知 把category中的method放到了class的方法之前 ,也就是说method没有被替换只是把category放到的target class之前,也就是所谓的覆盖。想调用被'被覆盖的方法',通过runtime中methodList 倒着查表就可以调用。

扩展(Extension)

Extension是在编译期决议的,伴随类的生命周期,一般用来隐藏类的信息包括私有属性,成员变量和方法。你必须在类的.m文件中才能添加Extension。

@interface Test ()

//私有属性
@property (nonatomic, copy) NSString *privateName;
//私有方法
-(void)privateMethod;

@end

对比:

  属性 Method
Category 分类中属性必须通过setter/getter实现。可以通过关联对象的形式保存。 可以添加方法,但是方法会覆盖类的方法,如果有多个分类中有相同的方法会调用最后一个加载的方法

可以通过方法功能不同将类拆分多个分类,方便使用和维护

可以向类中添加方法,但又不会影响该类的子类

使用方便,不用通过继承方式给类添加新的功能

把Framework私有方法公开

模拟多继承;(protocol 也是解决多继承问题)

不能给已有类添加成员变量

分类中可以有相同的方法,可能会造成方法调用错误(覆盖类中的方法,调用最后一个加载的分类)

(方法名相同问题可以通过不同的分类对方法添加前缀解决)

Extension 可以成员变量 保存到对象的成员变量列表中,私有变量 可以添加方法,方法会覆盖父类中的方法,不能存在相同的方法,私有方法 封装私有变量和方法,隐藏不想给外部的变量和方法  

猜你喜欢

转载自blog.csdn.net/wangchuanqi256/article/details/88740798