Swift: Make Two Types with The Same "Shape" Conform to a Common Protocol

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 method
isEqualTo::

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



Leave a reply



Submit