动态实例变量:解决脆弱的基类问题
2013-04-12 20:36
323 查看
原文链接:http://www.cocoawithlove.com/2010/03/dynamic-ivars-solving-fragile-base.html
作者:ani_di
版权所有,转载务必保留此链接 http://blog.csdn.net/ani_di
脆弱的基类问题是基类的很小修改就会破坏子类。实例变量的布局决定了你不能在基类中添加变量而子类不重新编译。
添加实例变量需要在对象指针位置增加变量的偏移,而子类的实例变量总是位于父类后面,而且子类变量的访问通常是在编译时就确定好了,这意味着你不能修改父类的大小,除非子类重新编译以重新计算引用。
大多数面向对象的语言都有类似的问题,添加实例变量几乎不可能在二进制上兼容。
Objectives-C结合现代Objective-C运行时是少数几个在编译语言环境去解决此问题。
动态不是意味着“任何时候”:动态只的是,实例变量的布局在编译时没有确定。事实上,这里的动态是从子类的角度(对于基类,实例变量同之前一样)。 实例变量只能在类关联注册之前(即还没有任何类实例创建)。参考苹果文档class
addlvar。
库的使用者创建他自己的子类
这工作的很好,直到库作者想在 LibraryBaseObject 添加新特性
传统的方法会破坏所有已存在的子类,因为子类没有分配足够的空间容纳 userSubObjectIVar,它在编译时决定的偏移等于 NSObject + LibraryBaseObject。
简单的用新的头文件重新编译,所有的偏移就可以正确更新。否则没有其他办法。
Greg Parker 的 Hamster Emporium:[objc explain]有个很不错的文章,Non-fragile
ivars里面的图表展示了实例变量布局的问题
把所有的数据保存在private类中,这样 LibraryBaseObject 就只占用一个指针的大小。
当然,这种方法有三个问题:
1. 你必须一开始就添加private
2. 两级解引用的性能问题
3. 每次使用 private前,都要强制转换到正确的class
对于程序员,这种方法在Algol之后都不是新内容:大部分编译语言访问struct, record或instance都是如此。
显然,要解决此问题,一些东西要在运行时改变。Objective-C运行时的解决方法是“基类实例变量区域的大小”可以在运行时查询。
现代Objective-C运行时访问实例变量变为下面几步:
一但这样完成,基类的实例变量区域就可以自由增长,子类的偏移随着变动。
所有实例变量都是动态的:程序中所有的实例变量都遵循这样规则,这意味着现代Objective-C运行时的偏移从未在编译时确定
是的,加上额外偏移会减慢一些,但非常小。
编译器已对此做了优化,父类区域大小会保存在寄存器中,而且相同变量一般不会重复计算(译注:此处省略一些原文CPU计算)
比如,开始是这样的
你可以通过修改声明动态添加变量
并在实现中添加下面的代码
由于没有匹配到myIvar或 anotherProperty,这两个实例变量将动态创建。
@synthesize 语句相当于声明。现在你可以这样写
如果你想隐藏 property 的声明或是不想改变头文件,你可以在实现中增加一个私有 category 在实现里面。像这样:
这需要放在 @implement 上面。
子类
两个类都 @synthesize 变量 myIvar。
有些令人奇怪,两个类里面的myIvar并不相同——他们是不同的实例变量,在内存中的位置不同。
为了解决种问题:如果 @synthesize 声明的实例变量与子类冲突,则基类的实例变量依然脆弱。为了支持这一想法,@synthesize 声明的总是当中 @private。
动态实例变量是“现代”运行时的特性;32位的Mac OS X不支持。
作者:ani_di
版权所有,转载务必保留此链接 http://blog.csdn.net/ani_di
动态实例变量:解决脆弱的基类问题
在现代Objective-C运行时(iPhone OS或64位Mac OS X),你可以动态的添加实例变量到一个类中而不用事先定义。动态变量还可以帮助数据隐藏或抽象,甚至可以解决子类和基类拥有相同的实例变量名而不会引用相同底层数据的混乱情况。简介
本文是有关Objective-C中的动态实例变量。动态实例变量用于解决脆弱的基类问题,即实例变量的布局。脆弱的基类问题是基类的很小修改就会破坏子类。实例变量的布局决定了你不能在基类中添加变量而子类不重新编译。
添加实例变量需要在对象指针位置增加变量的偏移,而子类的实例变量总是位于父类后面,而且子类变量的访问通常是在编译时就确定好了,这意味着你不能修改父类的大小,除非子类重新编译以重新计算引用。
大多数面向对象的语言都有类似的问题,添加实例变量几乎不可能在二进制上兼容。
Objectives-C结合现代Objective-C运行时是少数几个在编译语言环境去解决此问题。
动态不是意味着“任何时候”:动态只的是,实例变量的布局在编译时没有确定。事实上,这里的动态是从子类的角度(对于基类,实例变量同之前一样)。 实例变量只能在类关联注册之前(即还没有任何类实例创建)。参考苹果文档class
addlvar。
脆弱的基类的实例变量布局
我们先看一个由布局引起错误的例子。在基类添加实例变量会破坏所有子类
此例子是一个动态链接库(比如苹果写的Cocoa)中允许子类继承:@interface LibraryBaseObject : NSObject { NSString *baseObjectIVar; } @end
库的使用者创建他自己的子类
@interface UserSubObject : LibraryBaseObject { NSString * userSubObjectIVar; } @end
这工作的很好,直到库作者想在 LibraryBaseObject 添加新特性
@interface LibraryBaseObject : NSObject { NSString *baseObjectIVar; id newFeatureObject; } @end
传统的方法会破坏所有已存在的子类,因为子类没有分配足够的空间容纳 userSubObjectIVar,它在编译时决定的偏移等于 NSObject + LibraryBaseObject。
简单的用新的头文件重新编译,所有的偏移就可以正确更新。否则没有其他办法。
Greg Parker 的 Hamster Emporium:[objc explain]有个很不错的文章,Non-fragile
ivars里面的图表展示了实例变量布局的问题
以前解决脆弱的基类的方法
最常见的方法是这样定义你的基类@interface LibraryBaseObject : NSObject { id private; } @end
把所有的数据保存在private类中,这样 LibraryBaseObject 就只占用一个指针的大小。
当然,这种方法有三个问题:
1. 你必须一开始就添加private
2. 两级解引用的性能问题
3. 每次使用 private前,都要强制转换到正确的class
修复方法:让编译时的值成为动态的值
之前,我所说的访问实例变量的方法是:1. 在对象指针前加上实例变量的偏移,得到绝对偏移 2. 从绝对偏移指向的内存中解引用
对于程序员,这种方法在Algol之后都不是新内容:大部分编译语言访问struct, record或instance都是如此。
显然,要解决此问题,一些东西要在运行时改变。Objective-C运行时的解决方法是“基类实例变量区域的大小”可以在运行时查询。
现代Objective-C运行时访问实例变量变为下面几步:
1. 在对象指针加上子类实例变量的偏移 2. 加上父类实例变量区域 3. 解引用
一但这样完成,基类的实例变量区域就可以自由增长,子类的偏移随着变动。
所有实例变量都是动态的:程序中所有的实例变量都遵循这样规则,这意味着现代Objective-C运行时的偏移从未在编译时确定
性能影响
你可能担心,这种改变会让一个常见的任务(访问变量)变慢。是的,加上额外偏移会减慢一些,但非常小。
编译器已对此做了优化,父类区域大小会保存在寄存器中,而且相同变量一般不会重复计算(译注:此处省略一些原文CPU计算)
Synthesizing变量
下一个问题是,我们如何利用这种优势来给一个已存在的类添加实例变量?你可以使用 synthesized property。他可以在实现中创建出实例变量而声明中却不存在。比如,开始是这样的
@interface MyIvarlessObject : NSObject { } @end
你可以通过修改声明动态添加变量
@interface MyIvarlessObject : NSObject { } @property (nonatomic, copy) NSString *myProperty; @property (nonatomic, copy) NSString *anotherProperty; @end
并在实现中添加下面的代码
@synthesize myProperty=myIvar; // a dynamic ivar named myIvar will be generated @synthesize anotherProperty; // a dynamic ivar named anotherProperty will be generated
由于没有匹配到myIvar或 anotherProperty,这两个实例变量将动态创建。
@synthesize 语句相当于声明。现在你可以这样写
- (id)init { self = [super init]; if (self) { myIvar = [[NSString alloc] initWithString:@"someString"]; anotherProperty = [[NSString alloc] initWithString:@"someOtherString"]; } return self; }
如果你想隐藏 property 的声明或是不想改变头文件,你可以在实现中增加一个私有 category 在实现里面。像这样:
@interface MyIvarlessObject () @property (nonatomic, copy) NSString *myProperty; @property (nonatomic, copy) NSString *anotherProperty; @end
这需要放在 @implement 上面。
名字相同的多个实例变量
想象下面的基类@interface BaseObject : NSObject { } @property (nonatomic, copy) NSString *propertyOne; @end @implementation BaseObject @synthesize propertyOne=myIvar; @end
子类
@interface SubObject : BaseObject { } @property (nonatomic, copy) NSString *propertyTwo; @end @implementation SubObject @synthesize propertyTwo=myIvar; @end
两个类都 @synthesize 变量 myIvar。
有些令人奇怪,两个类里面的myIvar并不相同——他们是不同的实例变量,在内存中的位置不同。
为了解决种问题:如果 @synthesize 声明的实例变量与子类冲突,则基类的实例变量依然脆弱。为了支持这一想法,@synthesize 声明的总是当中 @private。
结论
通常你不用担心实例变量的内存布局。只需要在编写或升级动态库,向前兼容时注意。动态实例变量是“现代”运行时的特性;32位的Mac OS X不支持。
相关文章推荐
- 有效解决vue动态绑定多个class的官方实例语法无效的问题
- 快速解决vue动态绑定多个class的官方实例语法无效的问题
- 通过ThreadLocal和实例变量解决并发问题
- java实现动态代理代码实例(死循环溢出的问题的解决)
- 动态script标签解决跨域问题实例
- 嵌入式Linux2.6 Kernel Module模板动态加载实例和常见问题解决方法
- Java如何解决脆弱基类(基类被冻结)问题
- R.NET用于Excel Add-In的多实例(multi-Instance)问题及解决方法(2)
- javamail实例和相关问题解决办法
- 解决 easyui 动态添加控件时无法渲染的问题
- 动态规划解决01背包问题
- java线程安全问题之静态变量、实例变量、局部变量 .
- 解决:无法终止无法挂起BizTalk正在运行的服务实例的问题
- GridView动态添加模板列,并解决数据列PostBack后数据丢失问题!
- 迁入阿里云后:解决了一个IIS动态内容压缩的问题
- 动态加载JS文件,完美解决跨域、编码、嵌套、队列、兼容性、执行顺序等相关问题。
- 静态变量、实例变量、局部变量在多线程环境下的线程安全问题 java 多线程
- 解决ie动态修改link样式,import css不刷新的问题
- javascript中运用闭包和自执行函数解决大量的全局变量问题
- ubuntu在更新的时候报动态 MMap 没有空间了的问题解决