Objective-CiOS-进阶学习

iOS:内存管理基础篇

2020-05-09  本文已影响0人  码小菜
风景

目录
一,内存布局
二,Tagged Pointer
三,MRC
四,Copy

一,内存布局

1,图解

内存布局

2,说明

从低到高:
1>字符串常量
2>已初始化的全局变量和静态变量
3>未初始化的全局变量和静态变量

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>用于优化NSNumberNSStringNSDate等小对象的存储方式

2>引入该技术之前,系统会分配一块内存空间来存储它们的数据

3>引入该技术之后,系统直接将它们的数据存储在指针中,这样能够节省内存开销

4>当数据较大指针不够存储时,系统才会分配内存空间来存储

Tagged Pointer
// 系统把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 Pointer

3>如果不是,说明是真正的对象,就可以通过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 Pointer

2>在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
- (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,基本概念

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,copymutableCopy

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关键字

@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
 */
@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
上一篇 下一篇

猜你喜欢

热点阅读