Reputation: 386
I'm building a notification system for my app and I figured out a way to pass multiple types to my model.
I need to define a target, a second target and a third target in this model. These targets are different types according to the notification type. It looks like something like this:
class UserNotification<T1, T2, T3>: Codable {
let notifyType: String
let target: T1
let secondTarget: T2
let thirdTarget: T3
enum CodingKeys: String, CodingKey {
case notifyType = "notify_type"
case target
case secondTarget = "second_target"
case thirdTarget = "third_target"
}
It's working well as it is. The thing is I need to define a notification
variable in on of my views so I can pass notifications from my backend. This is where I'm stuck. How can I define my variable so I can cast types according to my notifyType
value ?
class NotificationBox: UITableViewCell, CellElement {
private var notification: UserNotification<?, ?, ?> // This is where I'm stuck
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
func setResource(resource: Codable) {
var notification = resource as! UserNotification<Any, Any, Any>
switch(notification.notifyType) {
case "get_badge":
self.notification = notification as! UserNotification<Any, Badge, Any>
case "group_reply":
self.notification = notification as! UserNotification<Message, Debate, Message>
case "level_unlock":
self.notification = notification as! UserNotification<User, Any, Any>
case "get_vote":
self.notification = notification as! UserNotification<Any, Debate, Any>
case "group_follow_argument":
self.notification = notification as! UserNotification<Message, Debate, Message>
case "user_follow_level_unlock":
self.notification = notification as! UserNotification<Any, Any, Any>
case "followed":
self.notification = notification as! UserNotification<Any, Any, Any>
case "group_invitation_new":
self.notification = notification as! UserNotification<Any, Any, Any>
default:
self.notification = notification as! UserNotification<Any, Any, Any>
}
configure()
}
Everything is working quite well excepting the variable definition. I don't know how to do it. I tried to define it as:
private var notification: UserNotification<Any, Any, Any>
But it gives me this error:
Cannot assign value of type 'UserNotification<Any, Badge, Any>' to type 'UserNotification<Any, Any, Any>'
Upvotes: 2
Views: 595
Reputation: 271150
You should use definitely use an enum with associated values here. As Schottky said in their answer, you'll need a custom Codable
implementation. Here's an example, implementing just 4 of the cases, assuming that User
, Badge
, Message
, Debate
are all Codable
.
enum UserNotification: Codable {
case getBadge(Badge)
case groupReply(Message, Debate, Message)
case levelUnlock(User)
case userFollowLevelUnlock
var notifyType: String {
switch self {
case .getBadge:
return "get_badge"
case .groupReply:
return "group_reply"
case .levelUnlock:
return "level_unlock"
case .userFollowLevelUnlock:
return "user_follow_level_unlock"
}
}
enum CodingKeys: String, CodingKey {
/*
When decoding/encoding, use convertFromSnakeCase/convertToSnakeCase to convert the key names to/from snake case
No need to hard code them here
e.g.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
*/
case notifyType
case target
case secondTarget
case thirdTarget
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(notifyType, forKey: .notifyType)
switch self {
case .getBadge(let badge):
// you can also encode the unused targets here if your backend needs them
try container.encode(badge, forKey: .secondTarget)
case .groupReply(let msg1, let debate, let msg2):
try container.encode(msg1, forKey: .target)
try container.encode(debate, forKey: .secondTarget)
try container.encode(msg2, forKey: .thirdTarget)
case .levelUnlock(let user):
try container.encode(user, forKey: .target)
case .userFollowLevelUnlock:
break // nothing more to encode in this case
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch try container.decode(String.self, forKey: .notifyType) {
case "get_badge":
self = .getBadge(
try container.decode(Badge.self, forKey: .secondTarget)
)
case "group_reply":
self = .groupReply(
try container.decode(Message.self, forKey: .target),
try container.decode(Debate.self, forKey: .secondTarget),
try container.decode(Message.self, forKey: .thirdTarget)
)
case "level_unlock":
self = .levelUnlock(
try container.decode(User.self, forKey: .target)
)
case "user_follow_level_unlock":
self = .userFollowLevelUnlock
default:
throw DecodingError.dataCorruptedError(forKey: .notifyType, in: container, debugDescription: "Unknown notifyType")
}
}
}
This way, the notification
property can just be:
private var notification: UserNotification
and setResource
is trivial.
Upvotes: 2
Reputation: 2014
Responding to the comments: I think that a better approach could be to use an enum with associated data; something like this:
enum Notification {
case badge(Badge)
case groupReply(message: Message, debate: Debate, reply: Message)
case followed
// ...
}
If you want to know which notification you have (and also get the associated data), you can do it like so:
switch notification {
case .badge(let badge):
// badge is now a let of type Badge
case .groupReply(let message, _, let reply):
// you typically ignore unrelated stuff with _
case .followed:
}
This has the downside that currently you have to provide your own Codable
conformance. This feature has been implemented for the next update of swift, however.
Upvotes: 1