Swift 中的类

Swift的类class

作为一门面向对象语言,类也是Swift的非常重要的类型,我们先来看下一个简单的类

// Swift中一个类可以不继承于任何其他基类,那么此类本身就是一个基类
class Person {
    // 定义属性
    var name :String = ""
    var height = 0.0
    
    // 构造器方法,注意如果不编写构造方法默认会自动创建一个无参构造方法
    init(name :String) {
        self.name = name
    }
    
    // 析构器方法,在对象被释放时调用,
    // 类似于ObjC的dealloc,注意这里没有括号和参数,无法直接调用
    deinit {
        print("deinit...")
    }
    
    // 定义对象方法
    func showMessage() {
        print("name=\(name),height=\(height)")
    }
}

// 创建类的对象
var p = Person(name: "奥格瑞姆·毁灭之锤")
p.height = 330.0
p.showMessage()

// 类是引用类型
var p2 = p
p2.name = "布莱克汉"
print(p.name)  // return:"布莱克汉"

// “===”表示等价于,这里不能使用等于“==”,等于用于比较值相等,p和p2是不同的值,但是指向的对象相同
if p === p2 {
    print("p===p2")  // p等价于p2,表示二者指向同一个对象
}

属性

Swift中淡化了成员属性的概念,把属性分为两种:
存储属性:

无论从概念上还是定义方式上来看,存储属性更像其他语言中的成员变量,但是不同的是:
可以控制读写操作(var表示可读可写,let表示只读)
通过属性监视器来属性的变化(willSet、didSet)
快速实现懒加载功能(lazy修饰)

计算属性:

计算属性并不直接存储一个值,而是提供getter来获取一个值,或者利用setter来间接设置其他属性。

下面是对象属性使用实例:

class Account {
    // 定义存储属性,设置默认值,添加属性监听器
    var balance:Double = 0.0 {
        // 即将赋值时调用
        willSet {
            // 注意此时修改balance的值没有用
            self.balance = 2.0
            // newValue表示即将赋值的新值,并且在属性监视器内部调用属性不会引起监视器循环调用
            print("Account.balance willSet,newValue=\(newValue),value=\(self.balance)")
        }
        // 赋值完成后调用
        didSet {
            // 注意此时修改balance的值将作为最终结果
            self.balance = 3.0
            // oldValue表示已经赋值的旧值,并且在属性监视器内部调用属性不会引起监视器循环调用
            print("Account.balance didSet,oldValue=\(oldValue),value=\(self.balance)")
        }
    }
}

class Person {
    // firstName、lastName、age是存储属性
    var firstName:String = ""   // var定义表示该存储属性可读可写
    var lastName:String = ""
    var age:Int = 0             // let定义表示该存储属性只读
    
    // 属性的懒加载,第一次访问才会初始化
    lazy var account = Account()  // 在Swift中懒加载的属性不一定就是对象类型,也可以是基本类型
    
    // fullName是一个计算属性,在get、set方法中不能直接访问计算属性,否则会引起循环调用
    var fullName:String {
        // 获取该计算属性时调用
        get {
            return firstName + "." + lastName
        }
        // 设置该计算属性时调用
        set {
            // set方法中的newValue表示即将赋值的新值,这里是分割字符串
            let array = newValue.componentsSeparatedByString(".")
            if array.count == 2 {
                firstName = array[0]
                lastName = array[1]
            }
        }
    }
    
    // 构造器方法,注意如果不编写构造方法默认会自动创建一个无参构造方法
    init(firstName:String,lastName:String,age:Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
    }
    
    // 定义方法
    func showMessage() {
        print("name = \(self.fullName),age = \(self.age)")
    }
}

// 创建对象
var p = Person(firstName: "安杜因", lastName: "洛萨", age: 22)
p.showMessage()  //return:name=安杜因.洛萨,age=22
p.fullName = "安杜因.洛萨"
p.showMessage()

p.account.balance = 10

/* 下面是存储属性监听器方法打印:
Account.balance willSet,newValue=10.0,value=2.0
Account.balance didSet,oldValue=0.0,value=3.0
*/
print("p.account.balance=\(p.account.balance)") // return:3.0

1.计算属性并不直接存储一个值,而是提供getter来获取一个值,或者利用setter来间接设置其他属性;
2.lazy属性必须有初始值,必须是变量不能是常量,因为常量在构造完成之前就已经确定了值;
3.在构造对象方法之前存储属性必须有值(可选类型除外),无论是变量属性还是常量属性,这个值既可以在属性创建时指定,也可以在构造方法内指定;
4.存储属性的默认值设置不会引起属性监视器的调用(另外在构造方法中赋值也不会引起属性监视器调用),只有在外部设置存储属性才会引起属性监视器调用;
除了上面的对象属性外,我们还可以定义类的属性:

class Student {
    // 定义类的属性只需要在前面加static关键字即可,也是分为存储属性和计算属性,这里是计算属性
    static var skin:Array<String> {
        // 只定义了getter,表示该计算属性是只读的
        get {
            return ["yellow", "white", "black"]
        }
    }
}

// 读取类的属性
for color in Student.skin {
    print(color)
}

方法

Swift的类中的方法可分为以下几个类别:
构造器方法
默认构造器方法(当存在有参数的指定构造器方法,被覆盖)
指定构造器方法
便利构造器方法
**析构器方法 **
**对象方法 **
**类方法 **
以下是方法使用实例:

class Person {
    // 定义属性
    var name:String = ""
    var height:Double = 0.0
    var age = 0
    
    // 指定构造器方法,注意如果不编写构造方法,默认会自动创建一个无参构造方法
    init(name:String, height:Double,age:Int) {
        self.name = name
        self.height = height
        self.age = age
    }
    
    // 便利构造方法,通过调用指定构造方法、提供默认值来简化构造方法实现
    convenience init(name:String) {
        self.init(name:name,height:0.0,age:0)
    }
    
    // 析构方法,在对象被释放时调用,注意此方法没有括号,没有参数,无法直接调用
    deinit {
        print("deinit...")
    }
    
    // 对象方法
    func modifyInfoWithAge(age:Int,height:Double) {
        self.age = age
        self.height = height
    }
    
    // 类方法,使用class修饰的方法即是类方法
    class func showClassName() {
        print("Class name is Person")
    }

}

// 通过便利构造方法创建对象
var p = Person(name: "Bobby")
// 调用对象方法
p.modifyInfoWithAge(22, height: 170)
// 调用类方法
Person.showClassName()

除构造方法、析构方法外,其他方法的参数默认除了第一个参数是局部参数,从第二个参数开始既是局部参数又是外部参数。但是,对于函数,默认情况下只有默认参数既是局部参数又是外部参数,其他参数都是局部参数。
构造方法的所有参数默认情况下既是外部参数又是局部参数
只有便利构造器方法才能调用当前类的指定构造方法
有参数的指定构造器方法会覆盖调用默认的无参构造器方法
一个对象被释放前,先自动调用自己的析构方法,然后一层一层往上调用父类的析构方法

下标脚本

下标脚本是一种访问集合的快捷方式,如果我们自定义的类具有集合类型的功能,我们可以定义下标脚本来快捷访问该类属性,定义下标脚本是通过关键字subscript进行的。

class Score {
    var english:Int = 0
    var chinese:Int = 0
    var math:Int = 0
    
    // 定义下标脚本
    subscript(index:Int) ->Int {
        get {
            switch index {
            case 0:
                return english
            case 1:
                return chinese
            case 2:
                return math
            default:
                return 0
            }
        }
        
        set  {
            english = newValue
            chinese = newValue
            math = newValue
        }
    }
}

var myScore = Score()
var sum:Int = 0
var i:Int = 0

for i=0;i<3;++i {
    sum += myScore[i]
}
print(sum)  // return:0

// 修改属性值
myScore[0] = 100
myScore[1] = 90
myScore[2] = 80

for i=0;i<3;++i {
    sum += myScore[i]
}
print(sum)  // return:240

以下就使用只读的形式实现使用下标访问属性值的功能。代码如下:

// 以下就使用只读的形式实现使用下标访问属性值的功能。代码如下:
class Score {
    var english:Int = 50
    var chinese:Int = 100
    var math :Int = 30
    
    subscript(index:Int) ->Int {
        switch index {
        case 0:
            return english
        case 1:
            return chinese
        case 2:
            return math
        default:
            return 0
        }
    }
}

var myScore = Score()
var sum:Int = 0
var i:Int = 0
for i=0;i<3;++i {
    print(myScore[i])
}

// 运行结果如下:50 100 30

具有多个参数的下标脚本

// 具有多个参数的下标脚本
// 具有一个入参参数的下标脚本一般使用在多维维数组中。以下就是使用具有两个参数的下标为二维数组赋值。
var value:Int = 0
class NewClass1 {
    var rows:Int=0,columns:Int=0
    var grid:[Double]
    
    init(rows:Int,columns:Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(count: rows * columns, repeatedValue: 0.0)
    }
    
    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    
    subscript(row:Int,column:Int)->Double {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

var matrix = NewClass1(rows: 2, columns: 2)
print("没有赋值前")
print(matrix[0,0])   // 0.0
print(matrix[0,1])   // 0.0
print(matrix[1,0])   // 0.0
print(matrix[1,1])   // 0.0

print("赋值后")
matrix[0,0]=1.0
matrix[0,1]=5.6
matrix[1,0]=2.4
matrix[1,1]=3.2
print(matrix[0,0])   // 1.0
print(matrix[0,1])   // 5.6
print(matrix[1,0])   // 2.4
print(matrix[1,1])   // 3.2

标脚本除了可以对访问对象以及对象中的属性外,还可以实现一些自定义的功能

// 下标脚本除了可以对访问对象以及对象中的属性外,还可以实现一些自定义的功能,如以下的代码,此代码实现的功能是计算下标值和10的乘积。代码如下:

class NewsClass2 {
    var count1:Int = 10
    subscript(index:Int) ->Int {
        get {
            let count = index * count1
            return count
        }
        set {
            count1 = newValue // 执行赋值操作
        }
    }
}

let newClass = NewsClass2()
print(newClass.count1)  // return:10
print(newClass[6])      // return:60

继承

和ObjC一样,Swift也是单继承的(可以实现多个协议,此时协议放在后面),子类可以调用父类的属性、方法,重写父类的方法,添加属性监视器,甚至可以将只读属性重写成读写属性。

// 定义一个父类
class Person {
    var firstName:String
    var lastName:String
    var age:Int = 0
    var fullName:String {
        get {
            return firstName + "." + lastName
        }
    }
    
    // 指定构造方法
    init(firstName:String,lastName:String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    
    // 对象方法
    func showMessage() {
        print("name=\(self.fullName),age=\(self.age)")
    }
    
    // 通过final声明,子类无法重写
    final func sayHello() {
        print("Hello World.")
    }
    
}

// 定义子类
class Student :Person {
    // 重写属性,为属性添加监视器,重写都需要加override关键字
    override var firstName:String {
        willSet {
            print("firstName willSet")
        }
        didSet {
            print("firstName didSet")
        }
    }
    
    // 添加子类属性
    var score:Double
    // 子类指定构造方法一定要调用父类构造方法
    // 并且必须在子类存储属性初始化之后调用父类构造方法
    init(firstName: String, lastName: String ,score:Double) {
        self.score = score
        super.init(firstName: firstName, lastName: lastName)
    }
    
    // 便利构造方法
    convenience init() {
        self.init(firstName:"",lastName:"",score:0)
    }
    
    // 将只读属性重写成了可写属性
    override var fullName:String {
        get {
            return super.fullName
        }
        set {
            let array = newValue.componentsSeparatedByString(".")
            if array.count == 2 {
                firstName = array[0]
                lastName = array[1]
            }
        }
    }
    
    // 重写对象方法
    override func showMessage() {
        print("name=\(self.fullName),age=\(self.age),score=\(self.score)")
    }
}

// 创建子类对象
var p = Student()
p.firstName = "安杜因"
p.showMessage()  // return:name=安杜因.,age=0,score=0.0

p.fullName = "安杜因.洛萨"
p.showMessage()  // return:name=安杜因.洛萨,age=0,score=0.0
p.sayHello()

只有指定构造方法才能调用父类的构造方法。
如果父类中存在有参数的指定构造方法,子类的指定构造方法不会自动调用父类无参的指定构造方法。
如果父类仅有一个无参构造方法(不管是否包含便利构造方法),子类的构造方法默认就 会自动调用父类的无参构造方法(这种情况下可以不用手动调用)。
常量属性只能在定义它的类的构造方法中初始化,不能在子类中初始化
一个对象被释放前,先自动调用自己的析构方法,然后一层一层往上调用父类的析构方法。
便利构造方法必须调用同一个类中的其他指定构造方法(可以是指定构造方法或者便利构造方法),不能直接调用父类构造方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容