× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

protocol typed array can't be downcast to concrete type array

Antonio Bello
Mar 01, 2015
<p>Ignoring the optional binding for a moment and using a direct assignment:</p> <pre><code>let x = ps as [X] </code></pre> <p>the following runtime error is reported:</p> <pre><code>fatal error: array element cannot be bridged to Objective-C </code></pre> <p>That means the downcast from array of protocols to array of adopters requires obj-c binding. This can be easily solved by declaring the protocol as objc compatible:</p> <pre><code>@objc protocol P : class { var value:Int {get} } </code></pre> <p>With that simple change, the code now works and no run time exception is raised.</p> <p>Now the <em>how</em> is solved, but leaving the <em>why</em> an open issue. I don't have an answer yet, but I'll try to dig deeper on that.</p> <p><strong>Addendum: figure out the "why"</strong></p> <p>I spent some time investigating on this issue, and following is what I've come with.</p> <p>We have a protocol and a class adopting it:</p> <pre><code>protocol P {} class X : P {} </code></pre> <p>We create an array of P:</p> <pre><code>var array = [P]() </code></pre> <p>Converting the empty array to <code>[X]</code> works:</p> <pre><code>array as [X] // 0 elements </code></pre> <p>If we add an element to the array, a runtime error occurs:</p> <pre><code>array.append(X()) array as [X] // Execution was interrupted, reason: ... </code></pre> <p>The console output says that:</p> <pre><code>fatal error: array element cannot be bridged to Objective-C </code></pre> <p>So casting an array of protocol objects to an array of its adopter requires bridging. That justifies why @objc fixes the issue:</p> <pre><code>@objc protocol P {} class X : P {} var array = [P]() array.append(X()) array as [X] // [X] </code></pre> <p>Sifting the documentation, I found out the reason for that to happen. </p> <p>In order to perform the cast, the runtime has to check whether <code>X</code> conforms to the <code>P</code> protocol. The <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_413">documentation</a> clearly states that:</p> <blockquote> <p>You can check for protocol conformance only if your protocol is marked with the @objc attribute</p> </blockquote> <p>To verify that (not that I don't trust the documentation), I've used this code in the playground:</p> <pre><code>protocol P {} class X : P {} let x = X() let y = x is P </code></pre> <p>but I get a different error, stating that:</p> <pre><code>Playground execution failed: &lt;EXPR&gt;:18:11: error: 'is' test is always true let y = x is P </code></pre> <p>Writing that in a "regular" project instead we get what expected:</p> <pre><code>protocol P {} class X {} func test() { let x = X() let y = x is P } Cannot downcast from 'X' to non-@objc protocol type 'P' </code></pre> <p>Conclusion: in order for a protocol typed array to be downcast to a concrete type array, the protocol must be marked with the <code>@objc</code> attribute. The reason is that the runtime uses the <code>is</code> operator to check for protocol conformance, which accordingly to the documentation is only available for bridged protocols.</p> <p>This tip was originally posted on <a href="http://stackoverflow.com/questions/25588196/protocol%20typed%20array%20can't%20be%20downcast%20to%20concrete%20type%20array/25589323">Stack Overflow</a>.</p>
comments powered by Disqus