Category为什么不能添加属性

分类中能不能定义实例变量,为什么?
答案:不能。类的内存布局在编译时期就已经确定了,category是运行时才加载的早已经确定了内存布局所以无法添加实例变量,如果添加实例变量就会破坏category的内部布局。
继续追问:
1:为什么说category是在运行时加载的?
2:不能添加实例变量,那为什么能添加属性? 

先来看看在runtime中的结构体的样子

在分类转化为c++文件中可以看出_category_t结构体中,存放着类名,对象方法列表,类方法列表,协议列表,以及属性列表。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CategoryName.m

 

1:category小括号里写的名字
2:要扩展的类对象,编译期间这个值是不会有的,在app被runtime加载时才会根据name对应到类对象
3:这个category所有的-方法
4:这个category所有的+方法
5:这个category实现的protocol,比较不常用在category里面实现协议,但是确实支持的
6:这个category所有的property,这也是category里面可以定义属性的原因,不过这个property不会@synthesize实例变量,一般有需求添加实例变量属性时会采用objc_setAssociatedObject和objc_getAssociatedObject方法绑定方法绑定,不过这种方法生成的与一个普通的实例变量完全是两码事。
这里已经可以回答第二个问题了。

再看catagory如何添加进runtime

其实在main函数之前,将runtime通过dyld动态加载进来的时候生效的。怎么验证,再来看runtime源码:
先从objc_init开始,其中大量出现的image并不是图片,而是一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等,所以 ImageLoader 作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。

 

void _objc_init(void)
{
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();
        
    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
} 

在load_images之后调用_read_images方法初始化map后的image,这里面干了很多的事情,像load所有的类、协议和category。
再仔细看category的初始化: 

/ Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            class_t *cls = remapClass(cat->cls);
            // 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 (isRealized(cls)) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            if (cat->classMethods  ||  cat->protocols 
                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->isa, hi);
                if (isRealized(cls->isa)) {
                    remethodizeClass(cls->isa);
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 getName(cls), cat->name);
                }
            }
        }
    } 

__objc_catlist,就是上面category存放的数据段。
以上代码做的事:
1把category的实例方法、协议以及属性添加到类上。
2把category的类方法和协议添加到类的metaclass上。
具体怎么做,主要是两个方法addUnattachedCategoryForClass和remethodizeClass。
addUnattachedCategoryForClass实现映射,remethodizeClass去做具体操作。
再往下看category的各种列表是怎么最终添加到类上的。
点开attachCategoryMethods方法可以看到它将所有category的实例方法列表拼成了一个大的实例方法列表,再通过attachMethodLists去加到方法列表里

static void 
attachCategoryMethods(class_t *cls, category_list *cats,
                      BOOL *inoutVtablesAffected)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    BOOL isMeta = isMetaClass(cls);
    method_list_t **mlists = (method_list_t **)
        _malloc_internal(cats->count * sizeof(*mlists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    BOOL fromBundle = NO;
    while (i--) {
        method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= cats->list[i].fromBundle;
        }
    }

    attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);

    _free_internal(mlists);

} 

结论:
1)category的方法并没有“完全替换掉”原来类已经有的方法,而是把扩展的方法放入到方法列表的前头,举个栗子(原来的方法列表<a,b,c,>,扩展的方法是<1,2,3>,会变成<1,2,3,a,b,c>。)
2)为什么平常所说的category的方法会“覆盖”掉原来类的同名方法,就是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,而且只要一找到对应名字的方法,就会结束查找。

另外的一些疑问

问:Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
答:Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法

问:load、initialize的区别,以及它们在category重写的时候的调用的次序。
答:区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译器生效的,所以能直接为类添加属性或者实例变量。

protocol添加属性会怎么

protocol是一系列的协议,要求代理去实现,自己并没有实现,方法或属性都是这样,只是做了声明要求代理去实现。所以添加的属性也只是声明其代理有实现这个属性,自身并没有实现其getter、setter以及ivar。

我们来试一下:

未实现警告
当我们在协议中定义一个必须实现(@ required修饰)的属性以后,如果实现类没有对这个属性做任何实现那么XCode中实现类中就会发出警告

方式一:在.m实现类中添加@synthesize speed;

@protocol FlyDelegate <NSObject>

@required
@property(nonatomic, assign) NSUInteger speed;

@end

@interface TestProtocolProperty : NSObject<FlyDelegate>

@end

@implementation TestProtocolProperty

@synthesize speed;

- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}

@end

方式二:在.m实现文件中添加合成speed属性的成员变量_speed和对应的getter和setter方法

@protocol FlyDelegate <NSObject>

@required
@property(nonatomic, assign) NSUInteger speed;

@end

@interface TestProtocolProperty : NSObject<FlyDelegate> {
    NSUInteger _speed;
}
@end

@implementation TestProtocolProperty

- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}

- (void)setSpeed:(NSUInteger)speed {
    _speed = speed;
}

- (NSUInteger)speed {
    return _speed;
}

@end

结论:OC语言的协议里面是支持定义属性的,而在协议中定义属性其实和在其中定义方法一样只是定义了getter和setter方法,并没有具体实现,所以当这个协议属性修饰符为@ required时,如果不实现编译器就会报出警告,最简单的方式就是加上属性同步语句@synthesize propertyName;

思考:属性和方法其实都是一个事物的特性,协议正是描述某类行为和特性的一种规范,基于这个事实,所以在协议中定义属性是很符合道理的。之所以在iOS开发中很少看到有人这么使用过是因为,iOS开发中协议通常是被用作代理模式而存在的,并且如果在协议中定义了是属性,就必须在实现类中添加对属性自动同步或者手动添加属性实现代码


 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值