写完类与对象后,我们今天来讲讲成员变量和属性。
1.类型编码
编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。当给定一个类型时,@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针这样的基本类型,也可以是结构体、类等类型。事实上,任何可以作为sizeof()操作参数的类型都可以用于@encode()。对于属性而言,还会有一些特殊的类型编码,以表明属性是只读、拷贝、retain等等。(原文出自这里)
2.成员变量
打开runtime.h,我们一点都不惊讶地发现成员变量实际上是一个指向objc_ivar的指针。
继续跟踪objc_ivar,找到如下定义
故名思义,ivar_name就是成员变量的名称,ivar_type就是类型,offset就是离基地址的偏移量。至于space是什么我不太清楚,猜测应该是为兼容架构而产生的变量。
3.属性
objc_property_t:声明的属性的类型,是一个指向objc_property结构体的指针。
property_getAttributes函数返回objc_property_attribute_t结构体列表,objc_property_attribute_t结构体包含name和value。下面的截图是一些关于属性和成员变量的操作,大同小异。
4.存取器
对于一个属性来说,我们既可以直接访问实例变量,也可以通过存取器间接访问。那什么时候直接访问,什么时候间接访问。我这里列举了一些使用存取器的情况。
(1)键值观察。存取器会自动调用willChangeValueForKey:和didChangeValueForKey:。如果直接访问实例变量,会导致监听者收不到回调。
(2)副作用。开发者在自身或者某个子类会在set方法中引入副作用。可能是发出通知火灾NSUndoManager中注册了事件。同时,开发者或者子类会在get方法中增加缓存,而直接访问实例变量会绕开缓存。
(3)惰性初始化。不用存取器的话,不会被初始化。
以下情况则是需要直接访问实例变量:
(1)存取器内部。有可能导致死循环。
(2)dealloc。这里使用存取器访问的话有可能导致监听者收到不必要的通知。
(3)初始化。类似dealloc。这里通常只初始化readonly对象。
5.健壮的实例变量
由于我有一份ppt,这里就直接上ppt截图,不一一讲了。
6.一些疑问
有时候我们看到这样的一个方法:class_addIvar(<#__unsafe_unretained Class cls#>, <#const char *name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char *types#>),这个方法显然是向一个类中添加成员变量,但是仅仅是能向一个动态创建的类中添加成员变量,而编译好的类是不能再通过这个方法添加了。动态添加成员变量的时候,我们必须为它指定内存管理策略,在这个方面里面却看不见类似的参数,那究竟该怎么做呢?详情请看这里。
好了,写完收工。