Swift-属性、方法(继承、重写)、初始化

属性

Swift中跟实例相关的属性可以分为2大类

存储属性(Strored Property)

类似于成员变量的概念,存储在实例的内存中,结构体、类可以定义存储属性,枚举不可以定义存储属性

计算属性(Computed Property)

本质就是方法(函数)、不占用实例的内存,枚举、结构体、类都可以定义计算属性

struct Circle {
    // 存储属性
    var radius: Double
    // 计算属性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2
        }
    }
}

var circle = Circle(radius: 5)
circle.radius // 5.0
circle.diameter // 10.0


MemoryLayout<Circle>.stride  // 8

关于存储属性,Swift有个明确的规定,在创建类或者结构体的实例时候,必须为所有的存储属性设置一个合适的初始值,可以再初始化器里为存储属性设置一个初始值,或者也可以分类一个默认的属性值作为属性定义的一部分

set传入的新值默认叫做newValue,也可以自定义

struct Circle {
    // 存储属性
    var radius: Double
    // 计算属性
    var diameter: Double {
        set(newdiameter) {
            radius = newdiameter / 2
        }
        get {
            radius * 2
        }
    }
}

定义计算属性只能用var,不能用let,计算属性的值是可能发生变化的 ,即使是只读计算属性

只读计算属性:只有get 没有set 。 下面两份代码等价

struct Circle {
    var radius: Double
    var diameter: Double {
        get {
            radius * 2
        }
    }
}

struct Circle {
    var radius: Double
    var diameter: Double { radius * 2 }
}

枚举的rawValue的本质是:只读计算属性

enum TestEnum: Int {
    case test1 = 1, test2 = 2
    var rawValue: Int {
        switch self {
        case .test1:
            return 10
        case .test2:
            return 23
    }
}


TestEnum.test2.rawValue

延迟存储属性Lazy Stored Property

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化

lazy属性必须是var,延迟属性初始化时需要改变结构体的内存,不能是let,let必须在实例的初始化方法完成之前就拥有值,如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化1次,所以要加锁

class Car {
    init() {
        print("Car init!")
    }
    func run() {
        print("Car is running")
    }
}

class Person {
    lazy var car = Car()
    init() {
        print("Person init!")
    }
    func goOut() {
        car.run()
    }
}

let p = Person()
print("-------")
p.goOut()
Person init!
-------
Car init!
Car is running

案例调用

class PhotoView {
    lazy var image: Image = {
        let var url = "https://www.ffsf.com/df.png"
        let data = Data(url: url)
        return image(data: data)
    }
}

属性观察器 Property Observer

可以为非lazy的var存储属性设置属性观察器

willSet会传递新值,默认叫做newValue,didSet会传递旧值,默认叫做oldValue。在初始化器中设置属性值不会触发willSet和didSet,在属性定义时设置初始值也不会触发willSet和didSet

struct Circle {
    var radius: Double {
        willSet {
            print("willset", newValue)
        }
        didSet {
            print("didset", oldValue, radius)
        }
    }
    
    init() {
        self.radius = 1.0
        print("Circle init!")
    }
}

var circle = Circle()
circle.radius = 10.4 // 5.0
Circle init!
willset 10.4
didset 1.0 10.4

属性观察器、计算属性的功能,同样可以应用到全局变量、局部变量的身上

var num: Int {
    get {
        return 10
    }
    set {
        print("setNum", newValue)
    }
}
num = 11

func test() {
    var age = 10 {
        willSet {
            print("willset", newValue)
        }
        didSet {
            print("didset", oldValue, age)

        }
    }
    age = 11  // willset 11 didset 10 11
}
test()

类型属性 Type Prooerty

严格来说,属性可以分为

实例属性:只能通过实例去访问。1:存储实例属性:存储在实例中的属性,每个实例中都有一份。 2:计算实例属性

类型属性:只能通过类型去访问。1:存储类型属性:整个程序运行过程中,只有一份内存(类似于全局变量)。2:计算类型属性

可以通过sttic定义类型属性, 如果是类,也可以用关键字class,因为struct只能用static  class里可以用class和static

struct Car {
    static var count: Int = 0
    init() {
        Car.count += 1
    }
}

let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 3

类型属性细节

不同于存储实例属性,你必须给存储类型属性设置初始值, 因为类型没有像实例那样的init初始化器来初始化存储属性

存储类型属性默认就是lazy,会再第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次,是线程安全的。存储类型属性可以用let

枚举类型也可以定义类型属性(存储类型属性、计算类型属性)

单例模式

public class FileManager {
    public static let shared = FileManager()
    private init() {}
}

public class FileManagers {
    public static let shared = {
       // .....
        return FileManagers()
    }()
    private init() {}
}

inout本质总结

如果实参有物理内存地址,且没有设置属性观察器,则直接将实参的内存地址传入函数(实参进行引用传递)

如果实参是计算属性 或者 设置值了属性观察器 采取了Copy In Copy Out的做法

调用该函数时,先赋值实参的值,产生副本:get    讲副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值

函数返回后,再讲副本的值覆盖实参的值:set

总结:inout的本质据实引用传递(地址传递)

方法

枚举、结构体、类都可以定义实例方法、类型方法

实例方法:Instance Method 通过实例调用。 类型方法Type Method 通过类型调用,用static或者class关键字定义

struct Car {
    static var count: Int = 0
    init() {
        Car.count += 1
    }
    static func getCount() -> Int { count }
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.getCount()) // 3

self 在实例方法中代表实例,在类型方法中代表类型

在类型方法static func getCount中  cout等价于self.count、Car.self.count、Car.count

结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改 在func关键字前加mutating可以运行这种修改行为

@discardableResult

在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告⚠️

struct Point {
    var x = 0.0, y = 0.0
    @discardableResult mutating
    func moveBy(delayX: Double) -> Double {
        x += delayX
        return x
    }
}

var p = Point()
p.moveBy(delayX: 10)

@discardableResult
func get() -> Int {
    return 10
}

get()

下标subscript

使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本

subscript的预发类似于实例方法、计算属性,本质就是方法(函数)

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue
            }
        }
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}

var p = Point()
p[0] = 11.3
p[1] = 22.2
print(String.init(format: "%.2f", p.x) + "==" + String.init(format: "%.2f", p[0]) + "==" + "11.3")
print(String.init(format: "%.2f", p.y) + "==" + String.init(format: "%.2f", p[1]) + "==" + "22.2")

supscript中定义了返回值类型决定了 get方法的返回值类型,set方法中newValue的类型, subscript可以接受多个参数,并且类型任意

subscript可以没有set方法,但必须要有get方法。

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}

如果只有get方法,可以省略get

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        if index == 0 {
            return x
        } else if index == 1 {
            return y
        }
        return 0
    }
}

可以设置参数标签

class Point {
    var x = 0.0, y = 0.0
    subscript(index i: Int) -> Double {
        if i == 0 {
            return x
        } else if i == 1 {
            return y
        }
        return 0
    }
}

var p = Point()
p[index: 1]

下标可以是类型方法

class Sum {
    static subscript(v1: Int, v2: Int) -> Int {
        return v1 + v2
    }
}

Sum[10, 33]

多参数的下标

class Grid {
    var data = [
    [0, 3, 3],
        [2, 3, 4],
        [2, 4, 4]
    ]
    subscript(row: Int, column: Int) -> Int {
        set {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return
            }
            data[row][column] = newValue
         }
        get {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return 0
            }
            return data[row][column]
        }
    }
}

var gird = Grid()
gird[0, 1] = 223
gird[2, 2] = 13
print(gird.data) 

[[0, 223, 3], [2, 3, 4], [2, 4, 13]]

结构体、类作为返回值对比  class的 可以不加set方法

class Point {
    var x = 0, y = 0
}

class PointManager {
    var point = Point()
    subscript(index: Int) -> Point {
        point
    }
}


var pm = PointManager()
pm[0].x = 2333
pm[0].y = 232222

(lldb) p pm[0]
(MachOSSwift.Point) $R0 = 0x0000000100577130 (x = 2333, y = 232222)
(lldb) p pm.point
(MachOSSwift.Point) $R1 = 0x0000000100577130 (x = 2333, y = 232222)

上面的代码 Point前面改成struct,就立刻报错

给struct的加上set方法就不报错了

class PointManager {
    var point = Point()
    subscript(index: Int) -> Point {
        set { point =  newValue }
        get { point }
    }
}

因为struct是值类型,是需要内容拷贝的,pm[0]这个就是一个临时的point,并不是真正的point,这个是无法修改的,所以会报错。 而class可以的原因是因为, get方法里面返回的point是指针变量,不是结构体,而通过指针变量去访问x 是可以访问成功的。所以如果是结构体的话 一定要加上set方法,如果是class则可以不加set方法

继承 Inheritance

值类型(枚举、结构体)不支持继承,只有类支持继承

没有父类的类,成为基类

Swift并没有像OC、Java那样的规定,任何类最终都要继承自某个基类,子类可以重写父类的下标、方法、属性(计算属性),重写必须加上override关键字

重写实例方法和下标。多态:父对象指向子类类型

class Animal {
    func speak() {
        print("Animal speak")
    }
    subscript(index: Int) -> Int {
        return index
    }
}

class Cat: Animal {
    override func speak() {
        super.speak()
        print("Cat speak")
    }
    
    override subscript(index: Int) -> Int {
        return super[index] + 1
    }
}

var anim: Animal
anim = Animal() // Animal speak
anim.speak() // 6
print(anim[6])

anim = Cat()
anim.speak() // Animal speak Cat speak
print(anim[9]) // 10

被class修饰的类型方法、下标,允许被子类重写,被static修饰的类型方法、下标,不允许被子类重写。所以想要重写父类,父类就用class,子类可以用static,它只影响的是它的子类不能被重写

class Animal {
    class func speak() {
        print("Animal speak")
    }
    subscript(index: Int) -> Int {
        return index
    }
}

class Cat: Animal {
    override class func speak() {
        super.speak()
        print("Cat speak")
    }
    
    override subscript(index: Int) -> Int {
        return super[index] + 1
    }
}

重写属性

子类可以将父类的属性(存储、计算)重写为计算属性,子类不可以将父类属性重写为存储属性,只能重写var属性,不能重写let属性。重写时,属性名、类型要一致

子类重写后的属性权限不能小于父类属性的权限,如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读可写的,如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的。

重写类型属性

被class修饰的计算类型属性,可以被子类重写,被static修饰的类型属性(存储、计算),不可以被子类重写

可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器

类的类属性

多态的原理:oc:runtime, c++:虚表(虚函数表)

初始化

类、结构体、枚举都可以定义初始化器

类有2中初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer).   

init(paramters) {  // 指定初始化器
    statements
}

convenience init(paramters) { // 便捷初始化器
    statements
}

认为主要的都设置为指定初始化器,其他的都可以设置为便捷初始化器。

每个类至少有一个指定初始化器,指定初始化器是主要初始化器,默认初始化器总是类的指定初始化器,类偏向于少量指定初始化器,一个类通常只有一个指定初始化器

初始化器的相互调用规则:指定初始化器必须从它的直系父类调用指定初始化器,便捷初始化器必须从相同的类里调用另一个初始化器,便捷初始化器最终必须调用一个指定初始化器。

初始化器的相互调用如图

这就是两段式初始化

Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查

两段式初始化

第一阶段:初始化所有存储属性

1:外层调用指定/便捷初始化器

2:分配内存给实例,但未初始化

3:指定初始化器确保当前类定义的存储属性都初始化

4:指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链

第二阶段:设置新的存储属性值

1:从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步指定实例

2:初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)

3:最终,链中任何便捷初始化器都有机会定制实例以及使用self

总结:第一阶段结束代表:属性都能得到初始化,内存得到分配,内存得到初始化,从下往上走的。第二阶段个性化定制从上往下走

安全检查

指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成

指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值

便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值

初始化器在第一阶段初始化完成之前,不能调用任何实例方法,不能读取任何实例属性的值,也不能引用self,直到第一阶段结束,实例才算完全合法

重写

当重写父类的指定初始化器时,必须加上override(即便子类的实现是便捷初始化器)

如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override,因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器

自动继承

1:如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器。

2:如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)

子类自动继承所有的父类便捷初始化器。

3:就算子类添加了更多的便捷初始化器,这些规则仍然适用

4:子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分

requeired

用required修饰初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)

如果子类重写了required初始化器时 也必须加上required,不用加override

class Person {
    required init() {}
    init(age: Int) {}
}

class Student: Person {
    required init() {
        super.init()
    }
}

属性观察器

父类的属性在它自己的初始化器重赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器

class Person {
    var age: Int {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSelt", oldValue, age)
        }
    }
    init() {
        self.age = 0
    }
}

class Student: Person {
    override init() {
        super.init()
        self.age = 1
    }
}
 
var stu = Student() // willSet 1  didSelt 0 1

可失败初始化器

类、结构体、枚举都可以使用init?来定义可失败初始化器

class Person {
    var name: String
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
}

不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器

可以用init!定义隐式解包的可失败初始化器

可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包

如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行

可以用一个非可失败初始化器重写一个可失败初始化器,但是反过来是不行的

给一个view的初始化可做参考,后续可再重新赋值

class ScrollItem: UIView {
    
    let textLabel : UILabel = UILabel()
        
    convenience init() {
        self.init(frame: CGRect.zero)
    }

    init(frame: CGRect) {
        super.init(frame: frame)
        layoutUI()
        
        textLabel.backgroundColor = UIColor.cyan
        addSubview(textLabel)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        layoutUI()
    }
    
    fileprivate func layoutUI() {
        textLabel.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
    }
}

反初始化器 deinit

deinit叫做反初始化器,类似于C++的析构函数,OC中的dealloc方法

当类的实例对象被释放内存时,就会滴啊用实例对象的deinit方法

class Person {
    deinit {
        print("person对象被销毁了")
    }
}

deinit不接受任何参数,不能写小括号,不能自定调用

父类的deinit不能被子类继承,子类的deinit实现执行完毕后会调用父类的deinit

可选链Optional Chaining

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值