探究iOS分类(category)为什么不能直接添加属性

本文探讨了iOS分类为何无法直接添加属性,分析了Category的特性,指出由于Category缺少ivar数组,故无法生成实例变量。通过关联对象解决属性getter和setter问题,但无法直接访问ivar。扩展(extension)则能在编译时添加属性。Protocol添加属性仅作声明,无实际实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

既然要探究的是分类,那么我们先看一下分类的定义

//Category表示一个结构体指针的类型
typedef struct objc_category *Category;

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

再看下Class的定义

//Class也表示一个结构体指针的类型
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

对比可以发现category中少了 struct objc_ivar_list * _Nullable ivars也就是说没有ivar数组,猜测这是原因,下面来做实验。

声明一个Dog类

@interface Dog : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Dog

@end

然后打印这个类中的所有的属性、ivar、方法列表:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //获取属性 
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([Dog class], &propertyCount);
    for (int i = 0; i < propertyCount; i ++) {
        objc_property_t property = properties[i];
        NSLog(@"属性:%d-----%s", i, property_getName(property));
    }
    free(properties); //注意释放c指针,以免内存泄露

    //获取ivar
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList([Dog class], &ivarCount);
    for (int i = 0; i < ivarCount; i ++) {
        Ivar ivar = ivars[i];
        NSLog(@"ivar:%d-----%s", i, ivar_getName(ivar));
    }
    free(ivars);
    
    //获取方法列表
    unsigned int methodCount;
    Method *methods = class_copyMethodList([Dog class], &methodCount);
    for (int i = 0; i < methodCount; i ++) {
        Method m = methods[i];
        NSLog(@"SEL:%d-----%s", i, sel_getName(method_getName(m)));
    }
    free(methods);
    
}

打印结果:name的属性,系统默认生成的_name的实例变量,还有getter setter方法

2018-08-15 22:59:24.378267+0800 Dog[46057:1034263] 属性:0-----name
2018-08-15 22:59:24.378466+0800 Dog[46057:1034263] ivar:0-----_name
2018-08-15 22:59:24.378853+0800 Dog[46057:1034263] SEL:0-----.cxx_destruct
2018-08-15 22:59:24.379576+0800 Dog[46057:1034263] SEL:1-----name
2018-08-15 22:59:24.379703+0800 Dog[46057:1034263] SEL:2-----setName:

下面来增加一个类目,看看发生声明情况


@interface Dog (ext)

@property (nonatomic, strong)UIColor  *color;

@end


@implementation Dog (ext)
/*此时已经报警告了:警告的意思是没有实现color的getter和setter方法。那么系统有没有帮我们生成_color呢?

Property 'color' requires method 'color' to be defined - use @dynamic or provide a method implementation in this category
Property 'color' requires method 'setColor:' to be defined - use @dynamic or provide a method implementation in this category
*/

@end

加上分类之后打印结果:发现只是在属性的列表里添加了color属性,并没有生成对应的ivar即_color,也出现了上面警告的并没有实现getter和setter方法;所以我们无法通过getter和setter方法操作color也不能直接访问_color,应该是并不存在这样一个地址供我们去访问。

2018-08-15 22:59:24.378029+0800 Dog[46057:1034263] 属性:0-----color
2018-08-15 22:59:24.378267+0800 Dog[46057:1034263] 属性:1-----name
2018-08-15 22:59:24.378466+0800 Dog[46057:1034263] ivar:0-----_name
2018-08-15 22:59:24.378853+0800 Dog[46057:1034263] SEL:0-----.cxx_destruct
2018-08-15 22:59:24.379576+0800 Dog[46057:1034263] SEL:1-----name
2018-08-15 22:59:24.379703+0800 Dog[46057:1034263] SEL:2-----setName:

问题分析完了,如何添加呢?根据问题我们要解决的是实现getter和setter方法,并且找到一个地址空间供我们访问:系统也帮我们提供了这样的方法:

@interface Dog (ext)

@property (nonatomic, strong)UIColor  *color;

@end


@implementation Dog (ext)
//这里用@selector(color)来用作 const void *key 的指针
- (UIColor *)color {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setColor:(UIColor *)color {
    objc_setAssociatedObject(self, @selector(color), color, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

进行关联之后来看下,是不是对应的getter和setter已经ivar了,打印结果:

2018-08-16 11:02:08.120595+0800 Dog[79591:1182559] 属性:0-----color
2018-08-16 11:02:08.124159+0800 Dog[79591:1182559] 属性:1-----name
2018-08-16 11:02:08.124811+0800 Dog[79591:1182559] ivar:0-----_name
2018-08-16 11:02:08.125605+0800 Dog[79591:1182559] SEL:0-----.cxx_destruct
2018-08-16 11:02:08.125983+0800 Dog[79591:1182559] SEL:1-----name
2018-08-16 11:02:08.126145+0800 Dog[79591:1182559] SEL:2-----setName:
2018-08-16 11:02:08.128130+0800 Dog[79591:1182559] SEL:3-----color
2018-08-16 11:02:08.128328+0800 Dog[79591:1182559] SEL:4-----setColor:

可以看到实现了getter和setter之后已经能够看到了color和setColor了,但是仍然没有ivar的_color,这个是当然的,系统没有实现我们也没添加,所谓的关联是我们通过const char的key(指针)来访问关联的对象的,所以关联之后我们只能通过getter和setter方法去操作,不能直接用ivar _color访问!!!
上面是把我们看到的现象进行分析,思考其为什么会有这样的现象呢?分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译器生效的,所以能直接为类添加属性或者实例变量。

####由此推想protocol添加属性会怎么?
由上面的分析是不是想到了protocol中添加属性?其实由protocol的用途也能猜测出:protocol是一系列的协议,要求代理去实现,自己并没有实现,方法或属性都是这样,只是做了声明要求代理去实现。所以添加的属性也只是声明其代理有实现这个属性,自身并没有实现其getter、setter以及ivar。有兴趣的朋友可以实践看看。

以上问题的分析及解决办法,有什么错误还望指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值