一尘不染

UIBezierPath子类初始化器

swift

我正在尝试创建UIBezierPath的子类,以添加一些对我有用的属性。

class MyUIBezierPath : UIBezierPath {
   var selectedForLazo : Bool! = false

   override init(){
       super.init()
   }

   /* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
   init(rect: CGRect){
       super.init(rect: rect)
   }

   /* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
   init(roundedRect: CGRect, cornerRadius: CGFloat) {
       super.init(roundedRect: roundedRect, cornerRadius: cornerRadius)
   }

   required init(coder aDecoder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
   }
}

编辑:我需要这个,因为在我的代码中我写

var path = MyUIBezierPath(roundedRect: rect, cornerRadius: 7)

并导致编译错误:

“必须调用超类’UIBezierPath’的指定初始化程序”

我试图在子类中添加该初始化器,但似乎不起作用。

你能帮我吗?


阅读 246

收藏
2020-07-07

共1个答案

一尘不染

注意 :此问题已在iOS 9中得以解决,在该版本中,API已被重写为init(rect:)存在,其他所有(方便初始化程序)也是如此。


问题

简而言之,您遇到的问题是以下代码无法编译:

    class MyBezierPath : UIBezierPath {}
    let b = MyBezierPath(rect:CGRectZero)

从Swift的角度来看,这似乎是错误的。该文档似乎说UIBezierPath有一个初始化程序init(rect:)。但是,为什么init(rect:)在我们的子类MyBezierPath中不继承UIBezierPath?根据初始化程序继承的一般规则,应该这样。

说明

UIBezierPath不适用于子类化。因此,它没有任何初始化程序-
除了init(),它是从NSObject继承的。在Swift中,UIBezierPath 看起来
好像具有初始化程序。但这是一个错误的表示。正如我们看到的Objective-
C标头所示,UIBezierPath实际上具有的是便捷构造函数,它们是类方法,例如:

+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;

现在,此方法(及其兄弟姐妹)展示了一些Swift不能很好处理的异常功能:

  • 它不仅是初始化程序的变体;它是一个 纯粹的 便利构造函数。Objective-C向我们展示UIBezierPath没有相应的true初始值设定项initWithRect:。在可可中,这是非常不寻常的情况。

  • 它返回UIBezierPath*,而不是instancetype。这意味着它不能被继承,因为它返回错误类型的实例。在子类MyBezierPath中,调用将bezierPathWithRect:产生UIBezierPath, 而不是 MyBezierPath。

斯威夫特严重地应付这种情况。一方面,它根据其通常的策略将类方法bezierPathWithRect:转换为表面上的初始化器init(rect:)。但是,另一方面,这不是“真正的”初始化程序,并且不能由子类继承。

因此,您被表观的初始化器所迷惑init(rect:),然后由于无法继承子类而无法在子类上调用它时感到惊讶和沮丧。

注意: 我并不是说Swift的行为不是错误;我认为这 一个错误(尽管我对将错误归咎于Swift还是UIBezierPath
API还是有些困惑)。Swift不应bezierPathWithRect:变成初始化程序,或者如果 确实
将其设为初始化程序,则应使该初始化程序可继承。无论哪种方式,它 都应该 是可继承的。但这不是,所以现在我们必须寻找一种解决方法。

解决方案

那你该怎么办?我有两种解决方案:

  • 不要继承。 首先,对UIBezierPath进行子类化是一个坏主意。它不是为这种事情而做的。取而代之的是子类的,做一个 包装 -一个类或结构,而不是具有的功能, 一个UIBezierPath,有它的功能 一个UIBezierPath。我们称之为MyBezierPathWrapper:
    struct MyBezierPathWrapper {
    var selectedForLazo : Bool = false
    var bezierPath : UIBezierPath!
    

    }

这只是将您的自定义属性和方法与普通的UIBezierPath结合在一起。然后可以分两步创建它,如下所示:

    var b = MyBezierPathWrapper()
b.bezierPath = UIBezierPath(rect:CGRectZero)

如果感觉不满意,可以通过添加采用UIBezierPath的初始化程序,使其一步创建。

    struct MyBezierPathWrapper {
    var selectedForLazo : Bool = false
    var bezierPath : UIBezierPath
    init(_ bezierPath:UIBezierPath) {
        self.bezierPath = bezierPath
    }
}

现在,您可以像这样创建它:

    var b = MyBezierPathWrapper(UIBezierPath(rect:CGRectZero))
  • 具有便利构造函数的子类。 如果您坚持子类化,即使UIBezierPath并非用于此类事情,也可以通过提供便捷构造函数来实现。之所以可行,是因为UIBezierPath唯一重要的是它的CGPath,因此您可以使此便捷构造函数成为一个 复制 构造函数,而只是从真实的UIBezierPath传递路径:
    class MyBezierPath : UIBezierPath {
    var selectedForLazo : Bool! = false
    convenience init(path:UIBezierPath) {
        self.init()
        self.CGPath = path.CGPath
    }
    

    }

现在,我们可以创建一种与以前的方法非常相似的方法:

    let b = MyBezierPath(path:UIBezierPath(rect:CGRectZero))

这不是很好,但是我认为比在解决方案中必须重新定义 所有
初始化程序要令人满意的多。最后,我实际上以一种更加压缩的方式做着与您正在做的事情完全相同的事情。但总的来说,我更喜欢第一个解决方案:首先不要继承子类。

2020-07-07