@property的objective-c“generic”类型,用于多个子类

时间:2013-09-06 18:40:38

标签: objective-c cocoa-touch

我不确定最有说服力的说法,但我会尽力而为。我创建了一个自定义类,它是一个具有一些属性的通用对象。我创建了几个子类来扩展它并使它们比超类更具体。所以为了举例,我会抛出一些通用的示例代码,这些代码可能是也可能不是正确的语法,只是为了说明我想要完成的事情。

@interface Vehicle : NSObject

@property (nonatomic) int wheels;

- (id)initWithNumberOfWheels:(int)wheels;

@end

从那里我为同一个“汽车”创建了一些子类。 “卡车”,为班级提供更多细节。

@interface Car : Vehicle

@property (nonatomic) BOOL convertible;
@property etc...

@end

和...

@interface Truck : Vehicle

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;

@end

所以这里有趣的地方。我想创建另一个分配这些对象的类,但我希望在init方法中确定车辆的“类型”,但使用相同的@property变量。例如(同样,这只是为了给出可视化表示的所有垃圾代码)

Road.h

#import "Car.h"
#import "Truck.h"

@interface Road : NSObject

@property (strong, nonatomic) NotSureWhatToUseHereToMakeThisWork *myRide;
// doesn't work: @property (strong, nonatomic) id myRide; 
// doesn't work: @property (strong, nonatomic) Vehicle *myRide; 


- (id)initWithCars;
- (id)initWithTrucks;

@end

Road.m

@implementation Road

- (id)initWithCars
{
//standard init code...

    myRide = [[Car alloc] initWithNumberOfWheels:4];
    myRide.convertable = NO;
}

- (id)initWithTrucks
{
//standard init code...

    myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
//yes, some trucks have more than 4 wheels

    myRide.is4x4 = YES;
}

@end

底线是如果我在@property中使用超类,它显然不会获得子类属性。基本上我想把所有这些保持为通用和可重用的。自从为汽车和汽车制造一个特殊的“公路”课程以来,它就没有了。一个用于卡车。毕竟,道路是一条道路。无论如何我还在做什么?有没有更好的方法来做这样的事情?主要目的是使对象仅为特定情况继承特定属性。我不想制作额外的@properties的原因是我不希望那些可见,如果它们不适用于这种情况。

编辑: 我添加了几个额外的片段,以显示我尝试过之前甚至发布不起作用的问题。

回答:正确的“答案”,如果有人好奇,请参阅CRD在“附录”中的回复。这工作的原因是类型“id”只能调用方法而不会继承属性。所以相反的解决方法(我这样说,因为我正在研究这个,得出结论这不是很好的编程,如果可能的话应该避免)将使用访问器方法来获取/设置属性。

id mySomethingObject = [[SomeClass alloc] init...];
[mySomethingObject setPropertyMethod]...; //sets value
[mySomethingObject propertyMethod]...; //gets value

而不是尝试使用......

mySomethingObject.property = ; //set
mySomethingObject.property; //get

如正确答案中所述,如果您分配了“id”的课程没有响应该方法,您的程序将崩溃。

5 个答案:

答案 0 :(得分:5)

您似乎对许多问题感到困惑。

首先是实例的类型与保存对实例的引用的变量的类型。创建对象时,它是某种特定类型,并且类型更改[*]。变量也有一个类型,并且也不会改变。子类型/继承允许您将某个类型的对象T的引用存储在某个其他类型的变量S中,前提是ST的超类型}。

其次是静态与动态类型。虽然Objective-C使用动态类型,但在某些操作中使用的实际对象类型是在运行时确定的,编译器本身使用静态类型,其中类型在编译期间确定,以帮助编写正确的程序。有时编译器静态检查只会产生警告,但在其他情况下,编译器将拒绝编译某些东西。特别是属性引用是基于静态类型编译的。

在您的示例中,这意味着您不能直接Car类型的变量引用的对象上引用Vehicle *的属性,即使您知道引用的对象是a Car - 在编译时,所有已知的是Vehicle

解决方案是首先测试引用对象的实际类型,然后引入更精确类型的局部变量,或者使用大量的强制转换。例如:

// (a) create an object of type Car (for a Reliant Robin ;-))
// (b) create a variable of type Car and store in it a reference to the created Car
Car *myCar = [[Car alloc] initWithNumberOfWheels:3];

// Create a variable of type Vehicle and store in it the reference stored in myCar
// The created instance is *still* a Car
Vehicle *myRide = myCar;

// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    // create a variable of type Car to avoid having to continually cast myRide
    Car *myCarRide = (Car *)myRide; // due to if above we know this cast is valid

    if (myCarRide.isConvertible) ...

要在没有中间变量的情况下执行此操作,请使用强制转换:

...
// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    if (((Car *)myCarRide).isConvertible) ...

这说明了为什么中间变量方法更好!

作为最后一个例子,您可以像这样编写initWithTrucks方法:

- (id)initWithTrucks
{
   //standard init code...

   Truck *myTruck = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   myTruck.is4x4 = YES;

   // Store the reference to the created Truck in myRide
   myRide = myTruck;
}

HTH

<强>附录

从您的评论中看起来您可能正在寻找动态类型,并且不希望编译器执行任何静态类型。这是(部分)支持,但不使用属性的点表示法 - 您必须直接使用getter和setter方法。

首先,在Road中,您声明myRideid类型:

@interface Road : NSObject

@property id myRide;

id类型意味着两件事(a)任何对象引用和(b)不静态检查对象上存在的方法。 但是编译器必须知道某些对象上存在一个被调用的方法,并且它仍然会对该方法的参数执行静态类型检查 - 因此它不是完整的动态类型(但是你可以传递id类型的表达式或声明你的方法来接受id类型的参数当然......)。

其次,您对属性的所有引用都直接使用getter或setter方法,并且不使用点符号(对于非属性方法,您只需像往常一样调用它们)。 E.g:

- (id)initWithTrucks
{
   //standard init code...

   myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   [myRide setIs4x4:YES];
}

如果您拨打[myRide setIs4x4:YES]myRide之类的电话引用Car对象,则会出现运行时错误。

一般建议尽可能多地使用编译器的静态类型检查。

[*]我们将忽略任何运行时魔法,有龙。在普通代码中,对象永远不会改变类型。

答案 1 :(得分:3)

您必须使用“Vehicle”类型,然后使用“Truck”或“Car”投射对象以获取特定属性

答案 2 :(得分:3)

最通用的架构是创建一个VehicleProtocol,任何类都可以实现。你仍然可以拥有一个实现协议和子类的Vehicle类(类似于实现NSObject协议的NSObject),或者让独立的类实现它。这条路将有一个属性@property (strong) id<VehicleProtocol> myRide


后一种架构的完整示例:没有车辆超类,但都是VehicleProtocol

#import <Foundation/Foundation.h>


@protocol VehicleProtocol <NSObject>
@property (nonatomic) NSUInteger wheels;
@end

@interface Car : NSObject <VehicleProtocol>
@property (nonatomic) BOOL convertible;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;
@end

@implementation Car
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end


@interface Truck : NSObject <VehicleProtocol>

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;

@end

@implementation Truck
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end




@interface Road : NSObject
@property (strong) id<VehicleProtocol> myRide;
@end

@implementation Road
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSArray *vehicles =  @[[[Car alloc] initWithNumberOfWheels:4], [[Car alloc] initWithNumberOfWheels:3], [[Truck alloc] initWithNumberOfWheels:10]] ;

        for (id v in vehicles) {
            if ([v isKindOfClass:[Truck class]]) {
                [v setIs4x4:YES];
            }
        }


        Road *road = [[Road alloc] init];
        road.myRide = vehicles[0];
        NSLog(@"%@", road.myRide);


        road.myRide = vehicles[2];
        NSLog(@"%@", road.myRide);

        NSObject *obj = [[NSObject alloc] init];
        road.myRide = obj; // warning in this line
        NSLog(@"%@", road.myRide);


    }
    return 0;
}

当然,它可能有比“经典子类化”更多的代码行,但依赖性较少。相反,课程同意履行合同。这里的合同只要求对象有任意数量的轮子。

请注意,我创建了一条道路并首先分配了一辆汽车而不是一辆卡车(我还展示了如何通过-isKindOfClass:识别汽车和卡车),两者都没有任何警告或错误,因为汽车和卡车完全满足合约。比我分配一个简单的NSObject。这里编译器发出警告,因为他认识到NSObject没有实现协议。虽然它不是编译器错误,但编译器不知道,如果您将在该对象上使用任何特定于协议的方法。

如果将普通的NSObject分配给myRide,则该行

NSLog(@"%@ %ld", road.myRide, (unsigned long)road.myRide.wheels);

将导致运行时崩溃(因为NSObject实例不响应轮) - 但在编译时它甚至不会触发警告。

答案 3 :(得分:1)

将汽车存储在Vehicle *类型的变量中不会删除对象的汽车属性 - 汽车仍然可以正常访问其所有状态 - 这只是意味着您无法通过那个变量。

但这是基本想法,不是吗?您希望这个类能够处理所有车辆,这就是它正在做的事情 - 它只向您展示所有车辆可用的功能。因此,您可以拥有与Vehicle类中的通用Vehicle进行交互所需的接口,并在子类中实现这些方法,以便在调用时执行类适当的行为,并且一切都会正常工作。

如果问题是专门创建您希望从该点开始一般处理的特定类的新实例,则可以使用静态类型的局部变量,并在设置后分配给通用类型变量。

例如,假设我们有一个人类玩家和AI玩家的游戏,人类玩家可以通过让AI玩家承受更多伤害来给自己一个优势。我们可以这样做:

@interface Combatant : NSObject
@property(nonatomic, strong) NSString *name;
@property(nonatomic) int hitPoints;

- (void)takeDamage:(int)damageAmount;
@end

@implementation Combatant
- (void)takeDamage:(int)damageAmount {
    self.hitPoints -= damageAmount;
}
@end

@interface HumanCombatant : Combatant
@property(nonatomic, strong) UserID *userID;
- (id)initWithUserID:(UserID *)theID;
@end

@implementation HumanCombatant
- (id)initWithUserID:(UserID *)theID {
    if ((self = [super init])) {
        _userID = [theID retain];
    }
}

- (void)takeDamage:(int)damageAmount {
    [super takeDamage: damageAmount];
    NSLog(@"Human took %d damage, has %d health remaining", damageAmount, self.hitPoints);
}
@end

@interface AICombatant : Combatant
@property(nonatomic) double damageMultiplier;
@end

@implementation AICombatant
- (void)takeDamage:(int)damageAmount {
    int modifiedDamage = damageAmount * self.damageMultiplier;
    [super takeDamage: modifiedDamage];
    NSLog("AI took %d damage, has %d health remaining", modifiedDamage, self.hitPoints);
}
@end

然后,在我们的大多数游戏代码中,我们可以使用变量类型为Combatant *,当您发送takeDamage:时,它会为战斗类型做正确的事情。 。我们调用对象Combatant *的外部代码将无法直接访问AICombatant的damageMultiplier属性,因为其他代码不知道Combatant是否是AICombatant,但该对象仍然具有该属性并将为其类正确运行。

答案 4 :(得分:0)

将其设为Vehicle*并使每个类实现type以返回表示类类型的常量。

@property (nonatomic, strong) Vehicle* yourRide;
...
if (yourRide.type == VehicleConstant_Truck) {
  Truck* yourTruck = (Truck*) yourRide;
  NSLog(@"This truck %s a 4x4", yourTruck.is4x4 ? "is" : "isn't");
}

为了让@vikingosegundo满意,另一种方法是:

if ([yourRide isKindOfClass:[Truck class]]) {

而不是上面的if语句。