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

DoS*_*DoS 0 cocoa-touch objective-c

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

@interface Vehicle : NSObject

@property (nonatomic) int wheels;

- (id)initWithNumberOfWheels:(int)wheels;

@end
Run Code Online (Sandbox Code Playgroud)

从那里我为同一个"汽车"和"卡车"创建一些子类,为课程提供更多细节.

@interface Car : Vehicle

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

@end
Run Code Online (Sandbox Code Playgroud)

和...

@interface Truck : Vehicle

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

@end
Run Code Online (Sandbox Code Playgroud)

所以这里有趣的地方.我想创建另一个分配这些对象的类,但我希望在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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

编辑:我添加了几个额外的片段,以显示我尝试过之前甚至发布这个无效的问题.

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

id mySomethingObject = [[SomeClass alloc] init...];
[mySomethingObject setPropertyMethod]...; //sets value
[mySomethingObject propertyMethod]...; //gets value
Run Code Online (Sandbox Code Playgroud)

而不是试图使用......

mySomethingObject.property = ; //set
mySomethingObject.property; //get
Run Code Online (Sandbox Code Playgroud)

正如在正确答案中所述,如果您分配了"id"的课程没有响应该方法,您的课程将会崩溃.

CRD*_*CRD 5

你似乎混淆了许多问题.

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

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

在您的示例中,这意味着您不能直接引用Car由类型变量引用的对象的属性Vehicle *,即使您知道引用的对象是Car- 在编译时,所有已知的是它是a 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) ...
Run Code Online (Sandbox Code Playgroud)

要在没有中间变量的情况下执行此操作,请使

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

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

作为最后一个例子,你写这样的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;
}
Run Code Online (Sandbox Code Playgroud)

HTH

附录

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

首先,在Road你声明myRide是类型id:

@interface Road : NSObject

@property id myRide;
Run Code Online (Sandbox Code Playgroud)

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

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

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

   myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   [myRide setIs4x4:YES];
}
Run Code Online (Sandbox Code Playgroud)

如果您进行调用,例如[myRide setIs4x4:YES]并且myRide正在引用某个Car对象,那么您将收到运行时错误.

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

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