開發中我們都會生成很多對象,這些對象都會被存儲在內存中,內存中的最小存儲單位為字節,每個字節存儲位置都有自己的編號也就是所謂的內存地址,我們通常都是通過指針訪問對象,指針其實就是對象在內存中分配的首地址。不同類型的變量會被分配不同大小的內存,但對象的內存分配也是有一些規律的,取到對象在內存中分配的地址,便能對這個對象進行操作。
一、根據對象地址獲取成員變量的指針地址
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”。