为何使用带UIView约束的协议数组时需强制向下转型?
Self: UIView约束的协议数组元素不能直接转为UIView? 先来看下你的代码和遇到的错误:
import UIKit protocol Test where Self: UIView {} class MyView: UIView, Test { } let array: [Test] = [MyView()] let view = array[0] let myAlpha = (view as UIView).alpha // ERROR
error: 'Test' is not convertible to 'UIView'; did you mean to use 'as!' to force downcast?
你肯定会纳闷:我明明给Test协议加了约束,只有UIView子类才能遵循它,数组里的元素怎么可能不是UIView?为什么还要强制转型?下面我来拆解这个问题:
核心原因:协议类型和遵循协议的类型是两回事
你加的where Self: UIView约束,只限制了哪些类型能遵循Test协议——必须是UIView的子类,但Test这个协议本身,在Swift的类型系统里是一个独立的协议类型,它并不等同于UIView类型。
打个比方:就好比你规定“只有苹果才能贴这个专属标签”,但标签本身不是苹果,你拿着带这个标签的物品,编译器只知道它有这个标签,不会直接默认它就是苹果——哪怕我们都知道只有苹果能贴这个标签,但编译器只认它拿到的类型信息(标签类型),不会做额外的逻辑推断。
所以当你从[Test]数组里取出元素时,编译器看到的类型是Test(协议类型),而不是UIView,自然不能直接转换。
有没有可能数组里出现非UIView的元素?
完全不可能!因为你的协议约束已经把路堵死了——任何想要遵循Test的类型,必须是UIView的子类,所以数组里的所有元素必然都是UIView的实例。但编译器没办法在编译期验证这一点,协议类型的设计就是为了灵活支持多种类型,编译器不会因为你加了Self约束就假设所有协议实例都属于某个父类。
怎么解决这个问题?
既然我们从业务逻辑上能100%确定所有元素都是UIView,那有两种优雅的处理方式:
方式1:安全使用as!强制转型
因为我们的约束已经保证了类型安全,所以这个强制转型绝对不会崩溃,放心用就行:
let myAlpha = (view as! UIView).alpha
方式2:给协议扩展UIView相关的便捷属性/方法
如果需要频繁访问UIView的属性(比如alpha、frame这些),可以给Test协议加个扩展,把转型逻辑封装起来:
extension Test { // 封装一个获取自身UIView实例的属性 var baseView: UIView { return self as! UIView // 这里的强制转型完全安全 } // 直接把UIView的alpha属性暴露出来 var alpha: CGFloat { get { baseView.alpha } set { baseView.alpha = newValue } } } // 之后使用起来就非常清爽了 let myAlpha = view.alpha
这样既避免了重复写转型代码,又让代码更易读。
内容的提问来源于stack exchange,提问作者J. Doe

