Swift alternative for heterogeneous collection of objects with "generic" contents.


(Mikhail Seriukov) #1

Hello everyone,
In objc there is quite common pattern when we use a base class with a type
property and concrete subclasses where type is uniquely identifying the
subclass.
We can then safely put subclasses objects into an array and then safely
downcast them when needed.
This kind of behaviour is commonly used in data sources implementations.

Like this:

typedef NS_ENUM(NSUInteger, CellDecriptorType) {
    CellDecriptorTypeUnknown,
    CellDecriptorType1,
    CellDecriptorType2
};

@interface BaseCellDescriptor : NSObject

@property (nonatomic, readonly) CellDecriptorType type;
@property (nonatomic, strong) id value;

@end

@interface CellDescriptor1 : BaseCellDescriptor
@end

@implementation CellDescriptor1

- (CellDecriptorType)type {
    return CellDecriptorType1;
}

- (NSString *)value {
return @"string value";
}

@end

@interface CellDescriptor2 : BaseCellDescriptor
@end

@implementation CellDescriptor2

- (CellDecriptorType)type {
    return CellDecriptorType2;
}

- (NSNumber *)value {
return @42;
}

@end

And somewhere later we do:

- (void)doWorkWithCellDescriptors:(NSArray<BaseCellDescriptor *>
*)descriptors {
    for (BaseCellDescriptor *descriptor in descriptors) {
        CellDecriptorType type = descriptor.type;
        switch(type) {
            case CellDecriptorType1: {
                CellDescriptor1 *aDescriptor = (CellDescriptor1
*)descriptor;
                NSString *value = aDescriptor.value;
                // Do something with value
                break;
            }
            case CellDecriptorType2: {
                CellDescriptor2 *aDescriptor = (CellDescriptor2
*)descriptor;
                NSNumber *value = aDescriptor.value;
                // Do something with value
                break;
            }
            case CellDecriptorTypeUnknown:
            default: {
                // Handle error
                break;
            }
        }
    }
}

I want to implement it swifty way. So the questions are:
0. Is it a bad practice to use this pattern and how we can avoid it?
1. Is it possible to avoid inheritance here and only use generic protocols
and how?
2. Is it possible to avoid downcasting if using this pattern in swift?

I've found the solution that seems to be a good example in this project
https://github.com/xmartlabs/Eureka.
They maintain both inheritance hierarchy and protocol hierarchy.

3. Is it a good practice to implement such tasks like in Eureka project,
what are the pros and cons of it?

Thanks for your time!


(Joe Groff) #2

If you have a fixed set of types here, and you want to switch over them in a type-safe way, the natural thing to do would be to use an enum with payloads:

enum BaseCellDescriptor {
  case type1(CellDescriptor1)
  case type2(CellDescriptor2)
}

func doWorkWithCellDescriptors(_ descriptors: [BaseCellDescriptor]) {
  for descriptor in descriptors {
    switch descriptor {
    case .type1(let aDescriptor):
      // aDescriptor has type CellDescriptor1 here
    case .type2(let aDescriptor):
      // aDescriptor has type CellDescriptor2 here
    }
  }
}

-Joe

···

On Jun 29, 2017, at 10:59 PM, Mikhail Seriukov via swift-users <swift-users@swift.org> wrote:

Hello everyone,
In objc there is quite common pattern when we use a base class with a type property and concrete subclasses where type is uniquely identifying the subclass.
We can then safely put subclasses objects into an array and then safely downcast them when needed.
This kind of behaviour is commonly used in data sources implementations.

Like this:

typedef NS_ENUM(NSUInteger, CellDecriptorType) {
    CellDecriptorTypeUnknown,
    CellDecriptorType1,
    CellDecriptorType2
};

@interface BaseCellDescriptor : NSObject

@property (nonatomic, readonly) CellDecriptorType type;
@property (nonatomic, strong) id value;

@end

@interface CellDescriptor1 : BaseCellDescriptor
@end

@implementation CellDescriptor1

- (CellDecriptorType)type {
    return CellDecriptorType1;
}

- (NSString *)value {
  return @"string value";
}

@end

@interface CellDescriptor2 : BaseCellDescriptor
@end

@implementation CellDescriptor2

- (CellDecriptorType)type {
    return CellDecriptorType2;
}

- (NSNumber *)value {
  return @42;
}

@end

And somewhere later we do:

- (void)doWorkWithCellDescriptors:(NSArray<BaseCellDescriptor *> *)descriptors {
    for (BaseCellDescriptor *descriptor in descriptors) {
        CellDecriptorType type = descriptor.type;
        switch(type) {
            case CellDecriptorType1: {
                CellDescriptor1 *aDescriptor = (CellDescriptor1 *)descriptor;
                NSString *value = aDescriptor.value;
                // Do something with value
                break;
            }
            case CellDecriptorType2: {
                CellDescriptor2 *aDescriptor = (CellDescriptor2 *)descriptor;
                NSNumber *value = aDescriptor.value;
                // Do something with value
                break;
            }
            case CellDecriptorTypeUnknown:
            default: {
                // Handle error
                break;
            }
        }
    }
}

I want to implement it swifty way. So the questions are:
0. Is it a bad practice to use this pattern and how we can avoid it?
1. Is it possible to avoid inheritance here and only use generic protocols and how?
2. Is it possible to avoid downcasting if using this pattern in swift?

I've found the solution that seems to be a good example in this project https://github.com/xmartlabs/Eureka.
They maintain both inheritance hierarchy and protocol hierarchy.