2018-05-24 学习对象初始化
一.生命周期
一个对象的生命周期有诞生(通过alloc或者new方法实现)、生存(接收消息和执行操作)、交友(借助方法的组合和参数)以及死亡(被释放)。释放之后,原来占用的内存可供新对象使用。
1.引用计数
当某段代码需要访问一个对象的时候,该对象的保留计数器加1,表示“我要访问该对象”;当这段代码结束对这个对象的访问时,该对象的保留计数器减1,表示它不再访问该对象。当保留计数器的值为0的时候,表示不再有代码访问该对象了,因此对象被销毁,其占用的内存会被回收。
当使用alloc、new方法或者通过copy消息创建一个对象的时候,对象的保留计数器设置为1。要增加对象的保留计数器值,可以给对象发送一条retain消息。要减少对象的保留计数器值,可以给对象发送一条release消息。
2.自动释放
Cocoa有个概念叫做自动释放池,没错就是那个NSAutoreleasePool。
当我们申请一个对象的时候,可以用alloc关键词,在ARC的xcode是不用自己手动调用release方法的,因为自动释放池会帮我们自动释放掉。
- (NSString *)description
{
NSString *description;
description = [[NSString alloc] initWithFormat:@"I am %d years old", 4];
return description;
}
3.内存管理
以下规则只针对ARC:
- 当使用new、alloc或者copy创建一个对象的时候偶,该对象的保留计数器值为1,当不再使用该对象的时候,要向该对象发送一条release或autorelease消息,这样,该对象将会在其使用寿命结束的时候被销毁。
- 当通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,不需要执行任何操作来确保该对象被清理。如果打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它。
- 如果保留了某个对象,需要最终释放或自动释放该对象。必须保持retain方法和release方法使用次数相等。
4.垃圾回收
关于ARC和MRC的区别,具体参考该博文:
https://www.jianshu.com/p/48665652e4e4
OC2.0引入了自动内存管理机制,也称垃圾回收。使用ARC后,系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象,编译器会管理好对象的内存,会在何时的地方插入retain, release和autorelease,通过生成正确的代码去自动释放或者保持对象。我们完全不用担心编译器会出错。
二.初始化对象
Cocoa的初始化惯例其实是用[[类名 alloc] init]而不是[类名 new],即便这两种方法是等价的。但是为什么要用alloc+init呢,看看这篇博文就知道了。
https://blog.csdn.net/xf931456371/article/details/50321357
原来是因为这个原因:
如果要用new方法去初始化的话,那么调用类的初始化就只能在类的init函数定义了。
但是如果用alloc+init的话,初始化的类型可以千变万化,你可以改init的名字,也可以改传入init的参数。
Person.h如下:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, assign) NSString* name;
@property (nonatomic, assign) int age;
-(id) initWithName: (NSString *) name andAge: (int) age;
@end
Person.m如下:
#import "Person.h"
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
-(id) initWithName:(NSString *)name andAge:(int)age
{
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}
@end
Main.m如下:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person;
person = [[Person alloc] initWithName: @"Hoben"
andAge: 5];
NSLog(@"%@'s age is %d", [person name], [person age]);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
1.分配与初始化
分配是一个新对象诞生的过程,从操作系统获得一块内存并将其指定为存放对象的实例变量的位置。向某个类发送alloc消息的结果,就是为该类分配一块足够大的内存,以存放该类的全部实例变量。
同时,alloc方法还顺便将这块内存区域全部初始化为0.所有BOOL类型变量被初始化为NO,所有int类型变量被初始化为0,所有float类型变量被初始化为0.0,所有指针被初始化为nil。
但是,一个刚刚分配的对象并不能立即使用,需要先初始化该对象,然后才使用它。OC拆分成了两个明确的步骤:分配和初始化,因此,只分配而不初始化的操作是错误的,以下是个反例:
Car *car = [Car alloc];
这个代码能运行,但可能会出现奇奇怪怪的bug。
2.初始化
初始化,即从操作系统取得一块内存,准备用于存储对象。init方法几乎总是返回它们正在初始化的对象。初始化应该要嵌套使用,将alloc和init放在同一行内,如:
//正确的代码
Car *car = [[Car alloc] init];
//错误的代码
//Car *car = [Car alloc];
//[car init];
这种嵌套是很必要的,因为初始化方法返回的对象可能和分配的对象不一样。
3.编写初始化方法
回到我们之前写过的init方法:
- (id)init
{
self = [super init];
if (self) {
// doSomething;
}
return self;
}
self = [super init],其作用是使超类完成它们自己的初始化工作。从根类NSObject继承的类调用超类的初始化方法,可以使NSObject执行所需的任何操作,以便对象能够响应消息并处理保留计数器。而从其他类继承的类调用超类的初始化方法,可以使子类有机会实现自己的全新的初始化。
实例变量所在的内存位置到隐藏的self参数之间的距离是固定的。如果从init方法返回一个新对象,则需要更新self,以便其后的任何实例变量的引用可以被映射到正确的内存位置。所以要用self = [super init]进行赋值。
如果在初始化对象的时候出现问题,则init方法可能返回nil。如使用init方法接收一个URL并使用网站的图像文件初始化一个图像对象。如果网络故障导致init方法返回nil,则这个方法的主体代码将不会执行。
最后,return (self)即将[super init]的返回值赋给self。
4.初始化的方式
在我之前做的例子中,初始化有两种方法:车造车、人造车
第一种方法即在Car.h文件中的init函数创建了Engine对象和4个tire对象,最后在main.m文件中调用alloc和init,使车变得可以运行。
第二种方法则在Car.h中什么都不做,而是定义了一个set方法和一个get方法,在main.m文件中,先创建Engine和tire对象,再将它们装到Car中去。
相比之下,第一种方法更适用于即买即用,如果Car类的预期用途是创建和使用基本的car对象,那用第一种方法。
第二种方法更适用于面对不同种类的tire和Engine,这时候需要在main.m文件中定义不同的零件,把他们再装到Car上面去。
5.让初始化更便利
许多类都包含便利初始化函数,让初始化便利得更痛快一些。
NSString的初始化:
NSString *string = [[NSString alloc] initWithFormat: @"Yes"];
initWithContentsOfFile:
读取文件内容并使文件内容初始化一个字符串:
NSString *string = [[NSString alloc] initWithContentsOfFile: @"/tmp/words.txt"];
6.改造我的Car代码
之前用到的new是时候换换了,这次我们再给Tire加点属性:
//Tire.h
@property (nonatomic, assign) float pressure;
@property (nonatomic, assign) float treadDepth;
-(void) setPressure:(float)pressure;
-(void) setTreadDepth:(float)treadDepth;
//Tire.m
@synthesize treadDepth = _treadDepth;
@synthesize pressure = _pressure;
-(void) setTreadDepth:(float)treadDepth
{
_treadDepth = treadDepth;
}
-(void) setPressure:(float)pressure
{
_pressure = pressure;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"Tire!
Pressure: %.1f TreadDepth: %.1f", _pressure, _treadDepth];
}
- (instancetype)init
{
self = [super init];
if (self) {
_pressure = 34.0;
_treadDepth = 24.0;
}
return self;
}
Car *car = [[Car alloc] init];
Engine *engine = [[Slant6 alloc] init];
[car setEngine: engine];
for (int i = 0; i < 4; i++) {
Tire *tire = [[Tire alloc] init];
[tire setPressure: 24.0 + I];
[tire setTreadDepth: 35.0 + I];
[car setTire: tire atIndex: I];
}
[car print];
看到car的Tire数组有点不舒服,把它换成NSMutableArray:
//Car.h
@interface Car : NSObject
{
Engine *engine;
// Tire *tires[4];
NSMutableArray *tires;
}
//Car.m
- (instancetype)init
{
self = [super init];
if (self) {
tires = [[NSMutableArray alloc] init];
for (int i = 0; i < 4; i++) {
[tires addObject: [NSNull null]];
}
}
return self;
}
-(void) setTire:(Tire *)tire
atIndex:(int)index
{
if (index < 0 || index > 3) {
NSLog(@"bad index (%d) in setTire:atIndex", index);
exit(1);
}
// tires[index] = tire;
[tires replaceObjectAtIndex: index withObject: tire];
}
7.便利化初始函数遇上继承
把我们的Tire初始化改得更简洁一些吧:
//Tire.m
- (instancetype)init
{
self = [super init];
if (self) {
_pressure = 34.0;
_treadDepth = 24.0;
}
return self;
}
-(id) initPressure:(float)pressure andTreadDepth:(float)treadDepth
{
if (self = [super init]) {
_pressure = pressure;
_treadDepth = treadDepth;
}
return self;
}
-(id) initPressure:(float)pressure
{
if (self = [super init]) {
_pressure = pressure;
_treadDepth = 24.0;
}
return self;
}
-(id) initThreadDepth:(float)treadDepth
{
if (self = [super init]) {
_pressure = 24.0;
_treadDepth = treadDepth;
}
return self;
}
是的,我又加了一些init方法,来确保两个变量能得到初始化。
现在给我们的AllWeatherRadial添加点属性:
//AllWeatherRadial.h
#import <Foundation/Foundation.h>
#import "Tire.h"
@interface AllWeatherRadial : Tire
@property (nonatomic, assign) float rainHandling;
@property (nonatomic, assign) float snowHandling;
-(void) setRainHandling:(float)rainHandling;
-(void) setSnowHandling:(float)snowHandling;
再加个init:
- (instancetype)init
{
self = [super init];
if (self) {
_rainHandling = 23.7;
_snowHandling = 42.5;
}
return self;
}
在main里面init一下:
Car *car = [[Car alloc] init];
Engine *engine = [[Slant6 alloc] init];
[car setEngine: engine];
for (int i = 0; i < 4; i++) {
AllWeatherRadial *tire = [[AllWeatherRadial alloc] initPressure:53.0 andTreadDepth:66.0];
[car setTire: tire atIndex: I];
}
[car print];
猜猜会发生什么事?
。。。
我擦,为啥我辛辛苦苦在继承类里面初始化的值不见了?!
冷静冷静,我们看看main函数是怎么初始化的:
看到这行没有,没错,因为AllWeatherRadial里面没有initPressure函数!
那怎么解决呢?难不成每个继承的子类都要加上我之前定义的四种init方法?哇,如果有n个子类继承了m个方法的话,那不是凉了,m*n个方法,有得你写了。。
不要担心,OC爸爸有一招妙招:
观察到没有, initPressure:(float)pressure andTreadDepth:(float)treadDepth是四个方法里面普适性最强的一个了,其他三个方法都可以按照这个来改,于是,我们让Tire.m变成这般:
//Tire.m
-(id) initPressure:(float)pressure andTreadDepth:(float)treadDepth
{
if (self = [super init]) {
_pressure = pressure;
_treadDepth = treadDepth;
}
return self;
}
- (id)init
{
// self = [super init];
self = [self initPressure: 34 andTreadDepth: 20]
if (self) {
// _pressure = 34.0;
// _treadDepth = 24.0;
}
return self;
}
-(id) initPressure:(float)pressure
{
if (self = [self initPressure: pressure andTreadDepth: 20]) {
// _pressure = pressure;
// _treadDepth = 24.0;
}
return self;
}
-(id) initThreadDepth:(float)treadDepth
{
if (self = [self initPressure: 34 andTreadDepth: treadDepth]) {
// _pressure = 24.0;
// _treadDepth = treadDepth;
}
return self;
}
//AllWeatherRadial.m
-(id) initPressure:(float)pressure andTreadDepth:(float)treadDepth
{
self = [super initPressure: pressure andTreadDepth: treadDepth];
if (self) {
_rainHandling = 23.7;
_snowHandling = 42.5;
}
return self;
}
成功啦!