目录

iOS:为什么分类中不能直接添加属性

源码

先晒一下OC类和分类的部分源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
typedef struct objc_class *Class;

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    ...
}


struct class_data_bits_t {
    ...

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    ...
}

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;
    
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

    ...
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

从上面可以看出来Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。 ivars是objc_ivar_list(成员变量列表)指针,其在objc_class中关系如下。

1
objc_class -> class_data_bits_t bits -> class_rw_t* data() -> const class_ro_t *ro -> const ivar_list_t * ivars;

再看一下Category的定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
typedef struct category_t *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;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从Category的定义也可以看出Category有实例方法,类方法,甚至可以实现协议,添加属性,但是没有实例变量。

分析

参考了位博主文章之后,这里再补充一下。

博主文章链接:http://www.jianshu.com/p/935142af6a47

通过上面的文章里面的详细介绍以及打印输出,可以看到,在一个分类中添加了一个属性,Xcode不会自动的为其生成一个下划线开头的成员变量及set和get方法。

如果你没有手动的实现这两个方法,直接在外面通过点语法调用这个属性,肯定就直接挂了,Unrecognised selector send to instance。

因为他就没有这两个方法,能不挂吗?所以当你真的在分类中声明了一个属性的时候,就要手动的去实现这个属性的set和get方法,这个时候就要用到运行时机制了,关联上去这个属性的存取过程。

结论

为什么不能为手动添加一个下划线开头的成员变量,然后在实现存取器方法的时候对这个成员变量来存取呢?非要用到所谓的运行时吗?

答案是因为:成员变量是一个类的东西,分类本身就不是一个类,它并没有自己的bits,以及后面的一些东西,分类本来就是OC里面通过运行时动态的为一个类添加的一些方法和属性等,不是一个真正的类,你怎么给他添加成员变量呢。

而因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的。

总结一下,其实分类中是可以为一个类添加属性的,但是一定做不到添加成员变量,不要混淆了成员变量和属性的概念。

只是说现在Xcode自动会给属性生成成员变量让大家对这个概念有点混淆。Property是Property,Ivar是Ivar。

有兴趣可以研究一下类是怎么被创建出来的,类最开始生成了很多基本属性,比如IvarList,MethodList,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。

这就是为什么分类里面不能增加成员变量的原因。

参考

https://blog.csdn.net/JGL357/article/details/79127483 https://www.jianshu.com/p/8aa63f7e98d1 https://blog.csdn.net/mumuyinyin/article/details/72854579 https://tech.meituan.com/DiveIntoCategory.html