iOS程序的内存布局(由低到高)
保留段 |
---|
代码段(_TEXT) |
数据段(_DATA) |
堆(heap) |
栈(stack) |
内核区 |
代码段:编译之后的代码
数据段:按如下由低到高分配
- 字符串常量,如NSString *str = @"abc" (常量区)
- 已初始化数据:已初始化的全局变量和静态变量等 (全局区)
- 未初始化数据未初始化的全局变量和静态变量等 (全局区)
栈:函数调用开销,比如局部变量。内存分配的内存空间越来越小
堆:通过alloc、malloc等动态分配的空间,分配的空间地址越来越大
Tagged Pointer
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer之后,NSNumber指针里面存储数据编程了:Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式创建对象来存储数据
- objc_msgSend能识别Tagged Pointer,如果是Tagged Pointer,直接从指针提取数据,节省了以前的调用开销
- 判断是否Tagged Pointer
iOS平台,最高有效位是1
Mac平台,最低有效位是1
判断Tagged Pointer相关源码
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
相关面试题
思考一下以下两段代码会发生什么事?有什么区别?
@property (strong, nonatomic) NSString *name;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
@property (strong, nonatomic) NSString *name;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
第一段会报坏内存访问(EXC_BAD_ACCESS)异常,第二段正常运行
原因:第一段self.name的本质是调用[self setName:]方法,方法实质实现如下
- (void)setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
当多个线程同时调用时,就会出现由于_name已经被释放但还继续调用[_name release]方法,造成坏内存访问,可以使用加锁方式解决该异常
第二段由于[NSString stringWithFormat:@"abc"]是个Tagged Ponter,采用的是直接赋值的方式,所以不会造成问题。
OC对象的内存管理
- 在iOS中,使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象会销毁,释放其占用的内存空间
- 调用retain会会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 内存管理经验总结:
当调用alloc、new、copy、mutableCopy方法返回一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
copy和mutableCopy
NSString | NSMutableString | NSArray | NSMutableArray | NSDictionary | NSMutableDictionary | |
---|---|---|---|---|---|---|
copy | 浅拷贝 | 深拷贝 | 浅拷贝 | 深拷贝 | 浅拷贝 | 深拷贝 |
mutableCopy | 深拷贝 | 深拷贝 | 深拷贝 | 深拷贝 | 深拷贝 | 深拷贝 |
深拷贝和浅拷贝的判断依据是有没有生成新的副本对象
总结:不可变对象的copy是浅拷贝,其他的均为深拷贝
自定义对象使用copy,要遵循NSCopying协议,实现copyWithZone:方法
ARC
- ARC的本质也是MRC, 只不过我们不用手动去管理引用计数,管理引用计数的相关代码由编译器(lvvm)帮我们生成了。ARC是编译器(lvvm)和Runtime机制(weak指针自动置nil)共同协调完成的。
- 在ARC环境下,我们需注意循环引用问题,一般使用weak或__unsafe_unretained解决循环引用。
- ARC 只能帮我们管理 Foudation 对象,非 Foundation 框架下的还是需要我们手动管理的,比如我们常用到 CoreFoundation,使用 CoreFoundation 时就需要注意是否分配了内存,如果分配了内存需要手动调用 free进行释放。以及 Foundation 对象转 CoreFoundation下的内存管理权的移交问题。
网友评论