从零开始——我的OC学习之路

2018-05-23 学习如何使用复合

2018-05-23  本文已影响4人  肠粉白粥_Hoben

一.书上的例子

这次书上是用Car和Engine、Tire的故事来介绍复合。
所谓复合,就是组合多个对象,使整个类和其子类能够正常分工协作。

1.description

OC里面有个方法叫- (NSString * ) description,即可以实现对自己的描述,重写里面的方法,只需要return @(NSString *)即可。
我们来看看Tire里面的description。

#import <Foundation/Foundation.h>

@interface Tire : NSObject

@end
#import "Tire.h"

@implementation Tire
- (NSString *)description
{
    return (@"I am a tire!");
}
@end

不难发现,在.h文件里面并没有定义description这个方法,因为这是OC自带的一个方法,只需要在.m文件输入description关键词即可修改自己想要的描述。

2.用init方法初始化

在Car.h里面定义自己的轮子和引擎。

@interface Car : NSObject
{
    Engine *engine;
    Tire *tires[4];
}

在Car.m里面初始化

- (id)init
{
    self = [super init];
    if (self) {
        engine = [Engine new];
        tires[0] = [Tire new];
        tires[1] = [Tire new];
        tires[2] = [Tire new];
        tires[3] = [Tire new];
    }
    return self;
}

这里init也是个自带的方法,至于[super init],即要超类(这里是NSObject)完成所需的一次性初始化,init方法返回的值(id型数据,即泛型对象指针,在Java里面叫Object)描述了被初始化的对象。

3.用set方法和get方法初始化

上文是创建了一台车,但是是要车自己造轮子和引擎,以现在的AI水平应该还不能达到这一点,所以,我们必须提供一个set方法和一个get方法,要“人”造车,而不是“车”造车。
来活学活用一下,昨天刚学的@property可不能就这样丢掉。
在Car.h加上set、get函数的定义。

@property (nonatomic, assign) Engine *engine;

-(void) setTire: (Tire *) tire
        atIndex: (int) index;

-(void) print;

-(Tire *) tireAtIndex: (int) index;

数组尽量还是自己自定义一个set方法吧,这语法看得我有点迷= =
来默写一遍:

-(void) setTire: (Tire*) tire 
        atIndex: (int) index

再默写一遍:

-(void) setTire: (Tire *) tire
        atIndex: (int) index

然后就可以实现了,在Car.m文件实现这些方法:

@synthesize engine = _engine;

-(void) setEngine:(Engine *)engine
{
    _engine = engine;
}

-(Engine*) engine
{
    return _engine;
}

-(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;
}

-(Tire *) tireAtIndex:(int)index
{
    if (index < 0 || index > 3) {
        NSLog(@"bad index (%d) in tireAtIndex", index);
        exit(1);
    }
    return (tires[index]);
}

注意两点:
1.有关数组的都要防止错误的输入导致的溢出问题。
2.get方法是不加get在前面的,Java里面是getObject,但是在OC,get方法是object,不接受反驳,语法习惯问题。
最后完善一下print方法:

- (void) print
{
    NSLog(@"%@", _engine);
    for (int i = 0; i < 4; i++) {
        NSLog(@"%@", tires[I]);
    }
}

4.main函数的调用

关键点来了!再次要面对很迷的OC初始化的语法,来看看怎么搞:

int main(int argc, char * argv[]) {
    //造新车
    Car *car = [Car new];
    //装引擎
    Engine *engine = [Engine new];
    [car setEngine: engine];
    //装轮子
    for (int i = 0; i < 4; i++) {
        Tire *tire = [Tire new];
        [car setTire:tire atIndex:i];
        NSLog(@"Setting tire No.%d", i);
    }
    //看效果
    [car print];
    
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

可以看到,一个用户调用了main创建了一个新的Car、Engine和四个Tires,再用set方法把Engine和Tires装到车上。
车倒是装好了,但是如果我要把Engine和Tire拿出来检查一下该怎么办呢,这就要用到我们之前定义的get方法了:

    //看引擎
    NSLog(@"Getting engine...");
    NSLog(@"%@", [car engine]);
    //看轮子
    NSLog(@"Getting tires...");
    for (int i = 0; i < 4; i++) {
        NSLog(@"%@", [car tireAtIndex: I]);
    }

说实话,OC的get方法不加get是个很明智的选择,你看这一句[car engine]就很简洁,对于我们【获得的目的】一目了然,所以我觉得OC也是一个很好看的语法,甚至有点爱上了她~

5.给汽车换上新装备(继承的复习)

现在我们有最新的引擎slant-6和最新的轮子,来让我们的跑车更酷炫吧!
首先slant-6是个引擎,那么我们就让他继承引擎。

#import <Foundation/Foundation.h>
#import "Engine.h"

@interface Slant6 : Engine

@end

重写一下描述:

- (NSString *)description
{
    return (@"I am Slant6, a new engine!");
}

轮子哥也同理,所以在这里就不赘述了。
好了,main函数里面已经初始化了原始装备了,怎么装上新的装备呢?没错,就是set方法!

    //换新引擎
    Slant6 *slant = [Slant6 new];
    [car setEngine: slant];
    
    //换新轮子
    for (int i = 0; i < 4; i++) {
        AllWeatherRadial *newTire = [AllWeatherRadial new];
        NSLog(@"Setting tire No.%d", i);
        [car setTire: newTire atIndex: I];
    }
    
    //看效果
    [car print];

自此,我们的跑车已经是最新版本,复合的学习也告一段落啦!

6.总结与复习

继承复合是一对好基友,继承是"is a",复合是"has a"
slant-6 ''is a'' Engine,所以要继承Engine;Car "has a" Engine,所以要用复合。
在解决一个问题的时候,一定要分清楚到底是什么关系,才用到相关的知识,如果让Car去继承Engine,那真的就贻笑大方了。

二.学习文件间的依赖

因为之前是先看这章的一部分来实现不同类的封装的,所以就简单看了一下,还是有点知识有用场的。

1.@class

如果是Car复合了Engine的话,那么这两句话是等价的:
1)在Car.h定义 @class Engine,同时在Car.m定义import "Engine.h"
2)在Car.h定义 import "Engine.h"。
虽然第二种相对来说比较简单,但是,因为如果Car和Engine相互依赖(或者更复杂的循环依赖)的话,那么IDE会报错的,所以还是用第一种比较好。
不信?(我也不太相信)来做个试验,我们来看看第二种到底会发生什么情况:
在Engine.h定义:

#import <Foundation/Foundation.h>
#import "Car.h"
@class Car;
@interface Engine : NSObject

@end

在Car.h定义:

#import <Foundation/Foundation.h>
#import "Engine.h"

接下来发生的bug出乎我的意料:


image.png

可怕,这个错误如果真的出现在实际编程的话,是很难找到bug的!
所以乖乖地用第一种方法吧,虽然有点繁琐,但是保证不出bug,什么?又不信?(我也不太相信),做个试验呗:
现在Engine.h依旧是:

#import <Foundation/Foundation.h>
#import "Car.h"
@class Car;
@interface Engine : NSObject

@end

但是我把Car.h改成了这样:

#import <Foundation/Foundation.h>
//#import "Engine.h"
@class Engine;
@class Tire;
@class Slant6;
@class AllWeatherRadial;

跑一跑,看到这个锤子没有!成功了,哈哈


image.png

不过main函数可就不能仅仅包一个Car.h就完事了,现在改成@class命名方法的话,我们需要包所有用上的头文件。

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Car.h"
#import "Engine.h"
#import "Tire.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"
上一篇 下一篇

猜你喜欢

热点阅读