美文网首页收藏ios
iOS分类(category)为什么不能直接添加属性?

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

作者: yumiao | 来源:发表于2019-11-15 10:38 被阅读0次

从定义分析

先看一下分类的定义:

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

再看下 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,也就是说没有 ivars 数组,也就没有地方来保存属性的值。

源码实测

先定义一个类 Person:

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end

@implementation Person
@end

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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 获取属性
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([Person 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([Person 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([Person 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);
}

打印结果如下:

2019-11-15 09:58:22.117 MusicPlayer[25393:2629911] 属性:0-----name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] 属性:1-----age
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] ivar:0-----_age
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] ivar:1-----_name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:0-----.cxx_destruct
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:1-----setName:
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:2-----name
2019-11-15 09:58:22.118 MusicPlayer[25393:2629911] SEL:3-----setAge:
2019-11-15 09:58:22.120 MusicPlayer[25393:2629911] SEL:4-----age

接着添加分类:

@interface Person (Height)
@property (nonatomic, assign) float height;
@end

@implementation Person (Height)
@end

此时 XCode 报警告,没有实现 setter 和 getter。必须实现:

- (float)height {
    return [objc_getAssociatedObject(self, _cmd) floatValue];
}

- (void)setHeight:(float)height {
    objc_setAssociatedObject(self, @selector(height), @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

此时,输出变为:

2019-11-15 10:20:12.838 MusicPlayer[25451:2634649] 属性:0-----height
2019-11-15 10:20:12.839 MusicPlayer[25451:2634649] 属性:1-----name
2019-11-15 10:20:12.839 MusicPlayer[25451:2634649] 属性:2-----age
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] ivar:0-----_age
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] ivar:1-----_name
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] SEL:0-----.cxx_destruct
2019-11-15 10:20:12.840 MusicPlayer[25451:2634649] SEL:1-----setName:
2019-11-15 10:20:12.841 MusicPlayer[25451:2634649] SEL:2-----name
2019-11-15 10:20:12.844 MusicPlayer[25451:2634649] SEL:3-----height
2019-11-15 10:20:12.845 MusicPlayer[25451:2634649] SEL:4-----setHeight:
2019-11-15 10:20:12.845 MusicPlayer[25451:2634649] SEL:5-----setAge:
2019-11-15 10:20:12.846 MusicPlayer[25451:2634649] SEL:6-----age

可以看到实现了 setter 和 getter 之后,仍然没有 ivar: _height,系统没有实现,我们也没有添加。所谓的关联是我们通过 const char 的 key (指针)来访问关联的对象的。所以关联之后我们只能通过 getter 和 setter 方法去操作,不能直接用 ivar _height 来访问!

关联对象的释放

关联对象会在 object_dispose() 方法中释放,不需要用户去释放。

NSObject 调用 -dealloc 时,只调用了 object_dispose()
object_dispose() 则做了以下事情:

  • 为 C++ 的实例变量们(iVars)调用 destructors
  • 为 ARC 状态下的 实例变量们(iVars) 调用 -release
  • 解除所有使用 runtime Associate 方法关联的对象
  • 解除所有 __weak 引用
  • 调用 free()

总结

分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译时生效的,所以能直接为类添加属性或者实例变量。

另外,个人认为:分类中添加 setter 和 getter,其实可以交给编译器来处理,这样会显得语言的设计更加简洁。在类中,声明属性是可以不写 setter 和 getter 的,编译器会帮我们实现,而分类为什么不呢?毕竟分类的属性声明确定之后,setter 和 getter 的实现其实也已经确定了。

由上面的分析联想到 protocol 中添加属性,其实由 protocol 的用途也能猜测出:protocol 是一系列的协议,要求代理去实现,自己并没有实现。方法或属性都是这样,只是做了声明要求代理去实现。所以添加的属性也只是声明其代理有实现这个属性,自身并没有实现其 getter、setter 以及 ivar。

相关文章

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

    从定义分析 先看一下分类的定义: 再看下 Class 的定义: 对比可以发现 category 中少了 struc...

  • iOS开发 Category

    1.说下category原理,以及category为什么只能添加方法不能添加属性? 分类的实现是将category...

  • iOS Category分类

    1.说下category原理,以及category为什么只能添加方法不能添加属性? 分类的实现是将category...

  • Category-关联对象

    分类添加属性 Category能否添加成员变量?如果可以,如何给Category添加成员变量?不能直接给Categ...

  • 类 *分类* 类扩展

    分类:Category iOS中的分类主要的作用就是为类提供扩展,增加一些方法。但是只能添加方法 不能添加属性?我...

  • iOS runtime之--动态添加属性和方法

    一、runtime添加属性 在Objective-C中,category分类默认只能添加方法,不能添加属性。根本原...

  • 分析Category、load、initialize的加载原理

    先来抛出3个问题:1.Category为什么不能直接添加属性?2.Category中有load方法吗?load方法...

  • Category 的 本质

    Category的实现原理,以及Category为什么只能添加方法不能添加属性 ?category的底层结构是st...

  • iOS底层学习:类的扩展和关联对象

    类的扩展和分类 category:分类、类别 给类增加方法 不能添加成员变量 可以使用runtime给分类添加属性...

  • iOS 类扩展

    类扩张和分类的区别 1、category类别、分类 专门用来给类添加新方法 不能添加成员属性,添加了也不能娶到 可...

网友评论

    本文标题:iOS分类(category)为什么不能直接添加属性?

    本文链接:https://www.haomeiwen.com/subject/mydjictx.html