1 MRC练习
1.1 问题
引用计数是Objective-C语言采用的一种内存管理技术,当一个对象被创建在堆上后,该对象的引用计数就自动设置为1,如果在其它对象中的对象成员需要持有这个对象时,则该对象的引用计数被加上1,此时如果该对象被释放,内存管理程序将首先把该对象的引用计数减1,然后判断该对象的引用计数是否为0,由于其它对象在持有该对象时将引用计数加了1,所以此时该对象的引用计数减1后不为0,则内存管理程序将不会释放该对象。直到持有该对象的其它对象也被释放时,该对象的引用计数再次减1,变为0时,该对象在堆上所占的存储空间才被释放。
引用计数技术的使用能够实现对资源的自动管理。
iOS5.0开始引入自动引用计数技术,iOS7.0以后则默认使用自动引用计数技术。自动引用计数技术,简称为ARC,由于ARC的出现,相对以前的方式被称为手动引用计数技术,简称为MRC。
1.2 方案
本案例是强制使用MRC(手动引用计数技术)来管理对象的引用计数的一个练习。由于iOS7.0以后使用Xcode创建的工程默认使用ARC(自动引用计数技术),所以需要强制转换回MRC。转换的方法如下步骤:
首先,创建一个工程,然后选择“工程导航”中的“工程项”,如图-1所示:
图-1
然后在右边窗口中选择Build Settings,如图-2所示:
图-2
下一步选择All选项,如图-3所示:
图-3
最后,向下滚动屏幕,找到Apple LLVM 5.1 – Language – Objective C中的Objective-C Automatic Reference Counting,将右边的选项选择为NO。如图-4所示:
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义Integer类
首先,在Day03工程中新添加Integer.h文件,用于定义新的类Integer。
代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Integer : NSObject
- @property int integer;
- -(void)print;
- @end
在上述代码中,为Integer类添加一个属性,整型变量integer;然后为Integer类添加一个方法print,用于将属性的值输出到控制台。
然后,在类Integer的实现部分,即在Integer.m文件中,添加print方法的实现。代码如下所示:
- #import "Integer.h"
- @implementation Integer
- -(void)print
- {
- NSLog(@"%d", self.integer);
- }
- @end
步骤二: 查看初始引用计数值
在Day03工程中新添加MRC.m文件,用于主程序,在主程序中定义Integer的对象并查看该对象的引用计数。此时对象的引用计数值为1。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- }
- return 0;
- }
上述代码中,以下代码:
- NSLog(@"%ld", [int1 retainCount]);
retainCount消息将得到对象int1当前的引用计数值。
步骤三:增加引用
在主程序中添加另一个引用,再查看该对象的引用计数。此时对象的引用计数值不会发生变化,仍然为1。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- Integer *int2 = int1;
- NSLog(@"%ld", [int1 retainCount]);
- }
- return 0;
- }
上述代码中,以下代码:
仅仅是定义了一个指针,将其初始化为int1,这将不会改变对象int1的引用计数值。
步骤四:增加引用计数
由于此时有两个指针指向对象int1,int1的引用计数应该变为2。但在MRC下,要想将int1的引用计数值加1,必须手动添加retain消息才能实现。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- Integer *int2 = int1;
- NSLog(@"%ld", [int1 retainCount]);
- [int2 retain];
- NSLog(@"%ld", [int1 retainCount]);
- }
- return 0;
- }
上述代码中,以下代码:
- [int2 retain];
- NSLog(@"%ld", [int1 retainCount]);
是向对象int2发送消息retain,该消息会使int2指向的对象的引用计数加1,由于int2与int1共同指向一个对象,所以向对象int1发送retainCount消息,得到的值为2。
步骤五:减少引用计数
在MRC下,要想将int1的引用计数值减1,必须手动添加release消息才能实现。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- Integer *int2 = int1;
- NSLog(@"%ld", [int1 retainCount]);
- [int2 retain];
- NSLog(@"%ld", [int1 retainCount]);
- [int2 release];
- NSLog(@"%ld", [int1 retainCount]);
- }
- return 0;
- }
上述代码中,以下代码:
- [int2 release];
- NSLog(@"%ld", [int1 retainCount]);
是向对象int2发送消息release,该消息会使int2指向的对象的引用计数减1,由于int2与int1共同指向一个对象,所以向对象int1发送retainCount消息,得到的值为1。
步骤六:释放对象
在MRC下,如果int1的引用计数值减1后值为0,则内存管理程序将把int1所占的内存空间释放掉。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- Integer *int2 = int1;
- NSLog(@"%ld", [int1 retainCount]);
- [int2 retain];
- NSLog(@"%ld", [int1 retainCount]);
- [int2 release];
- NSLog(@"%ld", [int1 retainCount]);
- [int1 release];
- NSLog(@"%ld", [int1 retainCount]);
- int1 = nil;
- [int1 print];
- }
- return 0;
- }
上述代码中,以下代码:
- [int1 release];
- NSLog(@"%ld", [int1 retainCount]);
是向对象int1发送消息release,该消息会使int1指向的对象的引用计数减1,由于int1指向的对象的引用计数已经为1,再减1,将会变为0。此时int1所指向的对象被释放了。但是,如果此时向对象int1发送retainCount消息,得到的int1的引用计数值仍然为1,这是因为此时retainCount消息访问的int1的地址空间,是已经释放的空间,在释放前该空间的引用计数变量没有清0的缘故。
1.4 完整代码
本案例中,类Integer声明,即Integer.h文件,完整代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Integer : NSObject
- @property int integer;
- -(void)print;
- @end
类Integer实现,即Integer.m文件,完整代码如下所示:
- #import "Integer.h"
- @implementation Integer
- -(void)print
- {
- NSLog(@"%d", self.integer);
- }
- @end
主程序,即MRC.m,完整代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Integer.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Integer *int1 = [[Integer alloc] init];
- NSLog(@"%ld", [int1 retainCount]);
- Integer *int2 = int1;
- NSLog(@"%ld", [int1 retainCount]);
- [int2 retain];
- NSLog(@"%ld", [int1 retainCount]);
- [int2 release];
- NSLog(@"%ld", [int1 retainCount]);
- [int1 release];
- NSLog(@"%ld", [int1 retainCount]);
- int1 = nil;
- [int1 print];
- }
- return 0;
- }
2 编写Student和Book类
2.1 问题
本案例需要创建一个Book类,类中有一个整型price属性,用于记录书的价格。还需要创建一个Student类,类中有两个带参属性,它们是整型的年龄age和类Book类型的book,分别用于存储学生的年龄和学生正在学习的书,并且有一个study方法,用于显示年龄为age的学生学习价格为book.price的书。
在主程序中创建Student类的一个对象和Book类的两个对象,首先让Student类的对象拥有一个Book类的对象,然后让Student类的对象更换了拥有对象,换成另一个Book类对象。
2.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义类Book
首先,在Day03工程中新添加Book.h文件,用于定义新的类Book。
代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Book : NSObject
- @property int price;
- @end
在上述代码中,定义了类Book,在类中有一个属性,是整型变量price,用于存储书的价格。
然后,在类Book的实现部分,即在Book.m文件中,添加dealloc方法,该方法在本类中实际上没有任何实际意义,只是在类的对象销毁时,在控制台上输出一个提示。
代码如下所示:
- #import "Book.h"
- @implementation Book
- -(void)dealloc{
- NSLog(@"书对象销毁了 price:%d",self.price);
- [super dealloc];
- }
- @end
dealloc方法不能在类外直接被调用,它是在一个对象被release并且对象的引用计数为0时,由内存管理程序调用的方法。
步骤二:定义类Student
首先,在Day03工程中新添加Student.h文件,用于定义新的类Student。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Book.h"
- @interface Student : NSObject
- {
- }
- @property(nonatomic,assign) int age;
- @property(nonatomic,retain) Book* book;
- -(void)study;
- @end
在上述代码中,定义了类Student,在类中有两个带参属性。
一个属性是整型变量age,如下代码所示:
- @property(nonatomic,assign) int age;
用于存储学生的年龄。它有两个参数,一个是nonatomic,它代表对属性赋值的时候不加锁,即在多线程环境下访问时可能会出现数据错误,如果需要在多线程环境下运行,为保证数据不会出现错误,可使用atomic参数,它会在对属性赋值的时候加锁。另一个参数是assign,对于C语言的基本数据类型,只能选取这个参数。
另一个属性是book,如下代码所示:
- @property(nonatomic,retain) Book* book;
用于存储学生正在学习的书。它有两个参数,一个是nonatomic,另一个参数是retain,该参数一般用于NSObject类及其子类的对象,这些对象在赋值时需要将引用计数加1,retain参数可以满足这样的需求。
然后,在类Student的实现部分,即在Student.m文件中,添加dealloc方法和study方法的实现。
代码如下所示:
- #import "Student.h"
- @implementation Student
- -(void)dealloc{
- [self.book release];
- NSLog(@"学生对象销毁了,dealloc方法执行了");
- [super dealloc];
- }
- -(void)study{
- NSLog(@"学生 age:%d 学习书 price:%d 中的知识",self.age,self.book.price);
- }
- @end
dealloc方法在这个类中是必须有的,前面在讲到属性book的retain参数时,指出retain参数会将赋值给属性book的对象的引用计数加1,那么这个对象必须还要减1,其存储空间才能最终得到释放。但是只要Student类对象的存储空间没有释放,属性book的引用计数就不能减1,所以只有在Student类对象的存储空间释放时,通过dealloc将属性book的引用计数减1。
study方法要完成的工作是在控制台上输出学生的年龄和正在学习的书。
步骤三:在主程序中使用Student类和Book类
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Student.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Student* stu1 = [[Student alloc]init];
- stu1.age = 18;
- Book* sanguo = [[Book alloc]init];
- sanguo.price = 10;
- stu1.book = sanguo;
- NSLog(@"%ld", [sanguo retainCount]);
- Book* hongloumeng = [[Book alloc]init];
- hongloumeng.price = 20;
- stu1.book = hongloumeng;
- NSLog(@"%ld", [hongloumeng retainCount]);
- NSLog(@"%ld", [sanguo retainCount]);
- [hongloumeng release];
- [sanguo release];
- [stu1 study];
- [stu1 release];
- }
- return 0;
- }
在上述代码中,以下代码:
- Student* stu1 = [[Student alloc]init];
- stu1.age = 18;
定义了一个Student类的对象stu1,并将stu1的age属性赋值为18。
在上述代码中,以下代码:
- Book* sanguo = [[Book alloc]init];
- sanguo.price = 10;
- stu1.book = sanguo;
- NSLog(@"%ld", [sanguo retainCount]);
首先,定义了一个Book类的对象sanguo,并将sanguo的price属性赋值为10。然后,将stu1的book属性赋值为sanguo。我们知道book属性有一个参数是retain,它会将赋值给它的对象的引用计数加1,那么现在将sanguo赋值给stu1的book属性,sanguo的引用计数会加1,所以当我们查看sanguo的引用计数值时会发现是2。
在上述代码中,以下代码:
- Book* hongloumeng = [[Book alloc]init];
- hongloumeng.price = 20;
- stu1.book = hongloumeng;
- NSLog(@"%ld", [hongloumeng retainCount]);
- NSLog(@"%ld", [sanguo retainCount]);
首先,定义了一个Book类的对象hongloumeng,并将hongloumeng的price属性赋值为20。然后,将stu1的book属性重新赋值为hongloumeng。我们知道book属性有一个参数是retain,它会将赋值给它的对象的引用计数加1,那么现在将hongloumeng赋值给stu1的book属性,hongloumeng的引用计数会加1,所以当我们查看hongloumeng的引用计数值时会发现是2。但如果此时再查看sanguo的引用计数值时会发现变成了1,这是为什么呢?原因是Student类的book属性参数retain,除了会将赋值给它的对象的引用计数加1外,在加1之前,还会将先前赋值给它的对象的引用计数减1。如本案例中,先将stu1的book属性赋值为sanguo,然后重新赋值为hongloumeng,那么在给hongloumeng引用计数加1之前,先将sanguo的引用计数减了一个1。
在上述代码中,以下代码:
- [hongloumeng release];
- [sanguo release];
对hongloumeng对象进行释放,它不会被从存储空间中删除,因为release只会将hongloumeng的引用计数由2减为1。但是,对sanguo对象进行释放,就会被从存储空间中删除,因为release只会将sanguo的引用计数由1减为0。
在上述代码中,以下代码:
是向对象stu1发送study消息时,因为hongloumeng的引用计数为1,所以它能显示出hongloumeng的价格。
在上述代码中,以下代码:
是释放对象stu1,在stu1的存储空间被释放之前,还会先调用Student类的dealloc方法,在dealloc方法中,hongloumeng的引用计数由1减为0,所以hongloumeng的对象存储空间被释放。而stu1的引用计数一直为1,此时对其进行release,将减为0,stu1的存储空间被释放。
2.3 完整代码
本案例中,类Book声明,即Book.h文件,完整代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Book : NSObject
- @property int price;
- @end
类Book实现,即Book.m文件,完整代码如下所示:
- #import "Book.h"
- @implementation Book
- -(void)dealloc{
- NSLog(@"书对象销毁了 price:%d",self.price);
- [super dealloc];
- }
- @end
本案例中,类Student声明,即Student.h文件,完整代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Book.h"
- @interface Student : NSObject
- {
- }
- @property(nonatomic,assign) int age;
- @property(nonatomic,retain) Book* book;
- -(void)study;
- @end
类Student实现,即Student.m文件,完整代码如下所示:
- #import "Student.h"
- @implementation Student
- -(void)dealloc{
- [self.book release];
- NSLog(@"学生对象销毁了,dealloc方法执行了");
- [super dealloc];
- }
- -(void)study{
- NSLog(@"学生 age:%d 学习书 price:%d 中的知识",self.age,self.book.price);
- }
- @end
主程序,即StudentBook.m,完整代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Student.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Student* stu1 = [[Student alloc]init];
- stu1.age = 18;
- Book* sanguo = [[Book alloc]init];
- sanguo.price = 10;
- stu1.book = sanguo;
- NSLog(@"%ld", [sanguo retainCount]);
- Book* hongloumeng = [[Book alloc]init];
- hongloumeng.price = 20;
- stu1.book = hongloumeng;
- NSLog(@"%ld", [hongloumeng retainCount]);
- NSLog(@"%ld", [sanguo retainCount]);
- [hongloumeng release];
- [sanguo release];
- [stu1 study];
- [stu1 release];
- }
- return 0;
- }
3 编写父类Animal和子类Cat、Dog
3.1 问题
本案例需要创建一个Animal类,类中有一个方法叫shout的方法,该方法默认输出 "动物会叫",这个类作为父类,派生出两个子类Cat类和Dog类。
Cat类没有覆盖父类的shout方法,而Dog覆盖了父类的shout方法,改成自己的输出,"汪汪汪"。
分别调用cat、dog对象的shout方法。
3.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义类Animal
首先,在Day03-2工程中新添加Animal.h文件,用于定义新的类Animal。
代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Animal : NSObject
- @property int age;
- -(void)shout;
- @end
在上述代码中,定义了类Animal,在类中有一个属性,是整型变量age,用于存储动物的年龄。类中还有一个eat方法的声明。
然后,在类Animal的实现部分,即在Animal.m文件中,添加shout方法的实现,该方法在控制台上输出“动物会叫”。
代码如下所示:
- #import "Animal.h"
- @implementation Animal
- -(void)shout{
- NSLog(@"动物会叫");
- }
- @end
步骤二:定义类Cat
首先,在Day03-2工程中新添加Cat.h文件,用于定义新的类Cat。
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Animal.h"
- @interface Cat : Animal
- @end
在上述代码中,以下代码:
是定义一个类Cat,继承父类Animal。
然后,在类Cat的实现部分,即在Cat.m文件中,什么都不做。
代码如下所示:
- #import "Cat.h"
- @implementation Cat
- @end
步骤三:定义类Dog
首先,在Day03-2工程中新添加Dog.h文件,用于定义新的类Dog。
代码如下所示:
- #import "Animal.h"
- @interface Dog : Animal
- @end
上述代码中,类Dog继承与父类Animal。
然后,在类Dog的实现部分,即在Dog.m文件中,覆盖父类Animal中的shout方法。该方法在控制台上输出“汪汪汪”。
代码如下所示:
- #import "Dog.h"
- @implementation Dog
- -(void)shout
- {
- NSLog(@"汪汪汪");
- }
- @end
Dog类中覆盖了Animal类中的方法后, Dog类中有两个shout方法,一个继承自Animal类的shout方法,另一个是是自定义的shout方法。
步骤四:在主程序中使用Animal类、Cat类和Dog类
代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Dog.h"
- #import "Cat.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Animal* animal = [[Animal alloc]init];
- animal.age = 30;
- Cat* cat = [[Cat alloc]init];
- NSLog(@"cat age:%d",cat.age);
- [cat shout];
- Dog* dog = [[Dog alloc]init];
- dog.age = 18;
- NSLog(@"dog age:%d",dog.age);
- [dog shout];
- }
- return 0;
- }
在上述代码中,以下代码:
- Animal* animal = [[Animal alloc]init];
- animal.age = 30;
定义了一个Animal类的对象animal,并将animal的age属性赋值为30。
在上述代码中,以下代码:
- Cat* cat = [[Cat alloc]init];
定义了一个Cat类的对象cat。
在上述代码中,以下代码:
- NSLog(@"cat age:%d",cat.age);
由于定义Cat类的对象cat后,没有为从Animal类继承过来的属性age赋值,所以输出的猫的年龄为0。
在上述代码中,以下代码:
对cat对象发送的shout消息,调用的是父类Animal中shout。因为定义Cat类时没有覆盖父类Animal类中的方法shout,所以在Cat类中只有从父类Cat中继承的shout方法。
在上述代码中,以下代码:
- Dog* dog = [[Dog alloc]init];
- dog.age = 18;
定义了一个Dog类的对象dog,并对从Animal类继承过来的属性age赋值为18。
在上述代码中,以下代码:
- NSLog(@"dog age:%d",dog.age);
由于定义Dog类的对象dog后,已经为从Animal类继承过来的属性age赋值为18,所以输出的狗的年龄为18。
在上述代码中,以下代码:
对dog对象发送的shout消息,调用的是Dog类中shout。因为定义Dog类时已经覆盖父类Animal类中的方法shout,即在Dog类中有两个shout方法,一个是从父类Animal中继承的shout方法,另一个是自定义的shout。按照继承的语法规则,当向Dog类的对象dog发送shout消息时,优先调用自定义的shout方法。
3.3 完整代码
本案例中,类Animal声明,即Animal.h文件,完整代码如下所示:
- #import <Foundation/Foundation.h>
- @interface Animal : NSObject
- @property int age;
- -(void)shout;
- @end
类Animal实现,即Animal.m文件,完整代码如下所示:
- #import "Animal.h"
- @implementation Animal
- -(void)shout{
- NSLog(@"动物会叫");
- }
- @end
本案例中,类Cat声明,即Cat.h文件,完整代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Animal.h"
- @interface Cat : Animal
- @end
类Cat实现,即Cat.m文件,完整代码如下所示:
- #import "Cat.h"
- @implementation Cat
- @end
本案例中,类Dog声明,即Dog.h文件,完整代码如下所示:
- #import "Animal.h"
- @interface Dog : Animal
- @end
类Dog实现,即Dog.m文件,完整代码如下所示:
- #import "Dog.h"
- @implementation Dog
- -(void)shout
- {
- NSLog(@"汪汪汪");
- }
- @end
主程序,即AnimalCatDog.m,完整代码如下所示:
- #import <Foundation/Foundation.h>
- #import "Dog.h"
- #import "Cat.h"
- int main(int argc, const char * argv[])
- {
- @autoreleasepool {
- Animal* animal = [[Animal alloc]init];
- animal.age = 30;
- Cat* cat = [[Cat alloc]init];
- NSLog(@"cat age:%d",cat.age);
- [cat shout];
- Dog* dog = [[Dog alloc]init];
- dog.age = 18;
- NSLog(@"dog age:%d",dog.age);
- [dog shout];
- }
- return 0;
- }
Objective-C----MRC内存管理 、 自动释放池 、 面向对象三大特性及封装 、 继承 、 组合与聚合
原文:http://www.cnblogs.com/52190112cn/p/5049254.html