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 = 22;
        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 = 23;
//        peo1 = [peo retain];//这样的写法是错误的,这时候指针重新指向了peo1,就是peo1指向了peo对象(peo对象的引用计数器加了1)
        peo1 = [peo copy];//把peo 拷贝一份给 peo1
        NSLog(@"%@ %ld",peo1.name,peo1.age);
        
    }
    return 0;
}
View Code  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
View Code 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
View Code Person.m文件

 

posted @ 2015-08-21 08:42  ywda  阅读(669)  评论(0)    收藏  举报