提到内存关键字就不得提到引用计数原则,和野指针。
在ARC中,对象的释放是遵循引用计数原则的,所谓引用计数原则实际上就是用计数的方式去控制这个对象的声明周期,当引用计数大于1的时候,代表着对象存在于内存中的某个位置,当引用计数等于0的时候,代表着回收之前对象所占的内存,也就是说对象释放掉了。具体而言就是:就是每个对象在初始化(alloc)以后引用计数由0变为1,对这个对象再执行retain,copy等操作时,使引用计数加1,当执行release方法的时候引用计数减1,释放的时候,引用计数为0;
野指针:指的是当对象或者数据释放以后指向对象或者数据占用内存的指针不为空,也就是内存释放掉以后,指向内存的指针没有为空。通过指针去访问内存,往往会造成崩溃问题
切回正题
ios中常用的内存关键字有weak,strong,copy,assign关键字。
作用简单说明如下:
- weak 修饰对象的时候代表弱引用,在对象释放后指向对象的指针置nil,也就是指针随之释放。常用于循环引用的解除。
- strong 代表强引用,引用计数加1,使对象的生命周期延长。
- copy 使引用计数加1,使对象的生命周期延长。
- assign 代表的是直接赋值,常用于修饰基本数据类型如:int 、float、NSInteger 等,(当修饰对象的时候,对象释放掉以后,指针不会置nil,修饰对象(很复杂)常会引起野指针问题)。
下面分别对数组,自定义对象,以及字符串应用这些关键字来理解各个关键字的作用。
首先定义好各个关键字的变量,
eg:
@interface ViewController ()
@property (nonatomic, strong) NSArray *strongArr;
@property (nonatomic, copy) NSArray *copydArr;
@property (nonatomic, assign) NSArray *assignArr;
@property (nonatomic, weak) NSArray *weakArr;
@end
接着赋值各个变量:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.strongArr = @[@"1",@"2"];
self.copydArr = @[@"1",@"2"];
self.assignArr = @[@"1",@"2"];
self.weakArr = @[@"1",@"2"];
}
然后在
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"strongArr== %@\n",self.strongArr);
NSLog(@"copydArr ====%@\n ",self.copydArr);
NSLog(@" weakArr ==%@",self.weakArr);
NSLog(@" assignArr = ===%@,\n ", self.assignArr);
}
结果如下:
strongArr== (
1,
2
)
2018-09-10 22:02:15.202955+0800 MemoryKeyWord[91058:3327756] copydArr ====(
1,
2
)
2018-09-10 22:02:15.203497+0800 MemoryKeyWord[91058:3327756] weakArr ==(null)
在打印assignArr的时候奔溃,此时通过lldb对assignArr进行调试打印其指针
p self.assignArr
(NSArray *) $0 = 0x00006000002390c0 发现此时assignArr的指针不为空
接着打印指向内存的类名结果如下:
p ((id)0x00006000002390c0)->isa
(Class) $2 = 0x000060000027b6c0
从上面的结果可以分析出,strong 和copy修饰的数组引用计数加1,延长了对象的周期,weak修饰的对象在方法viewDidLoad 调用完成以后就释放掉了,同时指向内存的指针也置为了空;对于assign修饰的数组来讲,造成了野指针,所以log打印的时候引起了崩溃,对象释放了,但是指向对象的指针没有释放,依然可以通过指针访问内存这是十分危险的。
对于自定义对象来讲,和数组是类似的,伙伴们可以自行尝试,其中需要注意的是对于自定义的对象适用copy关键字的话需要类遵循<NSCopying>协议。
接下来重点讲解字符串这个对象,既然关键字对于对象时适用的那么对于字符串看看是否适用!
重复上面的步骤:
@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, copy) NSString *copydStr;
@property (nonatomic, assign) NSString *assignStr;
@property (nonatomic, weak) NSString *weakStr;
- (void)viewDidLoad {
[super viewDidLoad];
self.strongStr = @"1234";
self.copydStr = @"1234";
self.weakStr = @"1234";
self.assignStr = @"1234";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"strongArr== %@\n p ==%p",self.strongStr,self.strongStr);
NSLog(@"copydArr ====%@\n p ==%p",self.copydStr,self.copydStr);
NSLog(@" weakArr ==%@ p ==%p",self.weakStr,self.weakStr);
NSLog(@" assignArr = ===%@,\n p ==%p", self.assignStr,self.assignStr);
}
看到这里是不是以为和数组和自定义对象时一样的结果呢?
说真的我开始也是这样认为的!结果真是还是太嫩呢!
结果为:
2018-09-10 22:34:47.465101+0800 MemoryKeyWord[91830:3354777] strongArr== 1234
p ==0x101d2f098
2018-09-10 22:34:47.468749+0800 MemoryKeyWord[91830:3354777] copydArr ====1234
p ==0x101d2f098
2018-09-10 22:34:47.469241+0800 MemoryKeyWord[91830:3354777] weakArr ==1234 p ==0x101d2f098
2018-09-10 22:34:47.471871+0800 MemoryKeyWord[91830:3354777] assignArr = ===1234,
p ==0x101d2f098
竟然是正常打印没有报错,这和常识相悖,仔细观察,发现这些字符串的地址都是一样的。
同时weak修饰的字符串也没有释放,
联想一下,字符串的存储位置,字面常量的字符串,是存储于静态区,看来是系统是对字符串做了优化。
打印一下这些字符串的类名为: NSCFConstantString
既然字面型的字符串出现了问题,那么就用NSString的实例化方法去创建字符串又是怎样的呢?
经测试: initWithString 方法和上面的是一样的,
对于initWithFormat 方法虽然结果也是一样的,也没有造成崩溃发生,有意思的是字符串类型却变为了 NSTaggedPointerString ,对于NSTaggedPointerString 查资料可知 NSTaggedPointerString是苹果优化字符串存储构造出来的,当字符串比较短(长度小于10)的时候,它和指针一块存储于一块内存空间(由于边界对齐的原因,存放指针的内存会有空闲),此时字符串的生命周期是和程序的生命周期一致,所以对于assign来就不会有野指针的情况发生。
当我们把字符串长度赋值为一个大于等于10的情况,此时发生的情况就和数据类型是数组的时候情况是一样的了,此时字符串类型为NSString 类型,存储为堆区。
经过上述的讨论,对于开头的关键字的叙述,需要在前面加一个限制范围,即对于内存存储位置为堆区的对象而言,才是真正正确的。
网友评论