初始化顺序

与 Objective-C 不同,Swift 的初始化方法需要保证类型的所有属性都被初始化。所以初始化方法的调用顺序就很有讲究。一般来说,子类的初始化顺序是:

  1. 设置子类自己需要初始化的参数,

  2. 调用父类的相应的初始化方法,

  3. 对父类中的需要改变的成员进行设定

其中第三步是根据具体情况决定的,如果我们在子类中不需要对父类的成员做出改变的话,就不存在第 3 步。而在这种情况下,Swift 会自动地对父类的对应 init 方法进行调用,也就是说,第 2 步的 super.init() 也是可以不用写的 (但是实际上还是调用的,只不过是为了简便 Swift 帮我们完成了)。

Designed,Convenience 和 Required

Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。

class ClassA {
    let numA : Int
    init(num: Int) {
        numA = num
    }
}

class ClassB: ClassA {
    let numB: Int

    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}

在上面的示例代码中,注意在 init 里我们可以对 let 的实例常量进行赋值,这是初始化方法的重要特点。在 Swift 中 let 声明的值是常量,无法被写入赋值,这对于构建线程安全的 API 十分有用。而因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为常量进行赋值,而不会引起任何线程安全的问题。

designated 初始化方法对应的是在 init 前加上covenience 关键字的初始化方法。这类方法是 Swift 初始化方法中的 “二等公民”,只作为补充和提供使用上的方便。所有的 covenience 初始化方法都必须调用同一个类中的 designated 初始化完成设置,另外 covenience 的初始化方法是不能被子类重写或者是从子类中以 super 的方式被调用的。

class ClassA {
    let numA : Int
    init(num: Int) {
        numA = num
    }

    convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 1000 : 1)
    }
}

class ClassB: ClassA {
    let numB: Int

    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}

只要在子类中实现重写了父类 covenience 方法所需要的 designed init 方法的话,我们在子类中就也可以使用父类的 covenience 初始化方法了。比如在上面的代码中,我们在 ClassB 里实现了 init(num: Int) 的重写。这样,即使在 ClassB 中没有 bigNum 版本的 convenience init(bigNum: Bool) ,我们仍然还是可以用这个方法来完成子类初始化:

let b = ClassB(bigNum: true)

因此进行一下总结,可以看到初始化方法永远遵循以下两个原则:

  1. 初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法来得到保证;

  2. 子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。

对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 covenience 一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool) 如果我们希望这个初始化方法对于子类一定可用,那么应当将 init(num: Int) 声明为必须,这样我们在子类中调用 init(bigNum: Bool) 时就始终能够找到一条完全初始化的路径了:

class ClassA {
    let numA : Int
    required init(num: Int) {
        numA = num
    }

    convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 1000 : 1)
    }
}

class ClassB: ClassA {
    let numB: Int

    required init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}

其实不仅仅是对 designated 初始化方法,对于 convenience 的初始化方法, 我们也可以加上 required 以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时会非常有帮助

初始化返回 nil

在 Swift 中默认情况下初始化方法是不能写 return 语句来返回值的,也就是说我们没有机会初始化一个 Optional 的值。在 Swift 1.0及之前的环境下的解决方法是使用工厂模式,也就是写一个类方法来生成和返回实例,或者在失败的时候返回 nil。Swift 的 NSURL 就做了这样的处理:

class func URLWithString(URLString: String!) -> Self!

使用的时候:

let url = NSURL.URLWithString("ht tp://swifter。tips") 
print(url)  // nil

不过虽然可以用这种方式来和原来一样返回 nil ,但是这也算是一种折衷。在可能的情况下,我们还是应该倾向于尽量减少出现 Optional 的可能性,这样更有助于代码的简化。

如果你确实想使用初始化方法而不愿意用工厂函数的话,也可以考虑用一个 Optional 量来存储结果.

这样你就可以处理初始化失败了,不过相应的代价是代码复杂度的增加:

let url: NSURL? = NSURL(string: "ht tp://swifter。tips") 
// nil

在 Swift 1.1 中 Apple 已经为我们加上了初始化方法中返回 nil 的能力。我们可以在 init 声明时在其后加上一个 ? 或者 ! 来表示初始化失败时可能返回 nil。比如为 Int 添加一个接收 String 作为参数的初始化方法。我们希望在方法中对中文和英文的数据进行解析,并输出 Int 结果。对其解析并初始化的时候,就可能遇到初始化失败的情况:

extension Int {
    init?(fromString: String) {
        self = 0
        var digit = fromString.characters.count - 1
        for c in fromString.characters {
            var number = 0
            if let n = Int(String(c)) {
                number = n
            }
            else {
                switch c {
                case "一":
                    number = 1
                case "二":
                    number = 2
                case "三":
                    number = 3
                case "四":
                    number = 4
                case "五":
                    number = 5
                case "六":
                    number = 6
                case "七":
                    number = 7
                case "八":
                    number = 8
                case "九":
                    number = 9
                case "零":
                    number = 0
                default:
                    return nil
                }

            }
            self = self + number * Int(pow(10, Double(digit)))
            digit = digit - 1
        }
    }
}
        let number1 = Int(fromString: "12");
        print(number1)
        let number2 = Int(fromString: "三二五");
        print(number2)
        let number3 = Int(fromString: "七9八");
        print(number3)
        let number4 = Int(fromString: "吃了吗");
        print(number4)
        let number5 = Int(fromString: "1a4n");
        print(number5)

        //Optional(12)
        //Optional(325)
        //Optional(798)
        //nil
        //nil

所有的结果都将是 init? 类型,通过 Optional Binding,我们就能知道初始化是否成功,并安全地使用它们了。我们在这类初始化方法中还可以对 self 进行赋值,也算是 init? 方法里的特权之一。

同时像上面例子中的 NSURL.URLWithString 这样的工厂方法,在 Swift 1.1 中已经不再需要。为了简化 API 和安全,Apple 已经被标记为不可用了并无法编译。而对应地,可能返回 nilinit 方法都加上了 ? 标记:

convenience init?(string URLString: String)

在新版本的 Swift 中,对于可能初始化失败的情况,我们应该始终使用可返回 nil 的初始化方法,而不是类型工厂方法。

results matching ""

    No results matching ""