OC:内存管理、dealloc方法、copy知识点

时间:2023-03-09 16:15:27
OC:内存管理、dealloc方法、copy知识点

属性的声明:使⽤@property声明属性


例如:@property NSString *name;
相当于@interface中声明了两个⽅法(setter、getter):

属性的实现:使⽤@synthesize实现属性

例如:@synthesize name = _name;
相当于@implementation实现了setter、getter

Objective-C提供属性的⺫的是为了简化程序员编码
为属性提供了⼀些关键字⽤以控制setter、getter的实现细节
这些关键字我们称为属性的属性(attribute)
⼀共3⼤类attribute。

第⼀类:读写性控制(readonly、readwrite、setter、getter)
readonly,告诉编译器,只声明getter⽅法(⽆setter⽅法)。

例如:@property(readonly)NSString *name;

//等价于 - (NSString *)name;
readwrite,告诉编译器,既声明setter⼜声明getter。

例如: @property(readwrite)NSString *name;

//等价于 - (NSString *)name; 

- (void)setName:(NSString *)name;
readwrite是读写性控制的默认设置

第⼆类:原⼦性控制(nonatomic、atomic)
atomic。setter、getter⽅法在多线程访问下是绝对安全的,即
setter、getter内部做了多线程访问处理。原⼦性控制的默认设置是
atomic
nonatomic。setter、getter⽅法内部不会做多线程访问处理,仅仅是
普通的setter、getter⽅法

程序开发过程中,setter、getter处处都在⽤,如果使⽤atomic,需要不断
的对setter、getter加锁解锁以保证线程访问安全,会很占⽤系统资源,降低
系统性能。
通常设置为nonatomic,某些属性需要线程安全的时候,才定义为atomic。
例如:@property (readwrite,nonatomic)NSString *name;

//等价于

- (NSString *)name; 

- (void)setName:(NSString *)name;

第三类:语义设置(assign、retain、copy)
assign。setter、getter内部实现是直接赋值。
例如:@property(nonatomic,assign)NSString *name;

- (void)setName:(NSString *)name{

_name = name;

}

- (NSString *)name{

return _name;

}

retain。setter、getter的内部实现会做内存优化。
例如:@property(nonatomic,retain)NSString *name;
- (void)setName:(NSString *)name{

if(_name != name){

[_name release];

_name = [name retain];

}

}
- (NSString *)name{

return [[_name retain]autorelease];

}

copy。setter、getter的内部实现也会做内存优化。
例如:@property(nonatomic,copy)NSString *name;
- (void)setName:(NSString *)name{

if(_name != name){

[_name release];

_name = [name copy];

}

}
- (NSString *)name{

return [[_name retain]autorelease];

}

如果属性是⾮对象类型(⽐如int,float等)属性的语义设置使⽤
assign。
如果属性是对象类型(⽐如

系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1, 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0

per = nil;//per只是一个指针,就像C语言里面的free擦除

[per name];

NSLog(@"%@",[per name]);这次就访问不到上面的名字了

、NSArray等)属性的语义设
置使⽤retain。
如果属性是对象类型并且想得到参数的copy,使⽤copy关键字。

点语法是Objective-C 2.0中定义的语法格式。提供了⼀种便捷的
属性访问⽅式。

凡是符合系统默认setter、getter书写格式的⽅法都可以使⽤点语
法。
例如:[person1 setName:@”zhangsan”];可以等价写成
person1.name = @”zhangsan”;。
NSString *name = [person1 name];可以等价写成

NSString *name = person1.name;
属性是⼀对getter、setter⽅法,点语法是属性的另⼀种调⽤格式。

KVC(Key-Value-Coding),键值编码,是⼀种间接访问实例变量的⽅
法。
key:键,⽤于标识实例变量
vlaue:实例变量对应的值

setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:
setValuesForKeysWithDictionary:

valueForKey:
valueForKeyPath:
valueForUndefinedKey:

当key不存在的时候,会执⾏setValue:forUndefinedKey:
系统默认实现是抛出⼀个异常

属性是Objective-C的重要语法,属性是⼀组getter、setter⽅法,内
部对实例变量进⾏操作。
点语法是⼀种属性的另外⼀种调⽤格式。
KVC是⼀种间接访问实例变量的⽅法。

存的三大问题

//野指针(已经退房,但是手里还有钥匙)(避免野指针出现 释放之后,将指针设为指向空)就是访问了没有所有权的空间,如果你想使用,必须保留对象的所有权,就是内存还存在

//过度释放(就是对一块内存多次释放)

//内存泄露 (开辟了空间,没有回收,苹果给的一个系统的最大内存是50M)

OC的内存管理机制:GC(垃圾回收机制)、引用计数机制。对于我们的IOS的内存管理方式我们用的是:引用计数机制。引用计数机制又分为:ARC(自动引用计数):自动管理内存,空间的开辟需要我们通过代码去完成,(程序员用打吗开辟空间)但是空间的回收是由我们的系统区回收(他回收空间的机制还是基于MRC)这是系统默认的内存管理机制

MRC (手动引用计数):手动管理内存,同样他是由开发人员开辟空间,不同的是,他的回收是由程序员自己负责来回收,(程序员开辟,程序员回收)所谓的回收就是开辟的对象在使用完之后要及时的释放内存。比起 ARC 可以使我们更加灵活的去控制我们的内存的使用以及何时去释放。

先学习MRC,理解之后,再学习ARC。

使用对象的前提是,一个对象(我家的狗)存在,[dog alloc]牵着狗,其他人也想遛狗,引用计数机制

影响引用计数的几个方法:

alloc 对象在堆区开辟空间,空间由无到有的过程,引用计数器由 0 ---- 1

retain 将原来的对象的引用计数器 + 1

copy   拷贝一份新的对象,将新的对象的引用计数器 + 1;原来的引用计数器不变

(深 copy 拷贝之后是重新开辟的空间)

release 将原有的对象的引用计数器 - 1(立即 - 1)

autorelease 延缓 - 1,在未来的某个时候使用对象的引用计数器 - 1,(就是离他最近的一个释放池里释放)

例如:

Person * per = [Person alloc];//per引用计数器 由0 到 1

per.name = @"zahngsan";

NSLog(@"%lu",per.retainCount);//retainCount 是无符号长整型,ARC不可以用,记录当前对象的引用计数

[per release];//释放

NSLog(@"%lu",per.retainCount);//为什么在这里还是1呢?     因为为1的时候有关延迟

在Person.m文件里重写方法

//重写 dealloc 方法 (销毁方法,当对象的引用计数器为0的时候,则会自动调用这个方法,来回收该空间)

-(void)dealloc{

NSLog(@"%@ oh,no 我被释放掉了",self);//self就是当前被释放掉的对象

[super dealloc];//调用父类的方法,才算真正的把内存销毁掉了

}

然后运行,看上面的例子,打印的结果。这时候 returnCount仍然是1,为什么?因为指针还在,但是已经是个野指针了

系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1, 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0

会发现打印的结果还是1,那到底这个对象销毁了没有?

判断对象的内存是否回收,只需要重写一下,对象销毁的方法,dealloc方法,如果走了这个方法,这就说明对象已经被销毁,说明该空间已经被回收

NSLog(@"%p",per);//这里每次打印的地址都不一样,说明已经是个野指针了

per = nil;//per只是一个指针,就像C语言里面的free擦除(这里就是上面写的“野指针”的那种情况)

[per name];

NSLog(@"%@",[per name]);

总结:这里一定要弄清楚

代码

//创建两个Person类

Person * perr = [[Person alloc]init];

Person * per1 = [[Person alloc]init];// 0 -> 1

Person * per2 = [[Person alloc]init];

per2 = [perr copy];//这里崩溃的原因是:Person这个类没有遵循NSCopying协议,要想让他不崩溃,首先要让Person遵循NSCopying协议 必须实现 copyWithZone方法(注意看正确的写法)

//打印的数字很大,所以不能仅仅依靠 retainCount 判断

NSLog(@"%lu",per2.retainCount);

//allock retain  会让对象引用计数器+1 copy 新的对象引用计数器 + 1

[per1 retain];//1 -> 2

NSLog(@"per1的引用计数器%lu",per1.retainCount);

[perr release];

[per1 release];

[per1 release];//这一次销毁对象的内存

[per2 release];

[per2 release];//这一次销毁对象的内存

per1 = nil;

per2 = nil;

//内存管理的原则(retainCount是辅助我们来查看对象引用的次数)

/*

内存管理原则

如果对一个对象进行 alloc retain copy 之后,你就拥有对这个对象的所有权,同时,我们就需要负责对这个对象进行 release 或者 autorelease

只要你调用了这三个方法(alloc retain copy ),下面就必须进行 release 或者 autorelease

*/

//字符串类对象(有点比较特殊)

NSString * str = [[NSString alloc]initWithFormat:@"abcdefghip"];//如果字符创串长度只有“xyz”的话,就会打印的结果也是比较长,如果小于10个字母就会打印很长的(retainCount)结果,如果大于等于10个字母 就会正常,因为小于10个字母的时候,有可能字符串对象不是放在堆区的内存,而是根据对应字符串所占空间大小可能分配在常量区,可能分配在堆区。。

//如果对象分配在常量区的话,他的 retainCount 就是一串非常大的数字(这个时候是以 %lu 打印,就是很大的数字,如果以 %ld 打印的话,输出的就是 - 1)

NSLog(@"%lu",str.retainCount);//

NSString * str1 = [[NSString alloc]initWithFormat:@"abc"];

NSLog(@"%ld",str1.retainCount);//显示 -1

//不要以retainCount为主要参考,还是以上面的内存管理原则为主 //结果说明他是在常量区的字符串

[str release];

[str1 release];//此时的str1的空间已经被回收,下面不能在使用str,否则会成为野指针

str  = nil;//这个一般放到dealloc方法里面写

str1 = nil;

NSString * str2 = @"niujian";

NSLog(@"%lu",str2.retainCount);

//        [str2 release];//写法错误。因为不是在堆区的,不能去人为的操作释放(这里以上面的原则为标准)

//        str2 = nil;    //写法错误。

//什么时候才能满足引用计数的概念?

//1.必须是一个对象  2.对象必须在堆区空间上

//注意:如果对象是在其他区的话,打印出的就是一个非常大的数字(%lu)。或者是 -1(%ld)

//练习:

Person * per3 = [[Person alloc]init];//0---1

NSLog(@">>%lu",per3.retainCount);

Person * per4 = [[Person alloc]init];//0---1

[per3 retain];//1---2

NSLog(@">>%lu",per3.retainCount);

[per3 retain];//2---3

NSLog(@">>%lu",per3.retainCount);

[per3 release];//3---2

NSLog(@">>%lu",per3.retainCount);

[per3 autorelease];//这个会在某个时刻(就看距他最近的自动释放池)2---2

NSLog(@">>%lu",per3.retainCount);

[per3 release];//2---1//上一次还有个autorelease就会自动的减去1

NSLog(@">>%lu",per3.retainCount);

[per4 release];//虽然是1但是已经被释放掉了

NSLog(@">>%lu",per4.retainCount);//

//[per3 release];//过度释放,会导致 crash

per3.name = @"liucuihua";//重写的方法里面已经把当前的对象的指针置为nil了

per3.age = 22;

NSLog(@"%@ %lu",per3.name,per3.age);


autorelease:会将对象放到离他最近的自动释放池中,挡自动释放池销毁的时候,对应的池中的每一个调用autorelease的对象发送release消息,让对象的引用计数器减1

#pragma waring: 在 MRC 下只要你调用了 alloc , retain ,copy 就必须进行 release 或者 autorelease

在定义属性的时候,属性自动生成的 _属性名  默认是私有的

assign语义机制下,不需要引用计数器,因为对于该语义类型的属性是直接赋值操作,取值操作

对于属性的属性是copy 或者 retain 的话我们的写法:

@synthesize name = _name;

-(void)setName:(NSString *)name{

//要把系统的内存机制改为手动管理

if (_name != name) {

[_name release];

_name = [[name copy]autorelease];

}

}

-(NSString *)name{

return [[_name copy]autorelease];//如果前面的属性的属性是retain那么这里的copy 要换成 retain

}

注意: peo1 = [peo retain];//这样的写法是错误的,这时候指针重新指向了peo1,就是peo1指向了peo对象(peo对象的引用计数器加了1)

代码:

//
// main.m
//
#import <Foundation/Foundation.h>
#import "Person.h" int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
//内存的三大问题
//野指针(已经退房,但是手里还有钥匙)(避免野指针出现 释放之后,将指针设为指向空)就是访问了没有所有权的空间,如果你想使用,必须保留对象的所有权,就是内存还存在
//过度释放(就是对一块内存多次释放)
//内存泄露 (开辟了空间,没有回收,苹果给的一个系统的最大内存是50M)
//oc里面的内存管理方式(1)GC垃圾回收 (2)引用计数(作为IOS只会用到的)ARC自动管理内存自动管理内存,空间的开辟需要我们通过代码去完成,(程序员用编码开辟空间)但是空间的回收是由我们的系统区回收他回收空间的机制还是基于MRC)这是系统默认的内存管理机制 MRC 手动管理内存手动管理内存,同样他是由开发人员开辟空间,不同的是,他的回收是由程序员自己负责来回收,(程序员开辟,程序员回收)所谓的回收就是开辟的对象在使用完之后要及时的释放内存。比起 ARC 可以使我们更加灵活的去控制我们的空间使用以及何时去释放。 //内存管理
//内存管理机制是引用计数机制
/*
影响引用计数的几个方法:
alloc 对象在堆区开辟空间,空间由无到有的过程,引用计数器由 0 ---- 1
retain 将原来的对象的引用计数器 + 1
copy 拷贝一份新的对象,将新的对象的引用计数器 + 1;原来的引用计数器不变
(深 copy 拷贝之后是重新开辟的空间)
release 将原有的对象的引用计数器 - 1(立即 - 1)
autorelease 延缓 - 1,在未来的某个时候使用对象的引用计数器 - 1,(就是离他最近的一个释放池里释放) */
Person * per = [Person alloc];//per引用计数器 由0 到 1
per.name = @"zahngsan";
NSLog(@"%lu",per.retainCount);//retainCount 是无符号长整型,(是非自动内存管理)ARC不可以用,记录当前对象的引用计数
[per retain];//per引用计数器 由1 到 2
// NSLog(@"%lu",per.retainCount);
// [per release];//释放 系统会自己处理 retainCount 为0 就释放对象(在销毁的内部才变为0)
NSLog(@"%lu",per.retainCount);//打印还是1,为什么?本来引用对象应该是0才对,为什么打印的结果却是1?
[per release];
[per release];
/*
系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1, 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0
会发现打印的结果还是1,那到底这个对象销毁了没有?
判断对象的内存是否回收,只需要重写一下,对象销毁的方法,dealloc方法,如果走了这个方法,这就说明对象已经被销毁,说明该空间已经被回收
*/
NSLog(@"%p",per);//这里每次打印的地址都不一样,说明已经是个野指针了
per = nil;//per只是一个指针,就像C语言里面的free擦除 他就不再指向原来的内存地址了
[per name];
NSLog(@"%@",[per name]);
// [per retain];
// //[per copy];
// NSLog(@"%lu",per.retainCount); //下午代码
//创建两个Person类
Person * perr = [[Person alloc]init];
Person * per1 = [[Person alloc]init];// 0 -> 1
Person * per2 = [[Person alloc]init];
per2 = [perr copy];//这里崩溃的原因是:Person这个类没有遵循NSCopying协议,要想让他不崩溃,首先要让Person遵循NSCopying协议 必须实现 copyWithZone方法(注意看正确的写法)
//打印的数字很大,所以不能仅仅依靠 retainCount 判断
NSLog(@"%lu",per2.retainCount);
//allock retain 会让对象引用计数器+1 copy 新的对象引用计数器 + 1
[per1 retain];//1 -> 2
NSLog(@"per1的引用计数器%lu",per1.retainCount);
[perr release];
[per1 release];
[per1 release];//这一次销毁对象的内存
[per2 release];
// [per2 release];//这一次销毁对象的内存
// per1 = nil;
// per2 = nil;
//内存管理的原则(retainCount是辅助我们来查看对象引用的次数)
/*
内存管理原则
如果对一个对象进行 alloc retain copy 之后,你就拥有对这个对象的所有权,同时,我们就需要负责对这个对象进行 release 或者 autorelease
只要你调用了这三个方法(alloc retain copy ),下面就必须进行 release 或者 autorelease
*/ //字符串类对象(有点比较特殊)
NSString * str = [[NSString alloc]initWithFormat:@"abcdefghip"];//如果字符创串长度只有“xyz”的话,就会打印的结果也是比较长,如果小于10个字母就会打印很长的(retainCount)结果,如果大于等于10个字母 就会正常,因为小于10个字母的时候,有可能字符串对象不是放在堆区的内存,而是根据对应字符串所占空间大小可能分配在常量区,可能分配在堆区。。
//如果对象分配在常量区的话,他的 retainCount 就是一串非常大的数字(这个时候是以 %lu 打印,就是很大的数字,如果以 %ld 打印的话,输出的就是 - 1)
NSLog(@"%lu",str.retainCount);//通过retainCount可以判断他是在哪个区上
NSString * str1 = [[NSString alloc]initWithFormat:@"abc"];
NSLog(@"%ld",str1.retainCount);//显示 -1
//不要以retainCount为主要参考,还是以上面的内存管理原则为主 //结果说明他是在常量区的字符串 [str release];
[str1 release];//此时的str1的空间已经被回收,下面不能在使用str,否则会成为野指针
str = nil;//这个一般放到dealloc方法里面写
str1 = nil;
NSString * str2 = @"niujian";
NSLog(@"%lu",str2.retainCount);
// [str2 release];//写法错误。因为不是在堆区的,不能去人为的操作释放(这里以上面的原则为标准)
// str2 = nil; //写法错误。
//什么时候才能满足引用计数的概念?
//1.必须是一个对象 2.对象必须在堆区空间上
//注意:如果对象是在其他区的话,打印出的就是一个非常大的数字(%lu)。或者是 -1(%ld) //练习:
Person * per3 = [[Person alloc]init];//0---1
NSLog(@">>%lu",per3.retainCount);
Person * per4 = [[Person alloc]init];//0---1
[per3 retain];//1---2
NSLog(@">>%lu",per3.retainCount);
[per3 retain];//2---3
NSLog(@">>%lu",per3.retainCount);
[per3 release];//3---2
NSLog(@">>%lu",per3.retainCount);
[per3 autorelease];//这个会在某个时刻(就看距他最近的自动释放池)2---2
NSLog(@">>%lu",per3.retainCount);
[per3 release];//2---1//上一次还有个autorelease就会自动的减去1
NSLog(@">>%lu",per3.retainCount);
[per4 release];//虽然是1但是已经被释放掉了
NSLog(@">>%lu",per4.retainCount);//
// [per3 release];//过度释放,会导致 crash
per3.name = @"liucuihua";//重写的方法里面已经把当前的对象的指针置为nil了
per3.age = ;
NSLog(@"%@ %lu",per3.name,per3.age);
// per3 = nil;
// per4 = nil;
#pragma waring: (MAR管理原则) 只要你调用了 alloc , retain ,copy 就必须进行 release 或者 autorelease
//autorelease:会将对象放到离他最近的自动释放池中,挡自动释放池销毁的时候,对应的池中的每一个调用autorelease的对象发送release消息,让对象的引用计数器减1 Person * peo =[[Person alloc]init];
Person * peo1 = [[Person alloc]init];
peo.name = @"HHH";
peo.age = ;
// peo1 = [peo retain];//这样的写法是错误的,这时候指针重新指向了peo1,就是peo1指向了peo对象(peo对象的引用计数器加了1)
peo1 = [peo copy];//把peo 拷贝一份给 peo1
NSLog(@"%@ %ld",peo1.name,peo1.age); }
return ;
}

main.m文件

//
// Person.h #import <Foundation/Foundation.h> @interface Person : NSObject<NSCopying>
//遵循NSCopying协议,同时间也服从协议中的方法
@property(nonatomic,copy)NSString *name;//这里生成的 _name 默认是私有的
@property(nonatomic,assign)NSInteger age;
//assign(语义机制下)不需要进行引用计数器,他的内部是直接的赋值操作
-(void)setAge:(NSInteger)age;
-(NSInteger)age;
@end

Person.h文件

//
// Person.m #import "Person.h" @implementation Person -(id)copyWithZone:(NSZone *)zone{
// Person * per = [[Person copyWithZone:zone]init];
//新创建一个对象
Person * per = [[Person allocWithZone:zone]init];
//对应吧原对象的内容也赋值给新的对象
per.name = self.name;
per.age = self.age;
//返回新建的对象
return per;
} //@synthesize name ; age;//这种写法就是为多个属性(不要系统默认生成的setter getter方法)
//@synthesize name = _name;//这里是告诉系统系统不要提供 setter getter 方法
@synthesize age = _age;//这里写之后,我不要系统提供给我的 setter getter 方法,后面也访问不到 _age(系统也不会给你 _age )表明 setter getter 方法要自己写
//@synthesize age;//这时候就能够访问到 _age
@synthesize name = _name;
-(void)setName:(NSString *)name{
//要把系统的内存机制改为手动管理
if (_name != name) {
[_name release];
_name = [[name copy]autorelease];
}
}
-(NSString *)name{
return [[_name copy]autorelease];//如果前面的属性的属性是retain那么这里的copy 要换成 retain
}
-(void)setAge:(NSInteger)age{
//cmd + shift + k (快捷键) 就是把之前的程序的缓存 clean 清除(用于例如,我先手写的实例变量,然后自己又用属性写法,这时候系统不能能识别)
//属性默认生成的 _属性 是私有的在访问的时候用点语法
//在用KVC的setvalue: forKey方法的时候,一定要重写 setValue:forUndefinedKey方法
_age = age;
}
-(NSInteger)age{
return _age;
}
//重写 dealloc 方法 (销毁方法,当对象的引用计数器为0的时候,则会自动调用这个方法,来回收该空间)
-(void)dealloc{ //我们还有一个age属性,他不需要回收,他只用于存值
NSLog(@"对象为:%@ 。oh,no 我被释放掉了",self.name);//self就是当前被释放掉的对象
[super dealloc];//调用父类的方法,才算真正的把内存销毁掉了
self.name = nil;//这里吧要释放掉的对象的指针置为 nil
}
@end

Person.m文件