iOS-底层原理(26)-内存管理之内存布局+Tagged Po
一 iOS程序的内存布局
image.png-
代码段:编译之后的代码
-
数据段
- 字符串常量:比如NSString *str = @"123"
- 已初始化数据:已初始化的全局变量、静态变量等
- 未初始化数据:未初始化的全局变量、静态变量等
-
栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
-
堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
代码例子如下
int a = 10;
int b;
implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self test1];
}
- (void)test1 {
static int c = 20;
static int d;
int e;
int f = 20;
NSString *str = @"123";
NSObject *obj = [[NSObject alloc] init];
NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n",
&a, &b, &c, &d, &e, &f, str, obj);
}
分析结果如下
/*
字符串常量
str=0x1029a4068
已初始化的全局变量、静态变量
&a =0x1029a4db8
&c =0x1029a4dbc
未初始化的全局变量、静态变量
&d =0x1029a4e80
&b =0x1029a4e84
堆
obj=0x60400001b510
栈
&f =0x7ffeed25a990
&e =0x7ffeed25a994
*/
二 Tagged Pointer
-
从64bit开始,iOS引入了
Tagged Pointe
r技术,用于优化NSNumber
、NSDate
、NSString
等小对象的存储 -
在没有使用
Tagged Pointer
之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber
指针存储的是堆中NSNumber
对象的地址值
-
使用
Tagged Pointer
之后,NSNumber指针
里面存储的数据变成了:Tag + Data
,也就是将数据直接存储在了指针中
-
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
-
objc_msgSend
能识别Tagged Pointer
,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销 -
如何判断一个指针是否为
Tagged Pointer
?-
iOS
平台,最高有效位是1(第64bit) -
Mac
平台,最低有效位是1
-
三 判断是否为Tagged Pointer
// 如果是iOS平台(指针的最高有效位是1,就是Tagged Pointer)
# define _OBJC_TAG_MASK (1UL<<63)
// 如果是Mac平台(指针的最低有效位是1,就是Tagged Pointer)
# define _OBJC_TAG_MASK 1UL
- (BOOL)isTaggedPointer:(id)pointer {
return ((uintptr_t)pointer & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
调用
// 是否是tagger pointer
- (void)test3 {
NSNumber *number1 = @4;
NSNumber *number2 = @5;
NSNumber *number3 = @(0xFFFFFFFFFFFFFFF);
NSLog(@"%d %d %d", [self isTaggedPointer:number1], [self isTaggedPointer:number2], [self isTaggedPointer:number3]);
NSLog(@"%p %p %p", number1, number2, number3);
}
执行结果
image.png图解说明
image.png四 MRC
1.MRC环境下Set方法内部实现
- 被
assign
修饰
@property (nonatomic, assign) int age;
- (void)setAge:(int)age {
_age = age;
}
- (int)age {
return _age;
}
- 被
retain
修饰
@property (nonatomic, retain) Dog *dog;
- (void)setDog:(Dog *)dog {
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
- (Dog *)dog {
return _dog;
}
- 被
copy
修饰
@property (copy, nonatomic) NSArray *data;
- (void)setData:(NSArray *)data {
if (_data != data) {
[_data release];
_data = [data copy];
}
}
五 OC对象的内存管理
-
在iOS中,使用引用计数来管理OC对象的内存
-
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
-
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
内存管理的经验总结
-
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
-
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
-
可以通过以下私有函数来查看自动释放池的情况
pextern void _objc_autoreleasePoolPrint(void);
// 声明一
NSMutableArray *data = [[NSMutableArray alloc] init];
self.data = data;
[data release];
// 声明二
NSMutableArray *data = [NSMutableArray array];
self.data = data;
两个声明方式相同,只是一个有调用
release
操作,一个没有。因为array
内部会调用autorelease
方法。
本文参考MJ底层原理教程,非常感谢