Swift: Make two types with the same shape conform to a common protocol
The name
properties of the generated structs have type Name
, not NameRepresenting
as required by the protocol. Covariant returns are not supported in Swift just yet :(
What you can do is to add an associated type requirement:
protocol UserRepresenting {
associatedtype Name : NameRepresenting
var email: String { get }
var name: Name { get }
}
This requires that the conformers to have a type that conforms to NameRepresenting
and is the type of the name
property.
However, now that it has an associated type requirement, you cannot use UserRepresenting
as the type of a variable/function parameter. You can only use it in generic constraints. So if you have a function that takes a UserRepresenting
, you need to write it like this:
func someFunction<UserType: UserRepresenting>(user: UserType) {
}
and if one of your classes/structs need to store a property of type UserRepresenting
, you need to make your class/struct generic too:
class Foo<UserType: UserRepresenting> {
var someUser: UserType?
}
This may or may not work for your situation. If it doesn't, you can write a type eraser:
struct AnyUserRepresenting : UserRepresenting {
var email: String
var name: Name
struct Name : NameRepresenting {
var givenName: String
var familyName: String
}
init<UserType: UserRepresenting>(_ userRepresenting: UserType) {
self.name = Name(
givenName: userRepresenting.name.givenName,
familyName: userRepresenting.name.familyName)
self.email = userRepresenting.email
}
}
Now you can convert any UserRepresenting
to this AnyUserRepresenting
, and work with AnyUserRepresenting
instead.
Type of a class which conforms to a protocol as parameter in a method swift
In your code target
contains a "runtime type object". And you cannot use such thing as a generic type argument.
You know you cannot do this:
func doStuff<C: MyProtocol>(target: C.Type) {
var c: target? //<- This is illegal. You cannot use `target` where a "type" is needed.
}
Why don't you write it as:
func doStuff<C: MyProtocol>(target: C.Type) {
SomeClass<C>.doSomething()
}
You can use it as:
myObj.doStuff(ConformingClass.self)
Check if two objects implement a Swift protocol and its associated type
It was trickier than I expected (so I deleted my previous post to avoid confusion) but I believe this should work for you:
protocol ViewModelContainerVC
{
mutating func setModel(_ :Any)
}
protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}
You can then use the ViewModelContainerVC for type casting and assignment:
if let container = container as? ViewModelContainerVC
{
container.setModel(model)
}
[EDIT] for future reference, here's the same thing with a Bool return for type compatibility:
protocol ViewModelContainerVC
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}
extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}
Which will allow a combined condition :
if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }
swift - Comparing structs that conform to a protocol
This topic is covered in the WWDC 2015 session video Protocol-Oriented Programming in Swift, and here is my attempt to apply that to your situation:
You define a protocol Shape
and a protocol extension methodisEqualTo:
:
protocol Shape {
func isEqualTo(other: Shape) -> Bool
}
extension Shape where Self : Equatable {
func isEqualTo(other: Shape) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
isEqualTo:
checks if the other element is of the same type (and compares them with ==
in that case), and returns false
if they
are of different type.
All types which are Equatable
automatically conform to Shape
,
so that we can just set
extension Point : Shape { }
extension Line : Shape { }
(Of course you can add other methods to Shape
which should be
implemented by all shape types.)
Now we can define ==
for shapes as
func ==(lhs: Shape, rhs: Shape) -> Bool {
return lhs.isEqualTo(rhs)
}
and voilà, points and lines can be compared:
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 1, y: 3)
let l1 = Line(points: [p1, p2])
let l2 = Line(points: [p1, p2])
print(p1 == p2) // false
print(p1 == l1) // false
print(l1 == l2) // true
Remark: You have ==
for the Shape
type now, but I haven't figured out yet to how make Shape
conform to Equatable
. Perhaps someone
else can solve that part (if it is possible).
Protocol type cannot conform to protocol because only concrete types can conform to protocols
Rather than protocols use generics.
Declare a simple function
func decodeStickers<T : Decodable>(from data : Data) throws -> T
{
return try JSONDecoder().decode(T.self, from: data)
}
T
can be a single object as well as an array.
In your structs drop the Sticker
protocol. You can also delete Equatable
because it's getting synthesized in structs.
public struct StickerString : Codable {
let fontName: String
let character: String
}
public struct StickerBitmap : Codable {
let imageName: String
}
To decode one of the sticker types annotate the type
let imageStickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}]
"""
let stickerData = Data(imageStickers.utf8)
let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)
and
let stringSticker = """
{"fontName":"Times","character":"}
"""
let stickerData = Data(stringSticker.utf8)
let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)
To decode an array of StickerString
and StickerBitmap
types declare a wrapper enum with associated values
enum Sticker: Codable {
case string(StickerString)
case image(StickerBitmap)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringData = try container.decode(StickerString.self)
self = .string(stringData)
} catch DecodingError.keyNotFound {
let imageData = try container.decode(StickerBitmap.self)
self = .image(imageData)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string) : try container.encode(string)
case .image(let image) : try container.encode(image)
}
}
}
Then you can decode
let stickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}, {"fontName":"Times","character":"}]
"""
let stickerData = Data(stickers.utf8)
let recentStickers = try! JSONDecoder().decode([Sticker].self, from: stickerData)
print(recentStickers)
In a table view just switch
on the enum
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sticker = stickers[indexPath.row]
switch sticker {
case .string(let stringSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "StringStickerCell", for: indexPath) as! StringStickerCell
// update UI
return cell
case .image(let imageSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "ImageStickerCell", for: indexPath) as! ImageStickerCell
// update UI
return cell
}
}
Type erasure on protocol with Self type requirement
You have hit just about every basic mistake you can hit in designing protocols, but they are all extremely common mistakes and not surprising. Everyone does it this way when they start, and it takes awhile to get your head in the right space. I've been thinking about this problem for almost four years now, and give talks on the subject, and I still mess it up and have to back up and redesign my protocols because I fall into the same mistakes.
Protocols are not a replacement for abstract classes. There are two completely different kinds of protocols: simple protocols, and PATs (protocols with associated type).
A simple protocol represents a concrete interface, and can be used in some of the ways you might use an abstract class in other languages, but is best thought of as a list of requirements. That said, you can think of a simple protocol as if it were a type (it actually becomes an existential, but it's pretty close).
A PAT is a tool for constraining other types so that you can give those types additional methods, or pass them to generic algorithms. But a PAT is not a type. It can't be put in an Array. It can't be passed to a function. It cannot be held in a variable. It is not a type. There is no such thing as "a Comparable." There are types that conform to Comparable.
It is possible to use type erasers to force a PAT into a concrete type, but it is almost always a mistake and very inflexible, and it's particularly bad if you have to invent a new type eraser to do it. As a rule (and there are exceptions), assume that if you're reaching for a type eraser you've probably mis-designed your protocols.
When you made Comparable (and through it Equatable) a requirement of Shape, you said that Shape is a PAT. You didn't want that. But then again, you didn't want Shape.
It's difficult to know precisely how to design this, because you don't show any use cases. Protocols emerge from use cases. They do not spring up from the model typically. So I'll provide how you get started, and then we can talk about how to implement further pieces based on what you would do with it.
First, you would model these kinds of shapes a value types. They're just data. There's no reason for reference semantics (classes).
struct Triangle: Equatable {
var base: Double
var height: Double
}
struct Rectangle: Equatable {
var firstSide: Double
var secondSide: Double
}
I've deleted Square because it's a very bad example. Squares are not properly rectangles in inheritance models (see the Circle-Ellipse Problem). You happen to get away with it using immutable data, but immutable data is not the norm in Swift.
It seems you'd like to compute area on these, so I assume there's some algorithm that cares about that. It could work on "regions that provide an area."
protocol Region {
var area: Double { get }
}
And we can say that Triangles and Rectangles conform to Region through retroactive modeling. This can be done anywhere; it doesn't have to be decided at the time that the models are created.
extension Triangle: Region {
var area: Double { get { return base * height / 2 } }
}
extension Rectangle: Region {
var area: Double { get { return firstSide * secondSide } }
}
Now Region is a simple protocol, so there's no problem putting it in an array:
struct Drawing {
var areas: [Region]
}
That leaves the original question of equality. That has lots of subtleties. The first, and most important, is that in Swift "equals" (at least when tied to the Equatable protocol) means "can be substituted for any purpose." So if you say "triangle == rectangle" you have to mean "in any context that this triangle could be used, you're free to use the rectangle instead." The fact that they happen to have the same area doesn't seem a very useful way to define that substitution.
Similarly it is not meaningful to say "a triangle is less than a rectangle." What is meaningful is to say that a triangle's area is less than a rectangle's, but that's just means the type of Area
conforms to Comparable
, not the shapes themselves. (In your example, Area
is equivalent to Double
.)
There are definitely ways to go forward and test for equality (or something similar to equality) among Regions, but it highly depends on how you plan to use it. It doesn't spring naturally from the model; it depends on your use case. The power of Swift is that it allows the same model objects to be conformed to many different protocols, supporting many different use cases.
If you can give some more pointers of where you're going with this example (what the calling code would look like), then I can expand on that. In particular, start by fleshing out Drawing
a little bit. If you never access the array, then it doesn't matter what you put in it. What would a desirable for
loop look like over that array?
The example you're working on is almost exactly the example used in the most famous protocol-oriented programming talk: Protocol-Oriented Programming in Swift, also called "the Crusty talk." That's a good place to start understanding how to think in Swift. I'm sure it will raise even more questions.
In Swift, does an object conforming to a protocol absolutely need a delegate variable in order to work with the protocol?
If the object already conforms to the protocol by implementing the variables and/or methods, then what is the reason for creating a variable called delegate and setting the type to that of the protocol?
The whole purpose of the protocol in the protocol-delegate pattern is that the only thing this class, which is going to be sending delegate messages to the delegate, needs to know or care about, is that the delegate is an adopter of that protocol — i.e., that it implements the variables / methods. This class doesn't need to know the actual class of the delegate; it just needs to know that that the delegate can be sent the delegate messages.
Thus, it's all about the compiler. The object acting as delegate may conform to the protocol, but the compiler doesn't know that unless this variable is typed as a protocol-adopter. And if the compiler doesn't know, it won't let us send delegate messages to the delegate object! So that's how we type it. That's the minimum the compiler needs to know in order to allow us to send the delegate messages.
How to declare a function with a concrete return type conforming to a protocol?
The question was written before the days of swift-ui
The "some" keyword has solved it by allowing opaque types to be returned from functions
protocol MyDelegate {
func someMethod() -> some MyProtocol
}
Related Topics
Custom Radix Columns (+Special Characters)
How to Override Setter in Swift
Nil Cannot Be Assigned to Type Avcapturedeviceinput
"'Init' Is Deprecated" Warning After Swift4 Convert
Error "[Sharesheet] Connection Invalidated" Error iOS13+ But Not on iOS 11.4
Set Multiple Arrow Directions on UIpopovercontroller in Swift
Can't Get Throws to Work with Function with Completion Handler
How to Update UIviewrepresentable with Observableobject
What Was The Reason for Swift Assignment Evaluation to Void
When How to Start Submitting Apps to The iOS App Store Written Using The Swift Programming Language
Check Os Version Using Swift on MAC Os X
Loading Image from Assets to Nsimage Keep Getting Error, Expecting Nsimage.Name
Confusing About @Environmentobject in Swiftui
Enum Initialized with a Non-Existent Rawvalue Does Not Fail and Return Nil
Instantiating a Nested Class Using Nsclassfromstring in Swift