MrJre
MrJre

Reputation: 7161

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?

Upvotes: 3

Views: 486

Answers (4)

Rudolf Adamkovič
Rudolf Adamkovič

Reputation: 31486

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.

Upvotes: 1

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.

Upvotes: 0

Pradeep K
Pradeep K

Reputation: 3661

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 {...

Upvotes: 1

nhgrif
nhgrif

Reputation: 62062

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.

Upvotes: 2

Related Questions