2018-05-24 学习对象初始化

作者: 肠粉白粥_Hoben | 来源:发表于2018-05-24 20:04 被阅读8次

    一.生命周期

    一个对象的生命周期有诞生(通过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;
    }
    

    成功啦!

    相关文章

      网友评论

        本文标题:2018-05-24 学习对象初始化

        本文链接:https://www.haomeiwen.com/subject/uhojjftx.html