UIBezierPath子类初始化程序

din*_*hil 3 inheritance subclass ios 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")
   }
}
Run Code Online (Sandbox Code Playgroud)

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

var path = MyUIBezierPath(roundedRect: rect, cornerRadius: 7)
Run Code Online (Sandbox Code Playgroud)

并导致编译错误:

"必须调用超类'UIBezierPath'的指定初始值设定项"

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

你能帮我吗?

mat*_*att 8

注意:此问题在iOS 9中已得到解决,其中API已被重写以便init(rect:)存在,所有其他API 也是如此,因为它们应该是方便初始化器.


问题

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

    class MyBezierPath : UIBezierPath {}
    let b = MyBezierPath(rect:CGRectZero)
Run Code Online (Sandbox Code Playgroud)

从斯威夫特的角度来看,这似乎是错误的.文档似乎说UIBezierPath有一个初始化程序init(rect:).但那么为什么UIBezierPath init(rect:)不会在我们的子类MyBezierPath中继承?根据初始化器继承的正常规则,它应该是.

说明

UIBezierPath不适用于子类.因此,它没有任何初始化器 - 除了init()它,它继承自NSObject.在Swift中,UIBezierPath 看起来好像有初始化器; 但这是一种虚假的表现形式.UIBezierPath实际上有什么,正如我们可以看到的,如果我们看看Objective-C头,是方便构造函数,它们是类方法,例如:

+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;
Run Code Online (Sandbox Code Playgroud)

现在,这个方法(以及它的兄弟姐妹)演示了Swift没有很好处理的一些不寻常的功能:

  • 它不仅仅是初始化器的变体; 它是一个纯粹的便利构造函数.Objective-C向我们展示了UIBezierPath没有相应的真实初始化器initWithRect:.这在Cocoa中是一个非常不寻常的情况.

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

斯威夫特对这种情况严重妥协.一方面,它根据其通常的策略将类方法bezierPathWithRect:转换为明显的初始化方法init(rect:).但另一方面,这不是一个"真正的"初始化器,也不能被子类继承.

因此,你已经被明显的初始化程序误导了init(rect:),然后当你无法在你的子类上调用它时会感到惊讶和难过,因为它不是继承的.

注意:我不是说Swift在这里的行为不是一个bug; 我认为这一个错误(虽然我对是否要责怪Swift或UIBezierPath API上的错误有点朦胧).Swift不应该bezierPathWithRect:变成初始化器,或者,如果它确实使它成为初始化器,它应该使初始化器可以继承.无论哪种方式,它都应该是可继承的.但事实并非如此,所以现在我们必须寻找一种解决方法.

解决方案

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

  • 不要子类.对UIBezierPath进行子类化是一个糟糕的主意.它不是为了这种事情而制造的.取而代之的是子类的,做一个包装 -一个类或结构,而不是具有的功能,一个UIBezierPath,有它的功能一个UIBezierPath.我们称之为MyBezierPathWrapper:

    struct MyBezierPathWrapper {
        var selectedForLazo : Bool = false
        var bezierPath : UIBezierPath!
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    var b = MyBezierPathWrapper()
    b.bezierPath = UIBezierPath(rect:CGRectZero)
    
    Run Code Online (Sandbox Code Playgroud)

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

    struct MyBezierPathWrapper {
        var selectedForLazo : Bool = false
        var bezierPath : UIBezierPath
        init(_ bezierPath:UIBezierPath) {
            self.bezierPath = bezierPath
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    var b = MyBezierPathWrapper(UIBezierPath(rect:CGRectZero))
    
    Run Code Online (Sandbox Code Playgroud)
  • 具有便利构造函数的子类.如果你坚持继承子类,即使UIBezierPath不适用于那种事情,你也可以通过提供一个方便的构造函数来实现.这是有效的,因为关于UIBezierPath唯一重要的是它CGPath,所以你可以使这个方便构造函数成为一个复制构造函数,只是从真正的UIBezierPath传递路径:

    class MyBezierPath : UIBezierPath {
        var selectedForLazo : Bool! = false
        convenience init(path:UIBezierPath) {
            self.init()
            self.CGPath = path.CGPath
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    let b = MyBezierPath(path:UIBezierPath(rect:CGRectZero))
    
    Run Code Online (Sandbox Code Playgroud)

    它不是很好,但我认为它比你的解决方案重新定义所有初始化器更令人满意.最后,我真的以更加压缩的方式做着你正在做的事情.但总的来说,我更喜欢第一种解决方案:首先不要进行子类化.