Swift专题Swift首页投稿(暂停使用,暂停投稿)

12-Swift构造过程(Initialization)

2016-06-26  本文已影响293人  EndEvent

 构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
 通过定义构造器来实现构造过程,构造器即是创建特定类型实例的特殊方法。与OC构造器不同,swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

一、存储属性的初始赋值


 类和结构体在创建实例时,必须为所有存储类型属性设置合适的初始值。存储类型属性的值不能处于未知状态。

注意:当为存储类型属性设置默认值或在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。

class Cat {
    var name:String;
    var color:String;
    // 如果缺少如下构造过程,代码是会报错的,因为name和color没有初始值
    init() {    // 在此执行构造过程
        print("开始构造");
        name = "cat";  // 即设置默认值
        color = "black";  // 即设置默认值
    }
}
// 默认就是调用init()构造器
var myCat = Cat();
print("\(myCat.name) - \(myCat.color)");
输出结果:
开始构造
cat - black
class Cat {
    // 声明时,直接设置默认值
    var name:String = "cat";
    var color:String = "red";
}

二、自定义构造过程


 可以通过传入参数和可选属性类型来自定义构造过程,也可以在构造过程中修改常量属性。

// 自定义构造过程
class Cat {
    var name:String?;
    var color:String?;
    // 自定义构造器1
    // 构造参数: 外部参数名fromName,内部参数名name
    init(fromName name:String) {
        print("初始化名字");
        self.name = name;
    }
    // 自定义构造器2
    init(fromColor color:String) {
        print("初始化颜色");
        self.color = color;
    }
}
// 实例化
let myCat1 = Cat(fromName:"笨笨");
print(myCat1.name!);
let mycat2 = Cat(fromColor:"白色");
print(mycat2.color!);
输出结果:
初始化名字
笨笨
初始化颜色
白色
struct CustomColor {
    // 颜色的三原色,都是Double类型
    let red, green, blue: Double;
    // 构造器
    init(red:Double, green:Double, blue:Double) {
        self.red = red;
        self.green = green;
        self.blue = blue;
    }
}
// 必须要有外部名称,这里系统会报错
//let testColor = CustomColor(1.0, 0.0, 1.0);
// 注意: 没有外部参数,系统会自动生成与内部参数名一样的
let magenta = CustomColor(red: 1.0, green: 0.0, blue: 1.0);
struct CustomColor {
    // 颜色的三原色,都是Double类型
    let red, green, blue: Double;
    // 使用下划线`_`来显示描述外部参数名
    init(_ red:Double,_ green:Double,_ blue:Double) {
        self.red = red;
        self.green = green;
        self.blue = blue;
    }
}
// 不带外部参数名的构造器,调用时候跟简洁
let magenta = CustomColor(1.0, 0.0, 1.0);
// 基类: 汽车类
class Car {
    // 速度
    var currentSpeed = 0.0;
    // 车型别名
    let alias:String;
    // 车型颜色
    let color:String;
    // 构造器
    init(_ alias:String, _ color:String) {
        self.alias = alias;
        self.color = color;
    }
}
var myLamborghini = Car("Lamborghini","白色");
print("\(myLamborghini.alias) - \(myLamborghini.color)");
// 兰博基尼Lamborghini
// 该类继承Car
class Lamborghini:Car {
    // 以下构造器是错误的,不能在子类中修改!!!
    init(_ alias:String) {
        self.alias = alias;
    }
}

三、默认构造器


 如果结构体和类所有属性都有默认值,而且也没有自定义构造器,那么swift会自动给结构体和类创建一个默认的构造器。

class Cat {
    var name:String?;
    // 没有自定义构造器,系统将自动生成一个为所有属性设置默认值的默认构造器
    // 而属性name,是可选的,系统将自动设置默认为`nil`
}
let myCat = Cat();
struct Size {
    // 注意: 是提供了`默认值` 并且 `没有提供定制的构造器`,系统将自动添加一个成员逐一构造器
    var width = 0.0, height = 0.0;
}
// 参数名与成员属性名相同
let test = Size(width:30, height: 30);

四、值类型的构造器代理


构造器代理即是构造器通过调用其他构造器来完成实例的部分构造过程,这样能减少多个构造器间的代码重复。
 构造器代理的实现规则与形式在值类型和类类型不同。值类型(结构体和枚举类型)是不支持继承的,它们只能代理给本身提供的其他构造器。类则不同,它可以继承自其他类(参考继承),这意味该类有必要保证其所有继承的存储类型属性在构造时也能正确初始化。
 值类型,可以使用self.init在自定义构造器中引用其他的属于相同值类型的构造器,但只能在构造器内部调用self.init
 如果在值类型中自定义了构造器,那么将无法访问默认构造器(如果是结构体,即无法访问成员逐一构造器,参考本章节中三的内容)。
 备注:如果你有iOS基础,下面其实就是设置视图大小以及位置相关的几个结构体。

// 矩形大小结构体
struct Size {
    // 可以访问成员逐一构造器
    var width = 0.0, height = 0.0;
}

// 矩形中心点位置
struct Point {
    // 可以访问成员逐一构造器
    var x = 0.0, y = 0.0;
}

// 矩形的大小以及位置
struct Rect {
    // 位置
    var origin = Point();
    // 大小
    var size = Size();
    
    // 构造器1
    init(origin:Point, size:Size) {
        self.origin = origin;
        self.size = size;
    }
    // 构造器2
    init(center:Point, size:Size) {
        let originX = center.x - (size.width / 2);
        let originY = center.y - (size.height / 2);
        let tempOrigin = Point(x:originX, y: originY);
        // 使用`self.init`调用其他的构造器
        self.init(origin:tempOrigin, size: size);
    }
}
/**
  矩形大小(100,100),位置(50,50)
  其矩形的中心点为位置是(50+100/2, 50+100/2)
*/
// 位置: 调用成员逐一构造器,默认构造器
let origin = Point(x:50, y:50);
// 大小: 调用成员逐一构造器,默认构造器
let size = Size(width:100, height: 100);

// 写法一
let rect1 = Rect(origin:origin, size:size);
// 写法二
// 中心点位置
let center = Point(x: 50+100/2, y: 50+100/2);
let rect2 = Rect(center: center, size: size);

五、类的继承和构造过程


 类里面的所有存储型属性,包括继承自父类的属性,都必须在构造过程中设置初始值。
 swift提供了两种类型的类构造器来确保所有类实例中的存储型属性能获得初始值,分别为指定构造器便利构造器
指定构造器是类中最主要的构造器,一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。另外每个类都必须拥有至少一个指定构造器,大多数情况下,类都通过继承了父类中的指定构造器而满足了这个条件。
便利构造器是类中比较次要的、辅助型的构造器。可以定义便利构造器来调用同一类中的指定构造器,并为其参数提供默认值,也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

init(parameters) {
  statements
}
convenience init(parameters) {
  statements
}



两段式构造过程。在swift中类的构造过程包括两个阶段,第一个阶段,每个存储类型属性通过引入它们的类构造器设置初始值。当每个存储属性值确定后,第二阶段开始,它给每个类一个机会在新实例准备使用之前,再进一步定制它的存储类型属性。
 两段式构造器过程的使用栏构造过程更为安全,同时在整个类层次中也更为灵活。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外赋予不同的值。

备注: 两段式构造过程以及安全检查只做了解即可,了解系统的实例化过程。

// 类: 人
class Person {
    var name:String?
    var description:String {
        return "name:\(name!)";
    }
    // 指定构造器
    init () {
        // 初始化操作
        name = "";
    }
}
// 类: 学生,继承Person
class Student:Person {
    // 学号
    var sid:Int?;
    // 重写属性
    override var description: String {
        return "格式:" + super.description + "  sid:\(sid!)"
    }
    
    // 重写指定构造器,需要`override`修饰
    override init() {
        // 先调用父类的指定构造器,确保`name`属性已经被初始化
        super.init();
        // 再修改属性
        name = "StudentName";
        sid = 100_000_000_001;
    }
}
// 实例化学生类
var zhansan = Student();
print(zhansan.description);

注意: 子类可以在初始化时修修改继承来的变量属性,但不能修改该继承过来的常量属性。

六、可失败构造器


 当一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,那么为其定义一个可失败构造器,是非常有用的。所谓的"失败"是指,如给构造器传入无效的参数值,或缺少某种所需要的外部资源,又或者不满足某种必要的条件等。你可以添加一个或多个可失败构造器,其语法是init?,另外通过return nil语句,来表名可失败构造器在某种情况下是"失败"。

可失败构造器的参数名和类型,不能与其非失败构造器的参数名以及类型相同。
严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象自身被正确的构造器,所以即使在表明可失败构造器,失败的这种情况下,用到了return nil

class Foot {
    var name:String
    // 可失败构造器
    init?(name:String) {
        if name.isEmpty {
            // 表示"失败"
            return nil;
        }
        self.name = name;
    }
}
// 实例化Foot对象,并检查构造是否成功
// foot1类型是Foot?,而不是Foot
let foot1 = Foot(name:"bread");
if let temp = foot1 {
    print("foot1构造成功:\(foot1!.name)");
}
// 传入一个空值,构造失败
let foot2 = Foot(name:"");
if let temp = foot2 {
    print("foot2构造成功:\(foot2!.name)");
} else {
    print("foot2构造失败");
}
输出结果:
foot1构造成功:bread
foot2构造失败
// 加减乘除枚举
enum ArithmeticSign {
    // 对应加减乘除
    case Add, Subtract, Multiply, Divide;
    // 可失败构造器
    init?(sign:String) {
        switch sign {
        case "+":
            self = .Add;
        case "-":
            self = .Subtract;
        case "*":
            self = .Multiply;
        case "/":
            self = .Divide;
        default:    // 否则即是"失败"
            return nil;
        }
    }
}
// 枚举1
let sign1 = ArithmeticSign(sign: "+");
if sign1 != nil {
    print(sign1!);
}
// 枚举2
let sign2 = ArithmeticSign(sign: "~");
if sign2 == nil {
    print("sign2构造失败");
}
输出结果:
Add
sign2构造失败
// 加减乘除枚举
enum ArithmeticSign:Character {
    // 对应加减乘除
    case Add="+", Subtract = "-", Multiply = "*", Divide = "/";
}
// 枚举1
let sign1 = ArithmeticSign(rawValue: "+");
if sign1 != nil {
    print(sign1!);
}
// 枚举2
let sign2 = ArithmeticSign(rawValue: "~");
if sign2 == nil {
    print("sign2构造失败");
}
// 食物类
class Foot {
    // 注意是常量
    let name:String;
    // 可失败构造器
    init?(name:String) {
        self.name = name;
        
        if name.isEmpty {
            // 表示"失败"
            return nil;
        }
    }
}
// 面包类
class Bread:Foot {
    let quantity:Int;
    // 可失败构造器
    init?(name:String, quantity:Int) {
        // 赋值
        self.quantity = quantity;
        // 调用父类的构造方法
        super.init(name: name);
        
        if quantity < 1 {
            // "失败"
            return nil;
        }
    }
}
// 实例1
if let bread1 = Bread(name:"麦香面包", quantity: 1) {
    print("\(bread1.name):\(bread1.quantity)");
}
// 实例2
if let bread2 = Bread(name:"麦香面包", quantity: 0) {
    print("\(bread2.name):\(bread2.quantity)");
} else {    // Bread触发构建失败行为
    print("bread2构建失败");
}
// 实例3
if let bread3 = Bread(name:"", quantity: 1) {
    print("\(bread3.name):\(bread3.quantity)");
} else {    // Foot触发构建失败行为,但整个Bread同样还是失败的
    print("bread3构建失败");
}
输出结果:
麦香面包:1
bread2构建失败
bread3构建失败
class Foot {
    // 注意是变量
    var name:String?;
    // 指定构造器,构建name为nil
    init () {}
    // 可失败构造器,构建name为非空字符串
    init?(name:String) {
        self.name = name;
        
        if name.isEmpty {
            // 表示"失败"
            return nil;
        }
    }
}
// 面包类
class Bread:Foot {
    // 重写指定构造器
    override init() {
        // 调用父类的指定构造器
        super.init();
        // 再赋值
        name = "[untitled]";
    }
    // 重写一个可失败构造器
    override init(name: String) {
        // 调用父类的指定构造器,即先做初始化相关操作
        super.init();
        if name.isEmpty {
            self.name = "[untitled]";
        } else {
            self.name = name;
        }
    }
}
let bread1 = Bread(name:"");
print(bread1.name!);
let bread2 = Bread(name:"奶油面包");
print(bread2.name!);

七、必要构造器


 在类的构造器前添加required修饰符表名所有该类的子类都必须实现该构造器:

// 基类,书写格式
class SomeClass {
  required init() {
    // 在这里添加该必要构造器的实现代码
  }
}
// 子类,书写格式
class SomeSubCalss: SomeClass {
  // 子类重写父类的必要构造器时,必须添加`required`,为了保证继承链上子类的构造器也是必要的构造器。另外这里不需要添加`override`修饰符
  required init() {
    // 在这里添加子类必要构造器的实现代码
  }
}

注意: 如果子类继承的构造器能满足必要构造器的需求,则不需要再子类中提供必要构造器的实现。

八、通过闭包和函数设置属性的默认值


 如果某个存储类型属性的默认值需要特定的定制操作,那么可以使用闭包或全部函数来为该属性提供定制的默认值。每当某个属性所属的新类型创建时,对应的闭包或函数就会被调用,而它们的返回值会当做默认值赋值给这个属性。
  闭包或函数一般会创建与属性类型相同的临时变量,然后修改它的值以满足预期初始状态,最后将这个临时变量的值作为属性的默认值返回即可:

// 格式
class SomeClass {
  let someProperty:SomeType = {
    // 在这里闭包中给someProperty创建一个默认值
    // someValue必须和SomeType类型相同
    return someValue;
  }();  // 这里后面跟着括号,表示需要立即执行此闭包操作
}
// 猫类
class Cat {
    // 猫的颜色,通过闭包产生随机颜色
    var color:UIColor = {
        let randomRed = Float(arc4random_uniform(255)) / 255.0;
        let randomGreed = Float(arc4random_uniform(255)) / 255.0;
        let randomBlue = Float(arc4random_uniform(255)) / 255.0;
        
        let tempColor = UIColor(colorLiteralRed: randomRed, green:randomGreed, blue:randomBlue, alpha:1.0);
        return tempColor;
    }();
}
// 实例化
let myCat = Cat();
// 颜色是随机颜色
print(myCat.color);

注意: 如果你使用闭包来初始化属性的值,但在闭包执行时,实例的其他部分都还没有初始化,即意味此时还不能够在闭包里访问其他的属性,就算这个属性有默认值也不允许,另外也不能使用隐式的self属性,或调用其他的实例方法。


注:xcode7.3环境

上一篇 下一篇

猜你喜欢

热点阅读