Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm going through a series of Swift tutorials and I don't want to move forward without understanding the point of this
protocol Identifiable {
var id: String { get set }
}
/*:
We can’t create instances of that protocol - it’s a description, not a type by itself.
But we can create a struct that conforms to it:
*/
struct User: Identifiable {
var id: String
}
//: Finally, we’ll write a `displayID()` function that accepts any `Identifiable` object:
func displayID(thing: Identifiable) {
print("My ID is \(thing.id)")
}
This is the tutorial page
Say I want to now run displayID and get the thing.id, how would that work?
You can try it on swift playgrounds this is one way you can use it for example:
import Foundation
protocol Identifiable {
var id: String { get set }
}
struct User: Identifiable {
var id: String
}
class ViewController {
func displayID(thing: Identifiable) {
print("My ID is \(thing.id)")
}
}
let vc = ViewController()
let user = User(id: "12")
vc.displayID(thing: user)
// My ID is 12
Usually protocols are seen like contracts (interfaces in java/android) for a class or struct to follow, so you know that making a class or a struct comforming to a protocol will assure you an implementation of your basic methods that you might require for that kind of object/instance in future.
As well they are meant to allow you to provide in your automated tests for example a mocked sample of the implementation in order to get a mock id instead of a real one as in this example.
A protocol simply means ...
you MUST have all the stuff stated!
That is all a protocol is!
Here's your protocol
protocol Identifiable {
var id: String { get set }
}
all that means is you MUST have a "id" !
So this:
class Test: Identifiable {
}
is wrong !!!
But this:
class Test: Identifiable {
var id: String
}
is correct !!!
That's all it is!
Protocols are that simple!
Yes, it is true you cannot create an instance of protocol. But you can create an instance of classes and struct that implements protocol. Protocol just ensure that the struct or class that implements this protocol must have all these properties and methods that are defined in the protocol. You can say that Protocol is a contract. You need to fulfill it if you implements it.
Related
I have a Codable struct that is part of my app, RemoteData. I’m building a reusable package that will fetch the data and store it in UserDefaults. The data fetching, DataFetcher class has a Codable generic parameter. I am subclassing DataFetcher to pass in RemoteData as the generic param.
// in my app
struct RemoteData: Codable {
var experimentOne: [Variant<[Page]>]
var experimentTwo: [Variant<Bool>]
var experimentThree: [Variant<String>]
}
All of the properties in RemoteData will be arrays of type Variant<T> where T is Codable:
// in my package
public struct Variant<T: Codable>: Codable, VariantProtocol {
public var experimentName: String
public var variantName: String
public var percent: Int
public var value: T
}
I’d like to be able to save this data in UserDefaults. I’d like to perform some filtering on the Variant array to see if this user should see that configuration. I’d like to save the data so that each experiment name is the key and the single variant the user should see is the value rather than the whole array. Although if the whole array is the only option, I’d be ok with that too.
However, since my DataFetcher doesn’t know what the properties are since it is just taking in a generic I don’t think I can do that. My first thought was to create a protocol that RemoteConfig confirms to and that the DataFetcher generic also conforms to.
// in my package, but subclassing in my app to provide url
open class DataFetcher<T: Decodable> {
var remoteConfig: T?
var url: URL
public init(url: String) {
self.url = url
}
func fetchAndSaveData() { ... }
}
That doesn’t work because I then need to specify T in Variant and I will only be able to have Variant arrays of one type.
I’m stuck here and not sure how to move forward.
I've got the following snippet:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
var test: [MyProtocol] = []
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why doesn't this work? Shouldn't the where ID == UUID remove the ambiguity the error is concerned with? Am I missing something here?
I think this question is similar to this one: Usage of protocols as array types and function parameters in swift
However, I would have assumed that adding where ID == UUID should fix the problem? Why is that not the case?
Thanks!
Edit
So, this problem has occurred while experimenting with SwiftUI and struct data models. I've always used classes for any kind of data model but it seems like SwiftUI wants to get you to use structs as often as possible (I still don't see how that's realistically possible but that's why I'm experimenting with it).
In this particular case, I tried to have a manager that contains structs that all conform to MyProtocol. For example:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
struct A: MyProtocol { // First data model
var id: UUID = UUID()
}
struct B: MyProtocol { // Second data model
var id: UUID = UUID()
}
class DataManager: ObservableObject {
var myData: [MyProtocol]
}
...
I don't actually have to declare Identifiable on MyProtocol but I thought it would be nicer and cleaner.
Because this is not a current feature of Swift. Once there is an associated type, there is always an associated type. It doesn't go away just because you constrain it. And once it has an associated type, it is not concrete.
There is no way to "inherit" protocols this way. What you mean is:
protocol MyProtocol {
var id: UUID { get }
}
And then you can attach Identifiable to structs that require it:
struct X: MyProtocol, Identifiable {
var id: UUID
}
(note that no where clause is required.)
There is no Swift feature today that allows you to say "types that conform to X implicitly conform to Y." There is also no Swift feature today that allows for an Array of "things that conform to Identifiable with ID==UUID." (That's called a generalized existential, and it's not currently available.)
Most likely you should go back to your calling code and explore why you require this. If you post the code that iterates over test and specifically requires the Identifiable conformance, then we may be able to help you find a design that doesn't require that.
This question already has answers here:
Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?
(3 answers)
Closed 5 years ago.
Can anyone explain why this doesn't compile?
// In MyViewKit.framework
protocol UserRenderable {
var name : String { get }
}
protocol PostRenderable {
var title: String { get }
var author: UserRenderable { get }
}
// In MyDataKit.framework
struct User {
let id: String
let name : String
}
struct Post {
let id: String
let title: String
let author: User
}
// In MyApp
extension User : UserRenderable {}
extension Post: PostRenderable {}
But this (below) does? Apparently, read-only property requirements in a protocol can't be satisfied by a property which conforms to them (according to the answers to this question.)
What is typealias doing here?
// In MyViewKit.framework
protocol UserRenderable {
var name : String { get }
}
protocol PostRenderable {
var title: String { get }
var author: UserRenderable { get }
}
// In MyDataKit.framework
struct User {
let id: String
let name : String
}
struct Post {
let id: String
let title: String
let author: User
}
// In MyApp
extension User : UserRenderable {}
extension Post: PostRenderable {
// what is type alias doing here? Note: there are no associated types.
typealias User = UserRenderable
}
UPDATE Just clarifying why IMO this isn't a duplicate to Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?. I understand the limitation in the Swift language: I'm trying to also understanding whether or not this is a valid workaround.
A typealias has nothing to do with associated types in this particular instance. Typealias is just a way to let the compiler know what is the associated type in a protocol if it cannot be inferred, but a typealias can be used in different contexts as well.
Your second example compiles, because you change User from a concrete type to a typealias for UserRenderable in your Post extension. If you check the type of author in Xcode, you'll see that its type is actually UserRenderable after the extension rather than User. Due to the typealias, inside Post, User won't refer to the User struct, but rather it will be UserRenderable due to the local overwrite you did with the typealias.
I know this question has been asked before but I have no idea how to solve this current problem. I have defined a protocol MultipleChoiceQuestionable with an associatedtype property:
protocol Questionable {
var text: String {get set}
var givenAnswer: String? {get set}
}
protocol MultipleChoiceQuestionable: Questionable {
associatedtype Value
var answers: Value { get }
}
struct OpenQuestion: Questionable {
var text: String
var givenAnswer: String?
}
struct MultipleChoiceQuestion: MultipleChoiceQuestionable {
typealias Value = [String]
var text: String
var givenAnswer: String?
var answers: Value
}
struct NestedMultipleChoiceQuestion: MultipleChoiceQuestionable {
typealias Value = [MultipleChoiceQuestion]
var text: String
var answers: Value
var givenAnswer: String?
}
Types which conform to this protocol are saved in an array as Questionable like so:
// This array contains OpenQuestion, MultipleChoiceQuestion and NestedMultipleChoiceQuestion
private var questions: [Questionable] = QuestionBuilder.createQuestions()
Somewhere in my code I want to do something like:
let question = questions[index]
if let question = question as? MultipleChoiceQuestionable {
// Do something with the answers
question.answers = .....
}
This is not possible because Xcode warns me: Protocol MultipleChoiceQuestionable can only be used as a generic constraint. I've been searching around on how to solve this issue since generics are quite new for me. Apparently Swift doesn't know the type of the associatedtype during compile time which is the reason this error is thrown. I've read about using type erasure but I don't know if that solves my problem. Maybe I should use generic properties instead or perhaps my protocols are defined wrong?
If the action you want to apply to your sub-protocol objects does not rely on the associated type (i.e. neither has the a generic parameter nor returns the generic type) you can introduce an auxiliary protocol which just exposes the properties/methods you need, let your type conform to that protocol, and declare the question in terms of that protocol.
For example, if you just want to know some info about the question:
protocol MultipleChoiceInfo {
var numberOfAnswers: Int { get }
}
extension MultipleChoiceQuestion: MultipleChoiceInfo {
var numberOfAnswers: Int { return answers.count }
}
// do the same for the other multiple-choice types
Then you can access the questions through the new protocol like this:
let question = questions[index]
if let info = question as? MultipleChoiceInfo {
print(info.numberOfAnswers)
}
As I said, if you cannot provide an abstract (non-generic) interface then this won't work.
EDIT
If you need to process the generic data inside your questions you can extract the logic depending on the concrete generic type into another "processing" type which provides an interface to your questions. Each question type then dispatches its data to the processor interface:
protocol MultipleChoiceProcessor {
func process(stringAnswers: [String])
func process(nestedAnswers: [MultipleChoiceQuestion])
}
protocol MultipleChoiceProxy {
func apply(processor: MultipleChoiceProcessor)
}
extension MultipleChoiceQuestion: MultipleChoiceProxy {
func apply(processor: MultipleChoiceProcessor) {
processor.process(stringAnswers: answers)
}
}
Just create a type conforming to MultipleChoiceProcessor and do the type-check dance again:
if let proxy = question as? MultipleChoiceProxy {
proxy.apply(processor:myProcessor)
}
As an aside, if you don't have more protocols and structs in your real application, you might also just ditch the protocol stuff altogether... for this kind of problem it seems a bit over-engineered.
I have a struct like,
struct LoginConstants {
struct Selectors {
let testa = "test1234"
}
}
and a class like,
class Login: XCTestCase {
override class func setUp () {
// below constant will have a value like "LoginConstants"
let localConstants = "\(String(describing: self))Constants"
}
}
... so here I have a struct-name as a string format in localConstants.
My Question is how I can access the LoginConstants properties from the localConstants string?
NOTE:
I know I can access the LoginConstants() directly.
But I am planning to create a parent class where I can access this ***Constants struct dynamically.
Thanks for the help!
Objective-C has the ability to do this, but Swift does not. If you give a class an Objective-C name via the #objc attribute, you can use the Objective-C runtime functions to access it by name. However, this is not possible with a struct.
It's probably not the best way to go anyway. A better solution is to rethink what you are trying to do, and access the struct type directly rather than by name.