之前的三篇文章都讲的是interface和setter/getter,这一篇就讲一下ivar。

什么是成员变量

@interface MyViewController :UIViewController
{NSString *name;
}
@end

.m文件中,你会发现如果你使用 self.name,Xcode会报错,提示你使用->,改成self->name就可以了。因为OC中,点语法是表示调用方法,而上面的代码中没有name这个方法。
所以在oc中点语法其实就是调用对象的setter和getter方法的一种快捷方式, self.name = myName 完全等价于 [self setName:myName];

那么成员变量是何时分配内存,又储存在何处呢?

所以我们就要先分析一下objc_class结构体。

objc_class

首先我们知道,OC中,所有的对象都可以认为是id类型。那么id类型是什么呢?

typedef struct objc_class *Class;
typedef struct objc_object {  Class isa;
} *id;

根据runtime源码可以看到,id是指向Class类型的指针
而Class类型是objc_class结构的指针,于是我们可以看到objc_class结构体的定义:

struct objc_class {  Class superclass;const char *name;uint32_t version;uint32_t info;uint32_t instance_size;struct old_ivar_list *ivars;struct old_method_list **methodLists;Cache cache;struct old_protocol_list *protocols;// CLS_EXT onlyconst uint8_t *ivar_layout;struct old_class_ext *ext;
};
// runtime版本不同会有修改,但是本质属性大致如此

可以看到Objective-C对象系统的基石:struct objc_class。
其中,我们可以很快地发现struct objc_ivar_list *ivars,这个就是成员变量列表。

struct objc_ivar {char *ivar_name;char *ivar_type;int ivar_offset;int space;
};struct objc_ivar_list {int ivar_count;int space;struct objc_ivar ivar_list[1];
}

再深入看就能看到ivar真正的定义了,名字,type,基地址偏移量,消耗空间。
实际上在objc_class结构体中,有ivar_layout这么一个东西。

顾名思义存放的是变量的位置属性,与之对应的还有一个weakIvarLayout变量,不过在默认结构中没有出现。这两个属性用来记录ivar哪些是strong或者weak,而这个记录操作在runtime阶段已经被确定好。
具体的东西可以参考sunnyxx孙源大神的文章Objective-C Class Ivar Layout 探索

所以,我们几乎可以确定,ivar的确是在runtime时期就已经被确定。类型,空间,位置,三者齐全。

所以,这也就是为什么分类不能简单地用@property来添加成员变量的原因。

分类中的成员变量

还是sunnyxx大神的文章objc category的秘密,其中对OC分类的本质探索非常透彻。
先看一下分类的结构:

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;  ///  扩展属性method_list_t *methodsForMeta(bool isMeta) { ... }property_list_t *propertiesForMeta(bool isMeta) { ... }
};

可以看到,分类结构本身是不存在ivar的容器的,所以自然没有成员变量的位置。因此也很自然地没有办法自动生成setter和getter。

OC本身是一门原型语言,对象和类原型很像。类对象执行alloc方法就像是原型模式中的copy操作一样,类保存了copy所需的实例信息,这些信息内存信息在runtime加载时就被固定了,没有扩充Ivar的条件。

OC当然也没有封死动态添加成员变量这条路,因为我们有_object_set_associative_reference函数可以用。
那么它的原理又是什么呢?

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// retain the new value (if any) outside the lock.ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);// disguised_ptr_t是包装的unsigned long类型if (new_value) {// break any existing association.AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// secondary table existsObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;j->second = ObjcAssociation(policy, new_value);} else {(*refs)[key] = ObjcAssociation(policy, new_value);}} else {// create the new association (first time).ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {// setting the association to nil breaks the association.AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}}}// release the old value (outside of the lock).if (old_association.hasValue()) ReleaseValue()(old_association);
}

通过代码可以比较清楚的看出来,大概思路是通过维护Map,通过对象来生成一个唯一的 unsigned long 的变量来作为横坐标,查找到之后,再通过key做纵坐标去查找,这样就能找到对应的变量,也就是说只要在 某个对象 中key是唯一的,就能设置和获取对应的变量,这样就与class无关。

属性的内存结构

类的结构在runtime时期就已经确定了,所以按照类结构的角度来看,类中的成员变量的地址都是基于类对象自身地址进行偏移的。

@interface Person: NSObject {NSString * _name;NSString * _sex;char _ch;
}
@property(nonatomic, copy) NSString *pName;
@property(nonatomic, copy) NSString *pSex;
@end    @implementation Person
- (instancetype)init {if (self = [super init]) {NSLog(@"%p, %p, %p, %p, %p, %p, %p", self, &_name, &_sex, &_ch, _pName, _pSex);}return self;
}
@end

后面三个地址确实相差为8位,但是在类对象self和第一个成员变量之间相差的地址是10位。这0x10位的地址偏移,实际上就是isa指针的偏移。

指针在64位系统中占8位地址很正常,但是char类型的成员变量一样也是偏移了8位,明明char类型只需要1bit。这实际上是为了内存对齐

实际上在给类添加成员变量时,会调用这个函数:

BOOL
class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type)

alignment参数就是代表内存对齐方式。

uint32_t offset = cls->unalignedInstanceSize();uint32_t alignMask = (1<<alignment)-1;offset = (offset + alignMask) & ~alignMask;

上面这段代码就是地址偏移计算

苹果规定了某个变量它的偏移默认为1 <<
alignment,而在上下文中这个值为指针长度。因此,OC中类结构地址的偏移计算与结构体还是有不同的,只要是小于8bit长度的地址,统一归为8bit偏移

具体的绑定ivar都通过addIvar这个函数,包括@synthesize关键字的绑定,具体@synthesize绑定成员变量只需要看一下class_addIvar的具体实现即可。


其实通过对成员变量绑定的分析,还有一个相似的问题:为什么OC中的集合类,例如NSArray, NSDictionary等等,都只能存对象而不能存基本类型呢
其实答案不固定,但是思想基本都一致,大家自己思考思考就好。

从@property说起(四)深入成员变量相关推荐

  1. java成员变量除了方法传递_JAVA类与对象(四)----成员变量与局部变量 、成员方法、构造方法...

    类体中的变量分为两部分.变量定义部分定义的变量为类的成员变量,在方法体中定义的变量和方法中涉及的变量称为局部变量. 成员变量和局部变量的区别: (1).成员变量在整个类中都有效,局部变量只在定义它的方 ...

  2. 成员变量和属性区别(@property那点事儿)

    历史由来: 接触iOS的人都知道,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法.  但这只是在iOS5之后,苹果推出的一个新机制.看老代码时, ...

  3. OC基础--成员变量的封装

    一.封装的作用: 1.重用 2.不必关心具体的实现 3.面向对象三大特征之一 4.具有安全性 二.OC中成员变量的命名规范以及注意事项 1.命名规范--.成员变量都以下划线"_"开 ...

  4. Java学习笔记10---访问权限修饰符如何控制成员变量、成员方法及类的访问范围...

    1.Java有三种访问权限修饰符,分别为public.protected.private,还有一种为缺省权限修饰符的情况,记为default.其中,可以由public和default来修饰类:这四种修 ...

  5. 01_11_Java语言入门||02_面向对象与常用类||day11_final、权限、内部类、成员变量类型和方法的参数及返回值特例

    第一章 final 1.1 final关键字和概念和四种用法 final关键字代表最终,不可改变的 常见四种用法 可以用来修饰一个类 可以用来修饰一个方法 可以用来修饰一个局部变量 可以用来修饰一个成 ...

  6. python中的类的成员变量以及property函数

    1 python类的各种变量 1.1 全局变量 在类外定义的变量. 1.2 类变量 定义在类里面,所有的函数外面的变量.这个变量只有一份,是所有的对象共有的.在类外用"类."来引用 ...

  7. Objective-C 点语法 成员变量的作用域 @property和@synthesize关键字 id类型

    点语法 1.利用点语法替换set方法和get方法 方法调用 Student *stu = [Student new]; [stu setAge : 18]; int age = [stu age]; ...

  8. 【iOS开发】@property跟成员变量区别

    @property在xcode 4.5之前只是单纯的告诉编译器,"我在后面实现了对那个变量的setter和getter方法,你放心吧",但是如果你不写synthesize配合的话就 ...

  9. 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态

    一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...

最新文章

  1. java 监听写文件的进度_java读取文件显示进度条的实现方法
  2. android tabhost黑色背景,android更改FragmentTabHost背景和文本颜色
  3. Ant Design Pro 开发上手
  4. [Leedcode][JAVA][第355题][设计推特][面向对象][哈希表][链表][优先队列]
  5. 使用 angular directive 和 json 数据的 D3 带标签 donut chart示例
  6. WIN7 7100+TOAD最新版本9.7.2.5切换到SCRIPT显示注释的时候是乱码。表数据中文显示正常!求解决方案。...
  7. js 弹出提示信息,并跳转指定页面代码分享
  8. 二分插入排序(折半插入排序)--排序算法(六)
  9. jquery所有版本下载
  10. matlab学习笔记 struct函数
  11. C++获取成员变量的偏移地址
  12. module ‘sklearn.utils._openmp_helpers‘ has no attribute ‘__pyx_capi__‘
  13. EXCEL:摒弃千篇一律,修改工作表中网络线的颜色
  14. PyCharm打包可执行文件方法
  15. TensorFlow1.x最佳实践:Dataset API+Keras Model+TF Train
  16. CVPR 2022 | Adobe把GAN搞成了缝合怪!凭空P出一张1024分辨率全身人像
  17. Log of Grade Two
  18. USB device USB controller USB passthrough
  19. spring cloud gateway网关和链路监控
  20. 2021-02-23 PMP 群内练习题 - 光环

热门文章

  1. 昨夜,5G R16标准正式冻结!5G物联网扬帆起航
  2. 科技部5个6G重点项目
  3. 盘点2018十大科技丑闻,IT相关两项
  4. 人工智能+能源:能源行业变革新趋势
  5. 2018全球科技中心报告
  6. 自动驾驶技术之——虚拟场景数据库研究
  7. 谷歌放出AI平民化大招: 李飞飞宣布推出AutoML云平台,让普通企业也能用上深度学习
  8. 你值得拥有!更省钱地完成数据监听
  9. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程
  10. windows 注册表讲解