---------------------- Java培训、Android培训、iOS培训、.Net培训,期待与您交流! ---------------------
一、内存管理
移动设备的内存极其有限,每个app所能占用的内存也是有限制的。
内存管理的范围:任何继承了NSObject的对象,对基本数据类型无效。
内存管理方法:
- 你想使用某个对象,就应该让对象的引用计数器+1
- 当你不想使用某个对象时,就应该让对象引用计数器-1
- 谁retain,谁release
- 谁alloc,谁release
内存管理代码规范:
内存管理代码规范:
1.只要调用了alloc,必须有release(autorelease)
2.set方法的代码规范
1》基本数据类型:直接赋值
- (void)setAge:(int)age
{
_age = age;
}
2》OC对象类型
- (void)setCar:(Car *)car
{
//1.先判断是不是新传递进来的对象
if(car != _car)
{
//2.对旧对象做一次release
[_car release];
//3.对新对象做一次retain
_car = [car retain];
}
}
3.dealloc方法的代码规范
1》一定要调用[super dealloc],且放最后
2》对当前对象所拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
[super dealloc];
}
4.如果对象没有使用alloc就不需要release,如NSString类型对象
1. 引用计数器
每个OC对象都有自己的引用计数器,表示“对象被引用的次数”。
每个OC对象内部都有专门的4个字节的存储空间来存放引用计数器。
(1)引用计数器的作用:
1》当使用alloc、new或copy创建一个新对象时,新对象的引用计数器就为1。
2》当一个对象的引用计数器的值为0时,对象所占的内存就会被系统回收。如果对象的引用计数器不为0,对象在整个程序的运行过程中,都不会被回收(除非整个程序退出)。
(2)引用计数器的基本操作(针对当前对象)
1》retain 计数器+1 返回对象本身
2》release 计数器-1 没有返回值
3》retainCount 返回当前计数器值
4》dealloc
* 当一个对象要被回收的时候,就会自动调用
* 一定要调用[super dealloc],而且放在最后面
//当Person对象被回收时,一定会【自动】调用这个方法
- (void)dealloc
{
NSLog(@"Person对象被回收!!!");
//super的dealloc一定要调用,而且放在最后面
[super dealloc];
}
(3)对象的销毁
1》当对象的引用计数器为0时,对象被销毁,其所占的内存被系统回收。
2》当一个对象销毁时,系统会自动向对象发送一条dealloc的消息。(调用- dealloc方法)
3》一般会重写dealloc方法,在这里释放相关的资源。
4》一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面。
5》不要直接调用dealloc方法,系统在对象销毁时会调用的。
6》一旦对象被回收了,它占用的内存就不再可用,坚持使用就会导致野指针错误。
(4)僵尸对象、野指针、空指针
1》僵尸对象:所占内存已经被回收的对象,僵尸对象不能再使用
给已经释放的对象发送了一条消息:
message sent to deallocated instance 0x1001099d0
默认情况下,Xcode是不会管僵尸对象的,如何开启僵尸对象监控:
Xcode界面:set the active scheme -> edit scheme.. -> Diagnostics -> Enable Zombie Objects
2》野指针:指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错
EXC_BAD_ACCESS:访问了一块坏的内存(已被回收,不可用的内存)
3》空指针:没有指向任何东西的指针(存储的东西是nil,null,0),给空指针发送消息不会报错
2. setter方法的内存管理
(1)set方法实现
1》基本数据类型:直接赋值
- (void)setAge:(int)age
{
_age = age;
}
2》OC对象类型
- (void)setCar:(Car *)car
{
//1.先判断是不是新传递进来的对象
if(car != _car)
{
//2.对旧对象做一次release
[_car release];
//3.对新对象做一次retain
_car = [car retain];
}
}
2》对当前对象所拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
[super dealloc];
}
(2)dealloc方法实现
1》一定要调用[super dealloc],且放最后
@implementation Person
- (void)setCar:(Car *)car
{
// 对当前正在使用的车(旧车)进行一次release操作
// [nil release] 无操作
if (car != _car) {
[_car release];
// 对新车进行一次retain操作
_car = [car retain];
}
}
- (Car *)car
{
return _car;
}
- (void)setAge:(int)age
{
// 基本数据类型不需要管理内存
_age = age;
}
- (int)age
{
return _age;
}
- (void)dealloc
{
// Person对象释放,Car对象进行一次release操作
[_car release];
NSLog(@"年龄为%d的Person对象被dealloc", self.age);
[super dealloc];
}
@end
3. 设置property参数进行内存管理
@property 参数:
1》set方法内存管理相关的参数
* retain:release旧值,retain新值(适用于OC对象类型)
* assign:直接赋值(默认,适用于非OC对象类型)
* copy:release旧值,copy新值(一般用于NSString *)
2》是否要生成set方法
* readonly:只会生成getter的声明和实现
* readwrite:同时生成setter和getter的声明和实现
3》多线程管理
* nonatomic:性能高(推荐)
* atomic:性能低(默认)
4》setter和getter方法的名称
* setter:决定了set方法的名称,一定要由冒号:
* getter:决定了get方法的名称(一般用在BOOL类型变量中,getter = isRich)
@interface Person : NSObject
@property int age;
@property (nonatomic, readonly) double height;
@property (setter = setAbc:, getter = aaa) double weight;
// 返回BOOL类型的方法中,方法名一般以is开头
@property (getter = isRich) BOOL rich;
// retain参数:生成的set方法中,release旧值,retain新值
@property (retain) Book *book;
@property (retain) NSString *name;
@end
【注意】:被retain过的属性,必须在dealloc方法中release对应的属性。
@implementation Person
//- (void)setBook:(Book *)book
//{
// if (_book != book) {
// [_book release];
// _book = [book retain];
// }
//}
- (void)dealloc
{
[_book release];
[_name release];
[super dealloc];
}
@end
4. 循环引用
循环引用场景:A类引用了B类,同时B类也引用了A类。导致A对象和B对象永远无法释放。
面对这种两端循环引用,两端循环retain引用:
解决方法:一端用retain,一端用assign。
【开发中一个类引用另一个类的规范】:
1》在.h文件中用@class来声明类
2》在.m文件中用#import来包含类的所有东西
@class的作用:仅仅告诉编译器,某个名称是一个类
格式:@class 类名;
【@class和#import的区别】
1>#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中B *b只是类的声明,具体这个类里面有什么信息,编译器不需要知道。等实现文件.m中真正用到的时候,才会真正去查看B类里面的信息。
2>如果很多头文件都#import了同一个文件,那么一旦修改了最开始的头文件,后面所有#import引用到这个文件的所有的类都需要重新编译一遍。@class就不会有这种问题。
3>使用了@class方式,在.m实现文件中,如果需要用到被引用的实体变量或方法,我们还需要使用#import方式引入被引用类。#import方式则不用。
5. autorelease
@autoreleasepool
{ // { 开始 代表创建了释放池
// autorelease方法会返回对象本身
// [p autorelease];调用完autorelease方法后,不影响对象计数器
// autorelease会将对象放到一个自动释放池中
// 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
Person *p = [[[Person alloc] init] autorelease]; // p-1
p.age = 10;
@autoreleasepool {
Person *p2 = [[[Person alloc] init] autorelease]; // p2-1
p2.age = 20;
} // p2-0
// p-0
} // } 结束 代表销毁释放池
(1)autorelease的基本用法
1》autorelease会将对象【放到】一个自动释放池中
2》当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3》返回对象本身
4》调用完autorelease方法后,不影响对象计数器(销毁的时候计数器才改变(release))
(2)autorelease的好处
1》不用再关心对象释放的时间
2》不用再关心什么时候调用release
(3)autorelease的使用注意
1》占用内存较大的对象不要随便使用autorelease(一直占着,直到pool销毁才释放,太耗内存)
2》占用内存较小的对象使用autorelease,没有太大的影响
(4)常见错误
1》alloc之后调用了autorelease,又调用release,池释放时出现野指针错误
2》连续调用autorelease,池释放时也会出现野指针错误
@autoreleasepool {
// p-1
// 若调用2次autorelease方法,释放池的时候会进行2次release操作,出现野指针错误
Person *p = [[[Person alloc] init] autorelease];
// p-0
[p release];
} // 野指针错误
(5)自动释放池
1》在IOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在(先进后出)
2》当一个对象调用autorelease方法时,会将这个对象放到栈顶的池子中
(6)自动释放池的创建方式
1》IOS 5.0 before
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Person *p1 = [[[Person alloc] init] autorelease];
[pool release]; // [pool drain]; 排水..
2》IOS 5.0 later
@autoreleasepool {
// ... code ...
}
(7)系统自带的方法
1》系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
如:NSString、NSNumber
NSString *str = @"wert";
NSString *str1 = [NSString stringWithFormat:@"age is %d", 15];
2》开发中经常会提供一些【类方法】,快速创建一个已经autorelease的对象
* 创建对象的时候,不要直接用类名。一般用self
+ (id)person
{
// self 任何子类调用该方法,都可以 self == 子类
return [[[self alloc] init] autorelease];
}
// Person *p = [[[Person alloc] init] autorelease];
Person *p = [Person person];
p.age = 200;
二、ARC
ARC是编译器特性:
IOS 5之后,完全消除了手动管理内存的繁琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。
1. ARC的基本使用
(1)指针分2种
1》强指针:默认情况下,所有的指针都是强指针 __strong
2》弱指针:__weak
1》不允许调用release、retain、retainCount
2》允许重写dealloc,但不允许调用[super dealloc]
3》@property参数
* strong:成员变量是强指针,相当于原来的retain(适用于OC对象)
* weak:成员变量是弱指针,相当于原来的assign(还是适用于OC对象)
* assign:适用于非OC对象类型
(2)ARC的判断准则:只要没有强指针指向对象,就会释放对象。
(3)ARC的特点
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property (nonatomic, weak) Dog *dog;
@property (nonatomic, strong) NSString *name;
@end
/* 对象一被创建出来就被释放了(错误写法)
__weak Person *pp = [[Person alloc] init];
*/
__strong Person *p = [[Person alloc] init];
__weak Person *p2 = p;
p = nil; // 没有强指针指向对象,对象之后被释放
// 弱指针指向的对象都不存在了,弱指针被清空,不会发生野指针
p2 = nil; // 弱指针不能决定是否释放对象<span style="font-family:SimHei;font-size:18px;">
</span>
2. ARC的转换功能
要想将非ARC的代码转换为ARC的代码,有2种方式:
1》使用Xcode的自动转换工具
* Build Settings下选上Warnings中的Other Warning Flags 为 -Wall,这样编译器就会检查所有可能的警告,有助于我们避免潜在的问题;
* Build Options下面的Run Static Analyzer选项也最好启用,这样每次Xcode编译项目时,都会运行静态代码分析工具来检查我们的代码;
* 设置"Objective-C Automatic Reference Counting"选项为YES,不过Xcode自动转换工具会自动设置这个选项,这里只是说明一下如何手动设置;
* Edit -> Refactor -> Convert to Objective-C ARC.. -> Check -> NEXT -> Save.
2》手动设置某些文件支持ARC
项目参数的Build Phases -> Compile Sources -> 修改Compiler Flags:
用别人的非ARC框架时:
如果.m 文件不是ARC文件了,那么就告诉编译器,这个项目不是ARC的 (-fno-objc-arc);
如果项目默认情况下就不是ARC,想让某个文件是ARC (-fobjc-arc)。
3. ARC的循环功能
循环引用解决方案:
ARC:
一端用strong,一端用weak
非ARC:
一端用retain,一端用assign
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog;
@end
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
@property (nonatomic, weak) Person *person;
@end