My question is about a complex use case of generics in Swift. I'll try to explain as clearly as possible.
User of my iOS chat app can send different type of messages : text, images, gif, questions etc. The class design of the data holding these messages is :
Message that hold info that all type of message have (date, sender etc)Message, like TextMessage or ImageMessage that hold the specific info for each type of message (fileName for ImageMessage, text for TextMessage etc.)When a message is retrieved from server, it has a JSON prop called service, which I decode to a case of a Service enum. Each Service case has (1) one data class, and (2) one cell class. The data class is used to store the right T:Message class, the cell class is prompted by the collectionView to know which type of cell it needs to dequeue depending on the message service. Here is my Service enum :
public enum Service : String, CaseIterable
{
case CHAT = "CHAT"
case IMAGE = "IMAGE"
// ...
public func getSubclass() -> Message.Type {
switch(self){
case .TEXT : return TextMessage.self
case .IMAGE : return ImageMessage.self
// ...
}
}
/* problem here */
public func getCellClass <T> () -> MessageCollectionViewCell<T>.Type {
switch(self){
case .CHAT : return MessageCollectionViewCellChat.self
case .SENDING : return MessageCollectionViewCellChat.self
}
}
}
The class design for the cells is :
MessageCollectionViewCell<T:Message> that create the actual bubble view, with the date under it. A function recycle is declared in this class, to be overriden by final sub cells.MessageCollectionViewCellText, MessageCollectionViewCellImage etc., children of MessageCollectionViewCell<T:Message>. T needs to be the data type associated with the cell. They override the method recycle() so each type of the message can recycle in its own way (changing the text in the label for TextMessages, change the bitmap in the ImageView for ImageMessage..)Here is the code, so it's more clear :
class MessageCollectionViewCell<T:Message> : UICollectionViewCell {
// sub cells needs to override this identifier (used by the CollectionView to register cell)
class var cellIdentifier:String { return "MessageCollectionViewCell" }
init() {
// init chat bubble UI here..
}
public func recycle(withMessage: T) {
// UI actions to recycle the message that are common to all messages type ..
}
}
class MessageCollectionViewCellText : MessageCollectionViewCell<TextMessage>
{
override class var cellIdentifier:String { return "MessageCollectionViewCellText" }
let label = UILabel()
override init(frame:CGRect) {
super.init(frame:frame)
// create bubble content (put a label in the bubble, in this particular case of TextMessage)
}
override func recycle(message msgText: TextMessage){
// do all the things that are common to all message types (change date label for example)
super.setMessage(message: msg)
// specific things to do for TextMessage
label.text = msgText.text
}
}
The problem is the function returning the cell type in Service enum. In my Android app (Java), the return type is : Class<? extends MessageCollectionViewCell<?>>. Here, for the code I shared of getCellClass function above, Swift says "Cannot convert return expression of type 'MessageCollectionViewCellText.Type' to return type 'MessageCollectionViewCell<T>.Type'"
I also tried this :
// MT for Message Type, CT for Cell Type
public func getCellClass<MT:Message, CT:MessageCollectionViewCell<MT>> () -> CT.Type
{
switch(self) {
case .CHAT : return MessageCollectionViewCellChat.self as! CT.Type
case .IMAGE : return MessageCollectionViewCellImage.self as! CT.Type
}
}
It compiles properly without warning, but the app crashes because it cannot cast to CT.Type. Has someone an idea to implement this function ?
Thanks for reading
Instead of working with types the possible solution is to work with protocols, and then it is possible to avoid generics type mismatch problem in your code, like (with some replications)
public protocol Message {}
public struct TextMessage: Message {}
public struct ImageMessage: Message {}
public protocol MessageCollectionViewCell {}
public class MessageCollectionViewCellChat: MessageCollectionViewCell {}
public enum Service : String, CaseIterable
{
case CHAT = "CHAT"
case IMAGE = "IMAGE"
// ...
public func getMessage() -> Message {
switch(self){
case .CHAT : return TextMessage()
case .IMAGE : return ImageMessage()
// ...
}
}
/* problem here */
public func getCell() -> MessageCollectionViewCell {
switch(self){
case .CHAT : return MessageCollectionViewCellChat()
case .IMAGE : return MessageCollectionViewCellChat()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With