EXC_BAD_ACCESS using protocol composition - swift

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.

Related

Associated protocol in Swift

I'm trying to abstract views that are configured from a view model. I've been using associated types so far:
public protocol ViewModelProtocol: Equatable {}
public protocol ModeledView: class {
/// The type of the view model
associatedtype ViewModel: ViewModelProtocol
var viewModel: ViewModel? { get }
/// Sets the view model. A nil value describes a default state.
func set(newViewModel: ViewModel?)
}
Which I can use like:
struct MyViewModel: ViewModelProtocol {
let foo: String
static public func == (lhs: MyViewModel, rhs: MyViewModel) -> Bool {
return lhs.foo == rhs.foo
}
}
class MyView: UIView, ModeledView {
typealias ViewModel = MyViewModel
private(set) var viewModel: MyViewModel?
public func set(newViewModel: MyViewModel?) {
print(newViewModel?.foo)
}
}
However I'd like to specify a protocol for my view models, and not a concretized type. The reason is that one struct / class could comply to several such view model protocols. I don't want to either convert this object to another type just to pass it to the view, or have the view have an associated type with more requirements than it needs. So I think I'd like to do something like:
protocol MyViewModelProtocol: ViewModelProtocol {
var foo: String { get }
}
class MyView: UIView, ModeledView {
typealias ViewModel = MyViewModelProtocol
private(set) var viewModel: MyViewModelProtocol?
public func set(newViewModel: MyViewModelProtocol?) {
print(newViewModel?.foo)
}
}
struct DataModel: MyViewModelProtocol {
let foo: String
let bar: String
static public func == (lhs: MyViewModel, rhs: MyViewModel) -> Bool {
return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}
}
let dataModel = DataModel(foo: "foo", bar: "bar")
let view = MyView()
view.set(newViewModel: dataModel)
This doesn't work. The compiler says that MyView doesn't conform to the ModeledView protocol, and hints that
Possibly intended match 'MyView.ViewModel' (aka 'MyViewModelProtocol') does not conform to 'ViewModelProtocol'
I don't really get what's bothering the compiler as MyViewModelProtocol is defined as extending ViewModelProtocol
MyViewModelProtocol is defined as extending ViewModelProtocol
Correct. MyViewModelProtocol extends ViewModelProtocol. It doesn't conform to ViewModelProtocol. This is a classic case of "protocols do not conform to themselves." Your associated type requires that ViewModel be a concrete type that conforms to ViewModelProtocol, and MyViewModelProtocol is not a concrete type and it does not conform to anything (protocols do not conform to protocols).
The way to attack this problem is to start with the calling code, and then construct protocols that support what you want the calling code to look like. In the code you've given, the correct solution is to get rid of ModeledView entirely. It isn't doing anything; nothing relies on it, and it doesn't provide any extensions. I assume in your "real" code, something does rely on it. That's the key piece of code to focus on. The best way to achieve that is to write a few concrete examples of the kinds of conforming types you'd like to exist. (If you have trouble writing several concrete implementations, then reconsider whether there's anything to abstract.)
Just to elaborate a bit more why this won't work in your specific case (it won't work in more general cases either, but your case layers additional restrictions that are important). You've required that types that conform to ViewModelProtocol also conform to Equatable. That means they must implement:
static public func == (lhs: Self, rhs: Self) -> Bool {
This does not mean that some type conforming to ViewModelProtocol is Equatable to some other type conforming to ViewModelProtocol. It means Self, the specific, concrete type implementing the protocol. ViewModelProtocol is not itself Equatable. If it were, which code above would you expect dataModel == myViewModel to call? Would it check bar or not? Remember, == is just a function. Swift doesn't know that you're asking "are these equal." It can't inject things like "if they're different concrete types they're not equal." That's something the function itself has to do. And so Swift needs to know which function to call.
Making this possible in Swift will be a major addition to the language. It's been discussed quite a lot (see the link I posted in the earlier comments), but it's at least several releases away.
If you remove the Equatable requirement, it still won't work today, but the language change required is much smaller (and may come sooner).

Difference between extension and direct call for protocol

I got this code:
protocol Protocol {
var id: Int { get }
}
extension Array where Element: Protocol {
func contains(_protocol: Protocol) -> Bool {
return contains(where: { $0.id == _protocol.id })
}
}
class Class {
func method<T: Protocol>(_protocol: T) {
var arr = [Protocol]()
// Does compile
let contains = arr.contains(where: { $0.id == _protocol.id })
// Doens't compile
arr.contains(_protocol: _protocol)
}
}
Why doesn't the line of code compile where I commented 'Doens't compile'? This is the error:
Incorrect argument label in call (have '_protocol:', expected 'where:')
When I change the method name in the extension to something else, like containz (and ofcourse change the name of the method that calls it to containz), I get this error when I try to call it:
Using 'Protocol' as a concrete type conforming to protocol 'Protocol' is not supported
But why doesn't it work when I try to call it through an extension, but it does work when I create the function in the extension directly? There isn't really any difference that I can see.
I agree with matt that the underlying answer is Protocol doesn't conform to itself?, but it's probably worth answering anyway, since in this case the answer is very simple. First, read the linked question about why [Protocol] doesn't work the way you think it does (especially Hamish's answer, which is much more extensive than the accepted answer that I wrote). [Protocol] does not match the where Element: Protocol clause because Protocol is not a concrete type that conforms to Protocol (because it's not a concrete type).
But you don't need [Protocol] here. You have T: Protocol, so you can (and should) just use that:
var arr = [T]()
With that change, the rest should work as you expect because T is a concrete type that conforms to Protocol.

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.

Check Protocol Inheritance at Runtime

It's easy to check protocol conformance of a type at runtime:
guard type is Encodable.Type else { ... }
but this technique fails if type is not a struct or class, but instead a protocol that inherits from Encodable. Is there a way to make a similar check for protocols?
This is a tad hacky, but it will probably solve your problem:
Given these example types:
protocol Animal: Encodable {}
struct Person: Animal {}
protocol SomeOtherProtocol {}
struct Demon: SomeOtherProtocol {}
I'm simplifing the init logic for the example by assuming:
typealias Group = Array
let animals: Group<Animal> = [Person(), Person(), Person()]
let notAnimals: Group<SomeOtherProtocol> = [Demon(), Demon(), Demon()]
This should work for a custom collection class however... Continuing, define the following extension for your custom Collection
extension Group {
func asEncodables() -> Group<Encodable>?{
return self as? Group<Encodable>
}
var isElementEncodable: Bool {
return self is Group<Encodable>
}
}
You now have access to the following:
animals.asEncodables() //returns some
notAnimals.asEncodables() // returns none
animals.isElementEncodable //true
notAnimals.isElementEncodable //false
And so for your original question, you could make your check as follows:
guard notAnimals.isElementEncodable else { return }
Hope this helps you. Currently I know of no way to make the comparison with something similar to an if X is Y :/

How to specify multiple inter-dependent associated types in swift protocols?

How do I specify multiple associated types in a protocol with added dependencies on their inner associated types? For example:
protocol CombinedType
{
typealias First: FirstType
typealias Second: SecondType
var first: First { get }
var second: Second { get }
}
protocol FirstType
{
typealias Value
var value: Value { get }
}
protocol SecondType
{
type alias Value
var value: Value { get }
}
I need to specify a limitation on the CombinedType protocol that would make it so FirstType.Value == SecondType.Value much like in a where clause in generics.
Without that limitation, the code:
func sum<Combined: CombinedType>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
produces an error Combined.FirstType.Value is not compatible with Combined.SecondType.Value.
But if I explicitly add the where clause to the function, it compiles:
func sum<Combined: CombinedType where Combined.FirstType.Value == Combined.SecondType.Value>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
The problem is that this where needs to be copied everywhere, which produces a ton of boilerplate and it gets even worse when using CombinedType inside another protocol, in which case the where clause gets larger and larger.
My question is: can I somehow include that limitation in the protocol itself?