开发中我们都会生成很多对象,这些对象都会被存储在内存中,内存中的最小存储单位为字节,每个字节存储位置都有自己的编号也就是所谓的内存地址,我们通常都是通过指针访问对象,指针其实就是对象在内存中分配的首地址。不同类型的变量会被分配不同大小的内存,但对象的内存分配也是有一些规律的,取到对象在内存中分配的地址,便能对这个对象进行操作。
一、根据对象地址获取成员变量的指针地址
oc中的对象分为实例对象和类对象,实例对象中存储着自己的isa指针及
成员变量,成员变量在内存中的地址一般可根据实例对象的地址加上该成员变量的偏移量获得。
@interface NcClassObj :NSObject {
@public int _aNumber;
};
@property (nonatomic,strong) NSString *bString;
@end
@implementation NcClassObj
- (void)printIvars {
NSLog(@"-------NcClassObj-------");
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([NcClassObj class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSLog(@"%s offset = %td",name,ivar_getOffset(ivar));
}
free(ivars);
NSLog(@"-------NcClassObj-------");
}
@end
@interface NcChildClassObj :NcClassObj {
@public int _cNumber;
};
@property (nonatomic,strong) NSString *dString;
@end
@implementation NcChildClassObj
- (void)printIvars {
[super printIvars];
NSLog(@"-------NcChildClassObj-------");
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([NcChildClassObj class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSLog(@"%s offset = %td",name,ivar_getOffset(ivar));
}
free(ivars);
NSLog(@"-------NcChildClassObj-------");
}
@end
创建一个NcChildClassObj对象,打印对象的成员变量的偏移量
2018-07-05 18:07:54.305549+0800 Nunca[16297:1814058] -------NcClassObj-------
2018-07-05 18:07:54.305706+0800 Nunca[16297:1814058] _aNumber offset = 8
2018-07-05 18:07:54.305810+0800 Nunca[16297:1814058] _bString offset = 16
2018-07-05 18:07:54.305927+0800 Nunca[16297:1814058] -------NcClassObj-------
2018-07-05 18:07:54.306036+0800 Nunca[16297:1814058] -------NcChildClassObj-------
2018-07-05 18:07:54.306198+0800 Nunca[16297:1814058] _cNumber offset = 24
2018-07-05 18:07:54.306475+0800 Nunca[16297:1814058] _dString offset = 32
2018-07-05 18:07:54.306602+0800 Nunca[16297:1814058] -------NcChildClassObj-------
再分别打印地址验证
@implementation NcClassObj
- (void)printIvarsAddress {
NSLog(@"aNumber-----%p",&_aNumber);
NSLog(@"bString-----%p",&_bString);
}
@implementation NcChildClassObj
- (void) printIvarsAddress {
NSLog(@"self-----%p",self);
[super printIvarsAddress];
NSLog(@"cNumber-----%p",&_cNumber);
NSLog(@"dString-----%p",&_dString);
}
2018-07-05 18:17:07.686742+0800 Nunca[16482:1829783] self-----0x600000259080
2018-07-05 18:17:07.686899+0800 Nunca[16482:1829783] aNumber-----0x600000259088
2018-07-05 18:17:07.686997+0800 Nunca[16482:1829783] bString-----0x600000259090
2018-07-05 18:17:07.687063+0800 Nunca[16482:1829783] cNumber-----0x600000259098
2018-07-05 18:17:07.687159+0800 Nunca[16482:1829783] dString-----0x6000002590a0
成员变量的地址确实是等于对象地址加上变量的偏移量。
aNumber为int类型,需要占用四个字节的内存(0x600000259088、0x600000259089、0x60000025908a、0x60000025908b),bString为(NSString *)类型,也就是指向NSString的指针,需要占用八个字节的内存空间,bString的内存分配没有从0x60000025908c(0x600000259088+4)开始,而是从0x600000259090开始从而使0x60000025908c至0x60000025908f的四个字节成为空字节,是使用了字节对齐。
二、根据成员变量的指针地址操作变量
NcChildClassObj *obj = [[NcChildClassObj alloc] init];
obj -> _aNumber = 123;
obj.bString = @"abc";
void *p = (__bridge void *)obj;
int *offset_aNumber = p + 8;
void *offset_bString = p + 16;
unsigned long long bString_addr_val = *(unsigned long long *)(offset_bString);
NSString *bString = (__bridge id)(void*)bString_addr_val;
NSLog(@"-1---aNumber:%d bString:%@",*offset_aNumber,bString);
*offset_aNumber = 789;
*(unsigned long long *)offset_bString = (unsigned long long)@"abcdeeeeeee";
NSLog(@"-2---aNumber:%d bString:%@",obj -> _aNumber,obj.bString);
2018-07-06 14:22:51.149067+0800 Nunca[25514:2573992] -1---aNumber:123 bString:abc
2018-07-06 14:22:51.149421+0800 Nunca[25514:2573992] -2---aNumber:789 bString:abcdeeeeeee
三、对象在访问自己的成员变量时,实际上就是访问对象自己所在的内存地址加上成员变量的偏移量之后所在的内存区域
NSObject *obj = @"i like friday";
NSObject *obj2 = @"but";
NSObject *obj3 = @"today is monday";
NSObject *objyyy = [NcChildClassObj class];
void *p = &objyyy;
[(__bridge id)p print];
print方法在NcChildClassObj中,如下
- (void)print {
NSLog(@"the print value is %@",self.bString);
}
打印结果:
2018-07-06 14:38:29.729016+0800 Nunca[25760:2594962] the print value is but
分析:
objyyy为指向NcChildClassObj类对象的指针,跟NcChildClassObj类的实例对象很像,但是objyyy只有指针没有成员变量,p是指向objyyy指针地址的一个指针,当向p发送print消息时,实际是将消息发送给p所指向的objyyy指针,objyyy指针接收到消息后会根据自己的isa指针(首地址开始 连续八个字节中所存储的内容)去自己所属的类中去查找看是否能处理该消息,objyyy指针的isa指针指向的是NcChildClassObj类,因此能够成功调用到print方法。
在print方法中打印self.bString,此时的&self等于p指针,指向的是objyyy指针所在位置,已知bString的偏移量为16,因此&self.bString则等于objyyy指针地址+16后所得地址,打印各个变量的信息验证:
NSObject *obj = @"i like friday";
NSObject *obj2 = @"but";
NSObject *obj3 = @"today is monday";
NSObject *objyyy = [NcChildClassObj class];
void *p = &objyyy;
NSLog(@"obj == %p --%p",&obj,obj);
NSLog(@"obj2 == %p --%p",&obj2,obj2);
NSLog(@"obj3 == %p --%p",&obj3,obj3);
NSLog(@"objyyy == %p --%p",&objyyy,objyyy);
NSLog(@"p == %p --%p",&p,p);
[(__bridge id)p print];
- (void)print {
NSLog(@"the print class is %@ %p %p",self.class,&self,self);
NSLog(@"the print value is %@",self.bString);
}
打印结果:
2018-07-06 15:20:11.376352+0800 Nunca[26395:2667407] obj == 0x7fff5bbf96c8 --0x10436d640
2018-07-06 15:20:11.376476+0800 Nunca[26395:2667407] obj2 == 0x7fff5bbf96c0 --0x10436d660
2018-07-06 15:20:11.376551+0800 Nunca[26395:2667407] obj3 == 0x7fff5bbf96b8 --0x10436d680
2018-07-06 15:20:11.376645+0800 Nunca[26395:2667407] objyyy == 0x7fff5bbf96b0 --0x1043cf518
2018-07-06 15:20:11.376730+0800 Nunca[26395:2667407] p == 0x7fff5bbf96a8 --0x7fff5bbf96b0
2018-07-06 15:20:11.376835+0800 Nunca[26395:2667407] the print class is NcChildClassObj 0x7fff5bbf9688 0x7fff5bbf96b0
2018-07-06 15:20:11.376991+0800 Nunca[26395:2667407] the print value is but
print方法中self指向的地址为0x7fff5bbf96b0,推理self.bString的地址应为0x7fff5bbf96b0+16 即0x7fff5bbf96c0,0x7fff5bbf96c0 又正好是obj2指针所在位置,因此打印结果为obj2所指向的“but”。