从@property说起(四)深入成员变量
之前的三篇文章都讲的是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说起(四)深入成员变量相关推荐
- java成员变量除了方法传递_JAVA类与对象(四)----成员变量与局部变量 、成员方法、构造方法...
类体中的变量分为两部分.变量定义部分定义的变量为类的成员变量,在方法体中定义的变量和方法中涉及的变量称为局部变量. 成员变量和局部变量的区别: (1).成员变量在整个类中都有效,局部变量只在定义它的方 ...
- 成员变量和属性区别(@property那点事儿)
历史由来: 接触iOS的人都知道,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法. 但这只是在iOS5之后,苹果推出的一个新机制.看老代码时, ...
- OC基础--成员变量的封装
一.封装的作用: 1.重用 2.不必关心具体的实现 3.面向对象三大特征之一 4.具有安全性 二.OC中成员变量的命名规范以及注意事项 1.命名规范--.成员变量都以下划线"_"开 ...
- Java学习笔记10---访问权限修饰符如何控制成员变量、成员方法及类的访问范围...
1.Java有三种访问权限修饰符,分别为public.protected.private,还有一种为缺省权限修饰符的情况,记为default.其中,可以由public和default来修饰类:这四种修 ...
- 01_11_Java语言入门||02_面向对象与常用类||day11_final、权限、内部类、成员变量类型和方法的参数及返回值特例
第一章 final 1.1 final关键字和概念和四种用法 final关键字代表最终,不可改变的 常见四种用法 可以用来修饰一个类 可以用来修饰一个方法 可以用来修饰一个局部变量 可以用来修饰一个成 ...
- python中的类的成员变量以及property函数
1 python类的各种变量 1.1 全局变量 在类外定义的变量. 1.2 类变量 定义在类里面,所有的函数外面的变量.这个变量只有一份,是所有的对象共有的.在类外用"类."来引用 ...
- Objective-C 点语法 成员变量的作用域 @property和@synthesize关键字 id类型
点语法 1.利用点语法替换set方法和get方法 方法调用 Student *stu = [Student new]; [stu setAge : 18]; int age = [stu age]; ...
- 【iOS开发】@property跟成员变量区别
@property在xcode 4.5之前只是单纯的告诉编译器,"我在后面实现了对那个变量的setter和getter方法,你放心吧",但是如果你不写synthesize配合的话就 ...
- 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态
一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...
最新文章
- java 监听写文件的进度_java读取文件显示进度条的实现方法
- android tabhost黑色背景,android更改FragmentTabHost背景和文本颜色
- Ant Design Pro 开发上手
- [Leedcode][JAVA][第355题][设计推特][面向对象][哈希表][链表][优先队列]
- 使用 angular directive 和 json 数据的 D3 带标签 donut chart示例
- WIN7 7100+TOAD最新版本9.7.2.5切换到SCRIPT显示注释的时候是乱码。表数据中文显示正常!求解决方案。...
- js 弹出提示信息,并跳转指定页面代码分享
- 二分插入排序(折半插入排序)--排序算法(六)
- jquery所有版本下载
- matlab学习笔记 struct函数
- C++获取成员变量的偏移地址
- module ‘sklearn.utils._openmp_helpers‘ has no attribute ‘__pyx_capi__‘
- EXCEL:摒弃千篇一律,修改工作表中网络线的颜色
- PyCharm打包可执行文件方法
- TensorFlow1.x最佳实践:Dataset API+Keras Model+TF Train
- CVPR 2022 | Adobe把GAN搞成了缝合怪!凭空P出一张1024分辨率全身人像
- Log of Grade Two
- USB device USB controller USB passthrough
- spring cloud gateway网关和链路监控
- 2021-02-23 PMP 群内练习题 - 光环