Swift recursive protocol: How to mark leave node? - swift

I've a tree of (different) structs which I want to show in an NSOutlineView. I've also written an update function which determines the move/insert/reload operations on the outline view.
To make the update function more generic, I've written a protocol that makes the different structs similar:
protocol Outlinable: Equatable, Identifiable {
associatedtype T: Outlinable
var children: [T]? { get }
}
The children array is an optional to mark that a node might not have children.
I've made two structs conform to this protocol:
struct ImageWithErrors: Decodable, FetchableRecord {
let imageFile: ImageFile
let imageFileErrors: [ImageFileError]
}
struct LayerImagesListItem: Decodable, FetchableRecord {
let layer: Layer
let imageFiles: [ImageWithErrors]
}
extension LayerImagesListItem: Identifiable, Outlinable {
var id: Int64 { get { layer.id! }}
var children: [ImageWithErrors]? { get { imageFiles }}
}
extension ImageWithErrors: Identifiable, Outlinable {
var id: Int64 { get { -imageFile.id! }}
var children: [Outlinable]? { get { return nil }}
}
The LayerImagesListItem is a root struct, while the ImageWithErrors is (currently) a leave struct. But on this last struct I get the errors:
Type 'ImageWithErrors' does not conform to protocol 'Outlinable'
Protocol 'Outlinable' can only be used as a generic constraint because it has Self or associated type requirements
I've tried replacing [Outlinable] with [Any] but that doesn't solve anything.
How can I tell Swift that ImageWithErrors is never going to return any children?

Related

Using protocol in SwiftUI for providing "some View" / Generics?

I'm trying to get my head something in SwiftUI. I want to build a SwiftUI view and have something you could call a ViewProvider as a #State var. something like this:
protocol ViewProvider {
associatedtype ViewOne = View
associatedtype ViewTwo = View
#ViewBuilder var viewOne: ViewOne { get }
#ViewBuilder var viewTwo: ViewTwo { get }
}
struct ContentView: View {
#State private var parent: ViewProvider?
var body: some View {
VStack {
HStack {
Button(action: { parent = Father() }, label: { Text("Father") })
Button(action: { parent = Mother() }, label: { Text("Mother") })
}
if let p = parent {
p.viewOne
p.viewTwo
}
}
}
}
class Father: ViewProvider {
#ViewBuilder var viewOne: some View {
Text("Father One!")
}
#ViewBuilder var viewTwo: some View {
Text("Father Two!")
}
}
class Mother: ViewProvider {
#ViewBuilder var viewOne: some View {
Text("Mother One!")
}
#ViewBuilder var viewTwo: some View {
Text("Mother Two!")
}
}
This produces 2 different compiler errors.
#State private var parent: ViewProvider?
// Protocol 'ViewProvider' can only be used as a generic constraint because it has Self or associated type requirements
and
p.viewOne
p.viewTwo
// 2x Member 'viewOne' cannot be used on value of protocol type 'ViewProvider'; use a generic constraint instead
I have a vague idea of what I'm doing wrong, but no idea on how to solve it :)
What syntax should I use to get something like this to work?
Assuming you're on Swift 5.6 or lower, the problem is that you can only use protocols with associated types for conformance, ie you can't use them as types to pass around. The reasoning is that their associated types will be different for different conformers.
Say you have the following:
protocol P {
associatedtype T
var prop: T
}
class MyClass: P {
var prop: Int
}
class MyOtherClass: P {
var prop: String
}
What would the result of the following be?
let arr: [P] = [MyClass(), MyOtherClass()]
let myMappedArr = arr.map { $0.prop }
prop is of a different type for each conformer.
In Swift 5.7, however, you actually can pass around protocols of this sort. In later versions of Swift, you will have to use the keyword any to pass these protocols around as types.
See the proposal for unlocked existentials to learn more about it.
Lastly to address opaque types here:
Since you can't pass around protocols with associated types, you can't have something like
#State var myState: ViewProvider or even #State var myState: some ViewProvider, because your state variable is assigned, and you can't assign something of an opaque type.
In SwiftUI's View, this works because the view property is computed, and thus the type can be inferred
// type is inferred to be (something like) Group<Text>
var body: some View {
Group {
Text("something")
}
}
whereas here, you can't find a suitable type to assign to a property whose type is opaque
#State var myState: some ViewProvider
...
// You don't know myState's type, so you can't assign anything to it
myState = ... // error - you won't be able to find a matching type to assign to this property
To wit, the line #State private var parent: ViewProvider? in your code simply won't compile in Swift 5.6 or lower, because you're not allowed to use your ViewProvider protocol as a type for anything other than conformance or as an opaque return type when used in functions or computed properties.
Sorry for all the edits. Wanted to provide a couple of potential solutions:
One way is to simply make your ContentView generic over the type of its ViewProvider
struct ContentView<ViewProviderType: ViewProvider> {
#State private var parent: ViewProviderType?
...
}
The other would be to simply remove the associatedtype from your protocol and just erase the view type you're trying to return:
protocol ViewProvider {
var viewOne: AnyView { get }
var viewTwo: AnyView { get }
}
If you're working with Swift 5.7, you may be able to use your type-constrained protocols as property types, or you can also use primary associated types, wherein you could declare properties of type ViewProvider<MyView> (though that doesn't necessarily solve your problem).
Generics or type erasure over ViewProvider's view types are probably the best candidates for what you're trying to do, even in a Swift 5.7 world.

Why do I get error when I use ForEach in Xcode 11 Beta 5?

Error message:
Generic parameter 'ID' could not be inferred
ForEach(0...self.workoutsViewModel.workoutRoutine[self.workoutIndex].routine[0].exercises.count - 1) { x in
Text("\\(x)")
}
The elements in the collection you pass as the first argument of ForEach must conform to Identifiable, or you must use a different initializer to specify the KeyPath of the id on your elements. For example, the following code does not compile:
struct MyModel {
let name: String
}
struct ContentView: View {
let models: [MyModel]
var body: some View {
ForEach(models) { model in
Text(model.name)
}
}
}
The models array does not satisfy the requirements of the ForEach initializer, namely, its elements do not conform to Identifiable. I can solve this in one of two ways:
1.) Extend MyModel to conform to Identifiable:
extension MyModel: Identifiable {
// assuming `name` is unique, it can be used as our identifier
var id: String { name }
}
2.) Use the convenience initializer on ForEach that allows you to specify a KeyPath to your identifier:
var body: some View {
ForEach(models, id: \.name) { model in
Text(model.name)
}
}
The answer by #daltonclaybrook is great because it explains why you're getting that error and illustrates the correct approach of creating a custom model. But if you're looking for a temporary, quick and dirty solution this works for me in Xcode 11.2.1 and iOS 13.2 for an array of String:
ForEach(strings, id: \.self) { string in
...
}

Working with generic constraints

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.

Strongly Typed Tree Hierarchy Using Protocols and Associated Types

I'm trying to design a strongly typed hierarchy of objects using protocols, but can't quite get it right.
For illustration, suppose that the concrete types that will end up adopting these protocols are the classes Country, State, and City.
Each node can have a parent (except the root object) and/or children (except the leaf objects): All State instances are children of the single Country instance and have it as parent, and they have City instances as children, etc.
So I start with these two protocols:
/// To be adopted by State and City
///
protocol Child: AnyObject {
associatedtype ParentType: AnyObject
// (-> so that property `parent` can be weak)
weak var parent: ParentType? { get }
}
/// To be adopted by Country and State
///
protocol Parent: AnyObject {
associatedtype ChildType: AnyObject
// (-> for symmetry)
var children: [ChildType] { get }
}
I have two separate protocols instead of one that groups all the above requirements, because I don't want to have to specify a "dummy" typealias ParentType for the root class Country (which is meaningless), nor a "dummy" typealias ChildType for the leaf class City.
Instead, I can separate the parent and child behaviours and just have the intermediate class State adopt both protocols.
Next, I want to make my clases initializable from a dictionary read from disk. For the child classes, I want to specify the parent at the moment of instantiation, so I came up with this scheme:
protocol UnarchivableChild: Child {
init(dictionary: [String: Any], parent: ParentType?)
}
protocol UnarchivingParent: Parent {
associatedtype ChildType: UnarchivableChild
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildType]
}
As it stands, it looks like I could go one step further and add a default implementation of the method readChildren(fromDictionaries:), like this:
extension UnarchivingParent {
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildType] {
return dictionaries.flatMap({ dictionary in
return ChildType(dictionary: dictionary, parent: self)
})
}
}
...because in this protocol, ChildType is constrained to UnarchivableChild, so it should support the initializer...? But I get:
Cannot invoke 'ChildType' with an argument list of type '(dictionary: ([String : Any]), parent: Self)'
(why the capitalized "Self"?)
I think I am missing something about how associated types work...
How can I code this default implementation?
Update: Apparently, passing self is the problem somehow. I modified the code to:
protocol Node: AnyObject {
}
protocol Parent: Node {
associatedtype ChildNodeType: Node
var children: [ChildNodeType] { get set }
}
protocol Child: Node {
associatedtype ParentNodeType: Node
weak var parent: ParentNodeType? { get set }
}
protocol UnarchivableChild: Child {
init(dictionary: [String: Any]) // Removed parent parameter!
}
protocol UnarchivingParent: Parent {
associatedtype ChildNodeType: UnarchivableChild
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildNodeType]
}
extension UnarchivingParent {
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildNodeType] {
return dictionaries.flatMap({
let child = ChildNodeType(dictionary: $0)
// Assign parent here instead:
child.parent = self // < ERROR HERE
return child
})
}
}
The error is:
Cannot assign value of type 'Self' to type '_?'
After reading over The Swift Programmng Language: Generics (Section: "Extensions With a Generci Where Clause"), I found the appropriate syntax to achieve what I wanted. I settled for this code (type names are slighlty different form the ones in the original question):
protocol DictionaryInitializable {
init(dictionary: [String: Any])
}
protocol ChildNode: AnyObject {
associatedtype ParentType: AnyObject
weak var parent: ParentType? { get set }
}
protocol ParentNode {
associatedtype ChildType: AnyObject
var children: [ChildType] { get set }
}
// This 'WHERE' clause fixes the issue:
extension ParentNode where ChildType: DictionaryInitializable,
ChildType: ChildNode, ChildType.ParentType == Self {
func readChildren(from dictionaries: [[String: Any]]) -> [ChildType] {
return dictionaries.map({
return ChildType(dictionary: $0)
})
}
}
The first constraint of the where clause lets me call the initializer init(dictionary:), the second one guarantees the presence of the property parent, and the third one lets me assign self as its value.
Alternatively, I can change:
protocol ParentNode {
associatedtype ChildType: AnyObject
to:
protocol ParentNode {
associatedtype ChildType: ChildNode
...and skip the second constraint.

EXC_BAD_ACCESS using protocol composition

I want to configure an object with multiple presentables; I've created protocols for them; so that I can combine them into a concrete presentable.
protocol Presentable {
}
protocol TextPresentable: Presentable {
var text:String { get }
}
protocol ImagePresentable: Presentable {
var image:String { get }
var images:[String] { get }
}
The concrete struct:
struct ConcretePresentable: TextPresentable, ImagePresentable {
var text:String { return "Text" }
var image:String { return "Image" }
var images:[String] { return ["A", "B"] }
}
The next thing would be to have a presenter, and I would check if the passed in presenter is actually valid:
typealias TextAndImagePresentable = protocol<TextPresentable, ImagePresentable>
struct ConcretePresenter {
func configureWithPresentable(presentable: Presentable) {
guard let textAndImagePresentable = presentable as? TextAndImagePresentable else {
return
}
print(textAndImagePresentable.text)
print(textAndImagePresentable.image)
print(textAndImagePresentable.images)
}
}
To configure:
let concretePresentable = ConcretePresentable()
let concretePresenter = ConcretePresenter()
concretePresenter.configureWithPresentable(concretePresentable)
It goes fine if I run this in a playground, with all of the code in the same place. However, once I put this in a project and split it up into multiple files (ConcretePresenter.swift, ConcretePresentable.swift holding the concrete structs and Presentable.swift which holds the protocols), I get a EXC_BAD_ACCESS.
Why does that happen?
As a disclaimer, I don't necessarily find this answer very satisfying, but it does work.
So, once I noticed that the text & image properties were being returned in each others places (the value for text is being returned by the image property and vice versa), I figured the problem had something to do with what Swift is doing with managing pointers here.
So, out of curiosity, I wanted to add a truly scalar value to the protocols. I added a value property as an Int to the TextPresentable protocol:
protocol Presentable {}
protocol TextPresentable: Presentable {
var text:String { get }
var value: Int { get }
}
protocol ImagePresentable: Presentable {
var image:String { get }
var images:[String] { get }
}
And then I set up the concrete implementation to return some known value. Here, we're returning 0.
struct ConcretePresentable: TextPresentable, ImagePresentable {
var text:String { return "SomeText" }
var value: Int { return 0 }
var image:String { return "SomeImage" }
var images:[String] { return ["A", "B"] }
}
After running this code, we still get the same crash, but I notice that value, which really shouldn't have a problem printing 0 is instead printing some very large number: 4331676336. This isn't right at all.
I also changed images from an array to a dictionary to see if the error persists--it does. It seems the crash is related to collections and not specific to arrays.
From here, I tried some other things.
I tried making ConcretePresentable a class rather than a struct.
class ConcretePresentable: TextPresentable, ImagePresentable
That resulted in the same behavior.
I tried making ConcretePresentable conform to the typealias rather than the protocols independently:
struct ConcretePresentable: TextAndImagePresentable
That resulted in the same behavior.
I tried doing both of the aforementioned at once:
class ConcretePresentable: TextAndImagePresentable
Yet still the same behavior.
I did come up with one way to make it work though. Make a protocol that conforms to the two protocols in your typealias and make ConcretePresentable conform to that:
protocol TextAndImagePresentable: TextPresentable, ImagePresentable {}
struct ConcretePresentable: TextAndImagePresentable {
// ...
}
The problem here is that if you don't explicitly make ConcretePresentable conform to the protocol, it will fail the guard let even if it does conform to TextPresentable and ImagePresentable.
I asked about this on Swift Users and filled a bug.
Confirmed to be a bug in the compiler:
https://bugs.swift.org/browse/SR-4477
Fixed by Joe Groff now:
Joe Groff added a comment - 2 hours ago
Merged. Should be fixed in future snapshots.
struct uses value semantics and so properties are copied. Swift should have reported this as an error since you are trying to inherit from two protocols which derive from the same base protocol. In classes this will work but in struct it wont because of value semantics for struct. In case you decide to add a variable to Presentable protocol Swift would be confused which ones to bring into the struct. From TextPresentable or ImagePresentable
You should use #protocol Presentable : class and then convert ConcretePresentable to class to fix this.
protocol Presentable : class {
}
class ConcretePresenter {...
The problem is in the cast of the Presentable to TextAndImagePresentable. The guard let succeed, but creates an invalid value (I don't know exactly why).
One way to check it, is look to the console on the execution of the commands:
print(textAndImagePresentable.text)
print(textAndImagePresentable.image)
print(textAndImagePresentable.images)
That will print:
Image
Text
Program ended with exit code: 9
One way to avoid it is to change your method signature to avoid the casting:
func configureWithPresentable(presentable: TextAndImagePresentable) {
print(presentable.text)
print(presentable.image)
print(presentable.images)
}
And, in my opinion, since nothing will happen if the presentable do not conform to both protocols, makes more sense to delimiter it on the method signature.