简体   繁体   English

Swift新手-失败的初始化器

[英]New to Swift - failable initializers

I am trying to convert a Java class to Swift: 我试图将Java类转换为Swift:

// Card class with only a main function
public class Card {
// fields
private int numvalue;

// constructor(s)
public Card(int v) {
    if (v > 0 && v < 11) {
        this.numvalue = v;}
    else {
        System.out.println("Error: Card value is outside the allowed range.
        Value must be between 1 and 10 inclusive.")
    }

If I try to initialize a Card object using the constructor with a value that isn't between 1 and 10, the initialization fails, and a statement is printed in the Terminal explaining that the value was not within the acceptable range. 如果我尝试使用值不在1到10之间的构造函数初始化Card对象,则初始化失败,并且在终端上打印一条语句,说明该值不在可接受的范围内。 I tried to recreate this in Swift. 我试图在Swift中重新创建它。

class Card {
    var numValue : Int?
    init(numValue:Int) {
        if (numValue > 0 && numValue < 11) {self.numValue = numValue}
        else {print("Card value must be between 0 and 11")}
    }
    func setNumValue(value:Int) -> () {
        self.numValue = value
    }
    func getNumValue() -> Int {
        return self.numValue!
    }
}

The above works in the proper cases, but it appears to still create a Card instance even when an unacceptable value is passed, because I can still use the setNumValue(value:) method to change the value. 上面的方法在适当的情况下有效,但是即使传递了不可接受的值,它似乎仍会创建Card实例,因为我仍然可以使用setNumValue(value :)方法来更改值。

I tried making the init(numvalue:) method failable, but then I get an Optional(Card) if initialization is successful. 我尝试使init(numvalue :)方法失败,但如果初始化成功,则会得到Optional(Card)。 Ideally, I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. 理想情况下,我希望任何成功初始化的Card对象都是Card对象,而不是包含Card的Optional。 Is this possible? 这可能吗?

Well, you'll have to check the returned Optional whether it is nil or not, and then unwrap it into a non-Optional: 好了,您必须检查返回的Optional是否为nil,然后将其包装为非Optional:

class Card {
    var numValue : Int
    init?(numValue:Int) {
        guard (numValue > 0 && numValue < 11) else {
            print("Card value must be between 0 and 11")
            return nil
        }

        self.numValue = numValue
    }
    func setNumValue(value:Int) -> () {
        guard (numValue > 0 && numValue < 11) else {
            print("Card value must be between 0 and 11")
            return
        }
        self.numValue = value
    }
    func getNumValue() -> Int {
        return self.numValue
    }
}


let cOk = Card(numValue:1)
let cFails = Card(numValue:-1)

if let c = cOk {
    // now work with c because it's a Card,
    // not an Optional<Card> 
}

Ideally, I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. 理想情况下,我希望任何成功初始化的Card对象都是Card对象,而不是包含Card的Optional。 Is this possible? 这可能吗?

This is not possible with the standard initialization schemes available in Swift. 使用Swift中可用的标准初始化方案是不可能的。 You will ultimately end up with an Optional<Card> if your initialization should fail under certain conditions. 如果初始化在某些情况下会失败,则最终您将得到Optional<Card>


Failable Initialization 初始化失败

You can make your initializer failable by adding a ? 您可以通过添加?来使初始化程序失败? to the end of the init keyword. init关键字的末尾。 That is init? 那是init? .

This indicates that the initializer could fail (return nil). 这表明初始化程序可能失败(返回nil)。

In your initializer, you can then return nil when the conditions for creating your instance are not met. 在初始化器中,当不满足创建实例的条件时,可以返回nil。

In this case, I'm using a guard statement, but you could also use an if statement. 在这种情况下,我使用的是guard语句,但是您也可以使用if语句。

class Card {
    var numValue : Int?

    init?(numValue: Int) {
        guard numValue > 0 && numValue < 11 else {
            print("Card value must be between 0 and 11")
            return nil
        }
        self.numValue = numValue
    }

    func setNumValue(value:Int) -> () {
        self.numValue = value
    }

    func getNumValue() -> Int {
        return self.numValue!
    }
}

Since you are now returning an optional instance of Card , you can use if let to safely unwrap the optional and do something with the instance, or handle the fact that an instance was not created successfully (you have a nil). 由于现在返回的是Card的可选实例,因此可以使用if let安全地打开可选实例并对该实例执行某些操作,或者处理实例未成功创建的事实(您有nil)。

let card = Card(numValue: 12)
if let card = card {
    print(card)
} else {
    print("Unable to create a Card instance")
}

In the above example, the "Unable to create a Card instance" string would be printed (in addition to "Card value must be between 0 and 11"). 在上面的示例中,将打印“无法创建Card实例”字符串(除了“ Card value必须在0到11之间”)。

More information about how failable initialization works in Swift can be found here . 有关在Swift中失败的初始化如何工作的更多信息,请参见此处

If your object type doesn't need to persist you should use a Struct. 如果您的对象类型不需要保留,则应使用Struct。 BTW no need to make the value property a variable and create methods to just change its properties. 顺便说一句,无需将value属性设为变量并创建方法来更改其属性。 You just create a new Card object to replace it instead of changing the old one. 您只需创建一个新的Card对象来替换它,而不用更改旧的Card对象。

Your Card struct should look like this: 您的Card结构应如下所示:

struct Card {
    let value: Int
    init?(value: Int) {
        guard 1...10 ~= value else {
            print("Card value is out of range 1...10")
            return nil
        }
        self.value = value
    }
}

let card = Card(value: 2)       // returns an object of optional type (Card?)   "optional Card(value: 10)\n"
let nilCard = Card(value: 11)   // valus out of the range 1...10 will return nil

if let card = Card(value: 10)  {
    print(card)   // "Card(value: 10)\n"
}

I'd like for any successfully initialized Card object to be a Card object, not an Optional containing a Card. 我希望将任何成功初始化的Card对象作为Card对象,而不是包含Card的Optional。 Is this possible? 这可能吗?

Yes it is possible. 对的,这是可能的。 Just add a precondition to your initializer and make it non failable: 只需在您的初始化程序中添加一个前提条件,并使它不可失败:

struct Card {
    let value: Int
    init(value: Int) {
        precondition(1...10 ~= value, "Card value is out of range 1...10")
        self.value = value
    }
}


let card = Card(value: 2)                // returns an object of type (Card)   "Card(value: 2)\n"
let cantInitCard = Card(value: 11)       // won't execute (precondition not met range 1...10)

You can also add a second parameter range (optional because you can set a default value) for your card initializer and add a range property to your card so you can check whats your possible card values: 您还可以为卡初始化程序添加第二个参数范围(可选,因为可以设置默认值),并向卡添加range属性,以便您可以检查可能的卡值:

struct Card {
    let value: Int
    let range: ClosedRange<Int>
    init(value: Int, range: ClosedRange<Int> = 1...10) {     // you can set a default range for yor cards
        precondition(range ~= value, "Card value is out of range: " + range.description)
        self.value = value
        self.range = range
    }
}

let card = Card(value: 5)       // returns an object of type (Card) "Card(value: 5, range: 1...10)\n"
print(card.value, card.range)   // "5 1...10\n"

If you would like to have a card with values with a different range 1...13 just pass the range 如果您想要一张具有不同范围1 ... 13的值的卡,只需通过该范围

let newCard = Card(value: 11,  range: 1...13)    // ok to execute (precondition met range 1...13) 
print(newCard.value, newCard.range)              // "11 1...13\n"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM