iOS:内存管理基础篇
目录
一,内存布局
二,Tagged Pointer
三,MRC
四,Copy
一,内存布局
1,图解
内存布局2,说明
-
代码段:编译之后的代码
-
数据段
从低到高:
1>字符串常量
2>已初始化的全局变量和静态变量
3>未初始化的全局变量和静态变量
-
堆:用
alloc
创建的对象(从低地址开始分配) -
栈:局部变量(从高地址开始分配)
3,代码
int a = 1;
int b;
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *string1 = @"123";
NSString *string2 = @"456";
static int c = 2;
static int d;
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
int e = 3;
int f;
NSLog(@"数据段-字符串常量1: %p", string1);
NSLog(@"数据段-字符串常量2: %p", string2);
NSLog(@"数据段-已初始化全局变量: %p", &a);
NSLog(@"数据段-已初始化静态变量: %p", &c);
NSLog(@"数据段-未初始化全局变量: %p", &b);
NSLog(@"数据段-未初始化静态变量: %p", &d);
NSLog(@"堆-对象1: %p", object1);
NSLog(@"堆-对象2: %p", object2);
NSLog(@"栈-局部变量1: %p", &e);
NSLog(@"栈-局部变量2: %p", &f);
}
return 0;
}
// 打印
数据段-字符串常量1: 0x1018311c8
数据段-字符串常量2: 0x1018311e8
数据段-已初始化全局变量: 0x101833de0
数据段-已初始化静态变量: 0x101833de4
数据段-未初始化全局变量: 0x101833dec
数据段-未初始化静态变量: 0x101833de8
堆-对象1: 0x600003e24010
堆-对象2: 0x600003e24080
栈-局部变量1: 0x7ffeee3d1c8c
栈-局部变量2: 0x7ffeee3d1c88
二,Tagged Pointer
1,作用
- 说明
1>用于优化
NSNumber
、NSString
、NSDate
等小对象的存储方式2>引入该技术之前,系统会分配一块内存空间来存储它们的数据
3>引入该技术之后,系统直接将它们的数据存储在指针中,这样能够节省内存开销
4>当数据较大指针不够存储时,系统才会分配内存空间来存储
- 图解
- 代码
// 系统把Tagged Pointer的地址混淆了,此方法是用来反混淆的
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t _objc_decodeTaggedPointer(id ptr) {
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
NSNumber *number1 = @(3);
NSNumber *number2 = @(0xFFFFFFFFFFFFFFFF);
NSLog(@"0x%lx", _objc_decodeTaggedPointer(number1));
NSLog(@"%p", number2);
}
return 0;
}
// 打印
0xb000000000000032
0x600000a3d980 // 堆上的地址
2,方法调用
- 代码
int main(int argc, char * argv[]) {
@autoreleasepool {
NSNumber *number = @(3);
NSInteger value = [number integerValue];
}
return 0;
}
- 说明
1>Tagged Pointer是个伪对象,它没有
isa
指针,无法访问类对象2>OC对象方法的调用会转换为
objc_msgSend
函数的调用,函数内部会判断当前对象是否为Tagged Pointer3>如果不是,说明是真正的对象,就可以通过
isa
指针找到类对象,然后在类对象的方法列表中找到方法进行调用4>如果是,就直接从指针中提取数据并返回,这样能够节省调用开销
3,如何识别
- 底层代码(源码下载地址)
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ // Mac平台
# define OBJC_MSB_TAGGED_POINTERS 0
#else // iOS平台
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS // iOS平台
# define _OBJC_TAG_MASK (1UL<<63) // 最高有效位是1(1000...)
#else // Mac平台
# define _OBJC_TAG_MASK 1UL // 最低有效位是1(...0001)
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
// 用指针地址进行位运算
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
- 说明
1>在Mac平台上,如果指针地址的最
低
有效位是1,那么该指针就是Tagged Pointer2>在iOS平台上,如果指针地址的最
高
有效位是1,那么该指针就是Tagged Pointer
- 实例代码
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *string1 = [NSString stringWithFormat:@"a"];
NSString *string2 = [NSString stringWithFormat:@"abcdefghij"];
NSLog(@"0x%lx", _objc_decodeTaggedPointer(string1));
NSLog(@"%p", string2);
}
return 0;
}
// 打印
0xa000000000000611
0x6000033467e0
/*
0x a000 0000 0000 0611
1,字符‘a’的ASCII码是97,61 = 6 * 16^1 + 1 * 16^0 = 97
2,首位a的二进制是1010,最高有效位是1
0x 0000 6000 0334 67e0
最高有效位是0
*/
4,应用
- 代码
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 1000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *string1 = [NSString stringWithFormat:@"abcdefghij"];
self.name = string1; // 会crash
NSString *string2 = [NSString stringWithFormat:@"a"];
self.name = string2; // 不会crash
});
}
}
@end
-
set
方法内部实现
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
- 分析
1>
string1
是真对象,当赋值给name
时会调用set
方法,set
方法内部会调用release
方法释放旧对象,当多条线程同时调用release
方法时就会crash,因为不能向已释放的对象再发送消息了2>
string2
是伪对象,当赋值给name
时不会调用set
方法,而是直接把指针的地址赋值过去,所以就不会crash
三,MRC
1,基本概念
- iOS系统使用引用计数来管理对象的内存
- 新创建对象的引用计数默认是1,当引用计数为0时,对象就会销毁,释放其占用的内存空间
- 调用
retain
方法会让对象的引用计数+1,调用release
方法会让对象的引用计数-1
2,当不再需要某个对象时,必须调用release
方法将它释放,否则会出现内存泄漏
@interface YJPerson : NSObject
@end
@implementation YJPerson
- (void)dealloc {
[super dealloc];
NSLog(@"%s", __func__);
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
NSLog(@"%@", person);
[person release];
}
return 0;
}
// 打印
<YJPerson: 0x600002c040e0>
-[YJPerson dealloc]
3,使用autorelease
方法,系统会在自动释放池结束时,自动调用对象的release
方法
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[[YJPerson alloc] init] autorelease];
NSLog(@"%@", person);
}
NSLog(@"end");
return 0;
}
// 打印
<YJPerson: 0x600003c880a0>
-[YJPerson dealloc]
end
4,想拥有某个对象时就让它的引用计数+1,不想拥有时就让它的引用计数-1
// YJDog
@interface YJDog : NSObject
- (void)eat;
@end
@implementation YJDog
- (void)eat {
NSLog(@"%s", __func__);
}
- (void)dealloc {
[super dealloc];
NSLog(@"%s", __func__);
}
@end
// YJPerson
@interface YJPerson : NSObject
- (void)setDog:(YJDog *)dog;
- (YJDog *)dog;
@end
@implementation YJPerson
{
YJDog *_dog;
}
- (void)setDog:(YJDog *)dog {
_dog = [dog retain]; // +1
}
- (YJDog *)dog {
return _dog;
}
- (void)dealloc {
[_dog release]; // -1
[super dealloc];
NSLog(@"%s", __func__);
}
@end
// main
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
YJDog *dog = [[YJDog alloc] init];
person.dog = dog;
[dog release];
[person.dog eat];
[person release];
}
return 0;
}
// 打印
-[YJDog eat]
-[YJDog dealloc]
-[YJPerson dealloc]
5,旧对象必须释放,否则会出现内存泄漏
- (void)setDog:(YJDog *)dog {
[_dog release]; // 释放dog1
_dog = [dog retain];
}
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
YJDog *dog1 = [[YJDog alloc] init];
YJDog *dog2 = [[YJDog alloc] init];
person.dog = dog1;
person.dog = dog2;
[dog1 release];
[dog2 release];
[person release];
}
return 0;
}
// 打印
-[YJDog dealloc]
-[YJDog dealloc]
-[YJPerson dealloc]
6,如果旧对象跟新对象是同一个对象就不需要释放,否则对象会提前释放导致crash
- (void)setDog:(YJDog *)dog {
if (_dog != dog) { // 加个判断
[_dog release];
_dog = [dog retain];
}
}
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
YJDog *dog = [[YJDog alloc] init];
person.dog = dog;
[dog release];
person.dog = dog;
[person release];
}
return 0;
}
// 打印
-[YJDog dealloc]
-[YJPerson dealloc]
7,@property
会自动生成带下划线的成员变量和get/set
方法
@interface YJPerson : NSObject
@property (nonatomic, retain) YJDog *dog;
@end
@implementation YJPerson
- (void)dealloc {
[_dog release]; // 这里还是需要手动处理
[super dealloc];
NSLog(@"%s", __func__);
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
YJDog *dog = [[YJDog alloc] init];
person.dog = dog;
[dog release];
[person release];
}
return 0;
}
// 打印
-[YJDog dealloc]
-[YJPerson dealloc]
8,基本数据类型不需要进行内存管理,用assign
修饰
@property (nonatomic, assign) NSInteger age;
- (void)setAge:(NSInteger)age {
_age = age;
}
- (NSInteger)age {
return _age;
}
// 对象类型不能用assign修饰,否则set方法也是直接赋值,这样外部的对象释放了,内部的对象就不能使用了
9,应用
@interface ViewController ()
@property (nonatomic, retain) NSArray *array;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 方式一
// NSArray *array = [[NSArray alloc] init];
// self.array = array;
// [array release];
// 方式二
// self.array = [[[NSArray alloc] init] autorelease];
// 方式三
self.array = [NSArray array]; // array方法内部会autorelease
}
- (void)dealloc {
self.array = nil; // set方法内部会release
[super dealloc];
}
@end
四,Copy
1,作用
- 产生一个副本对象,跟源对象互不影响
- 修改了源对象,不会影响副本对象
- 修改了副本对象,不会影响源对象
2,深浅拷贝
- 浅拷贝:指针拷贝,不会产生新对象
- 深拷贝:内容拷贝,会产生新对象
3,copy
和mutableCopy
- 代码
NSArray *array = [NSArray arrayWithObjects:@"1", @"2", nil];
NSLog(@"array -> %@ / %p", array.class, array);
NSLog(@"array+copy -> %@ / %p", [array.copy class], array.copy);
NSLog(@"array+mutableCopy -> %@ / %p", [array.mutableCopy class], array.mutableCopy);
NSLog(@"------------------------------------------------");
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:@"3", @"4", nil];
NSLog(@"mArray -> %@ / %p", mArray.class, mArray);
NSLog(@"mArray+copy -> %@ / %p", [mArray.copy class], mArray.copy);
NSLog(@"mArray+mutableCopy -> %@ / %p", [mArray.mutableCopy class], mArray.mutableCopy);
// 打印
array -> __NSArrayI / 0x600002041580
array+copy -> __NSArrayI / 0x600002041580
array+mutableCopy -> __NSArrayM / 0x600002e41e60
------------------------------------------------
mArray -> __NSArrayM / 0x600002e59650
mArray+copy -> __NSArrayI / 0x600002041d80
mArray+mutableCopy -> __NSArrayM / 0x600002e59e30
- 总结
源对象类型 | 拷贝方法 | 副本对象类型 | 是否产生新对象 | 拷贝类型 |
---|---|---|---|---|
NS* | copy | NS* | NO | 浅拷贝 |
mutableCopy | NSMutable* | YES | 深拷贝 | |
NSMutable* | copy | NS* | YES | 深拷贝 |
mutableCopy | NSMutable* | YES | 深拷贝 |
4,copy
关键字
- 可变的属性不能用
copy
修饰,否则会crash
@interface ViewController ()
@property (nonatomic, copy) NSMutableArray *mArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mArray = [NSMutableArray array];
[self.mArray addObject:@"1"];
}
@end
// 打印
-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x7fff80617ad0
/*
原因:set方法内部会进行一次copy操作,NSMutableArray + copy = NSArray
*/
- 不可变的属性建议用
copy
修饰,否则它会随着源对象的改变而变化,从而破坏了不可变的特性
@interface ViewController ()
@property (nonatomic, strong) NSArray *array;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
self.array = mArray;
[mArray removeObject:@"2"];
NSLog(@"%@", self.array);
}
@end
// 打印
(
1
)
/*
原因:set方法内部会进行一次retain操作,array和mArray指向的是同一块内存空间
*/
5,自定义类实现拷贝功能
// YJPerson
@interface YJPerson : NSObject<NSCopying, NSMutableCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation YJPerson
- (id)copyWithZone:(NSZone *)zone {
YJPerson *person = [[YJPerson allocWithZone:zone] init];
person.name = self.name;
person.age = self.age;
return person;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
YJPerson *person = [[YJPerson allocWithZone:zone] init];
person.name = self.name;
person.age = self.age;
return person;
}
- (NSString *)description {
return [NSString stringWithFormat:@"name: %@, age: %zd", self.name, self.age];
}
@end
// main
int main(int argc, char * argv[]) {
@autoreleasepool {
YJPerson *person1 = [[YJPerson alloc] init];
person1.name = @"123";
person1.age = 3;
YJPerson *person2 = [person1 copy];
YJPerson *person3 = [person1 mutableCopy];
person1.name = @"456";
person1.age = 5;
NSLog(@"%@", person1);
NSLog(@"%@", person2);
NSLog(@"%@", person3);
}
return 0;
}
// 打印
name: 456, age: 5
name: 123, age: 3
name: 123, age: 3