Swift -- 07 方法

544 阅读10分钟

swift.webp

方法

方法分为:
1、实例方法:通过实例调用
2、类型方法:通过类型调用,用static或者class关键字定义;

class Car{
    static var count = 0
    init(){
        Car.count += 1
    }
    
    static func loadCount()->Int {
        count
    }
}
 
let c1 = Car.init()
let c2 = Car.init()
print(Car.loadCount())

mutating

结构体枚举类型是值类型,默认情况下,值类型的属性不能被自身的实例方法修改;
也就是说,结构体枚举在自身内部的方法里,修改自身的属性;

如果需要修改,则通过mutating关键字;
使用方法:
func前面增加mutating关键字,可以允许修改值类型的属性

struct Point {
    var x = 1
    var y = 2
    mutating func changeXY(_ xx:Int,_ yy:Int){
        x += xx
        y += yy
    }
}
enum StateSwitch{
    case low,middle,high
    mutating func next(){
        switch self {
        case .low:
            self = .middle
        case .middle:
            self = .high
        case .high:
            self = .low
        }
    }
}

下标 subscript

使用subscript可以给任意类型(枚举、结构体、类)增加下标功能;
subscript类似实例方法、计算属性,其本质就是方法(函数)

/// 下标
class mPoint {
    var x = 0.0, y = 0.0
    //subscript定义下标,接收一个int 类型的index参数,返回值为Double类型
    subscript(index:Int)->Double
    {//使用set、get方法,表示subscript类似一个计算属性
        set{
            if index == 0{
                x = newValue
            }else {
                y = newValue
            }
        }
        get{
            if index == 0 {
                return x
            } else {
                return y
            }
        }
    }
}
var p = mPoint()

/* 调用下标的set方法
 0 赋值给 index,12.1赋值给newValue
 结果:x = 12.1
 */
p[0] = 12.1

/* 调用下标的set方法
 1 赋值给 index,13.1赋值给newValue
 结果:y = 13.1
 */
p[1] = 13.1
 
/*调用下标的get方法
 0 赋值给 index
 结果:返回x的值,12.1
 */
print(p[0])

/*调用下标的get方法
 1 赋值给 index
 结果:返回y的值,13.1
 */
print(p[1])

subscript中定义的返回值类型决定了:
1、get返回值的类型;
2、set方法中newValue的类型;\

subscript可以接受多个参数,并且可以是任意类型;

subscript可以没有set方法,但必须有get方法;这一点跟计算属性是保持一致的;

subscript可以是实例方法,也可以是类方法;

class Sum{
    static subscript(v1:Int,v2:Int)->Int
    {
        return v1+v2
    }
}
print(Sum[2,3])

继承 ‼️

值类型(结构体、枚举)不支持继承,只有支持继承;
在Swift中,没有父类的类,称为基类
Swift并不像OC、Java那样规定,任何类最终都必须继承至某个基类;

子类可以重写父类的下标方法属性(主要是计算属性),重写必须加上override关键字;
因为下标计算属性的本质就是方法;

内存结构分析

class Animal {
    var age = 0
}

class Dog:Animal{
    var weight = 0
}
 
class ErHa :Dog{
    var iq = 0
}

类存储在堆空间,堆空间的内存要求是16字节对齐;
类的前8字节,用来存放类型信息,往后8个字节,存放引用计数相关;

Animal类: age属性,占用8个字节,因此,Animal类,至少需要24字节,由于内存对齐原则,所以Animal类分配32字节,后8字节是空的;

Dog类: Dog类继承于Animal,Dog类也存在age属性,Dog有自己的weight属性,占用8字节,所以Dog 类分配32字节;

ErHa类: ErHa类继承于Dog,ErHa类也存在age、weight属性,ErHa有自己的iq属性,占用8字节,所以ErHa占用40字节,由于内存对齐原则, ErHa类分配48字节;

重写实例方法、下标

class Animal {
    var age = 0
    func speak(){
        print("animal speak")
    }
    subscript(index:Int)->Int{
        return index
    }
}

class Dog:Animal{
    var weight = 0
    override func speak() {
        super.speak()
        print("dog speak")
    }
    override subscript(indexInt) -> Int {
        return super[index]+1
    }
}

class ErHa :Dog{
    var iq = 0
}

var a = Animal()
a.speak()
print(a[6])//结果:6


/* a 第一次声明的时候,是Animal类型,
 在此执行Dog类型,
 父类类型指向子类类型,这是一种多态
 */= Dog()
a.speak()
print(a[6])//结果:7

重写类型方法、下标

类型方法可以通过关键字:staticclass来定义;
注意⚠️:
class修饰的类型方法、下标,允许被子类重写;
static修饰的类型方法、下标,不允许被子类重写;

class Animal {
    var age = 0
    class func speak(){
        print("animal speak")
    }
    class subscript(index:Int)->Int{
        return index
    }
}

class Dog:Animal{
    var weight = 0
    override class func speak() {
        super.speak()
        print("dog speak")
    }
    override class subscript(indexInt) -> Int {
        return super[index]+1
    }
}

重写属性

子类可以将父类的属性(计算属性、存储属性)重写为计算属性
但子类不可以将父类的属性,重写为存储属性;\

只能重写var声明的属性,不能重写let声明的属性;
重写时,属性名、类型要保持一致;\

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

重写实例属性

class Animal {
    var age = 0
    var num:Int
    {
        set {
            print("animal set num")
        }
        get
        {
            print("animal get num")
            return 1
        }
    }
}

class Dog:Animal{
    override var age: Int{
        set{
            print("dog set age")
            super.age = newValue > 0 ? newValue : 0
        }
        get{
            print("dog get age")
            return super.age
        }
    }
    
    override var num: Int{
        set {
            print("dog set num")
        }
        get
        {
            print("dog get num")
            return super.num
        }
    }
}

重写类型属性

class修饰的计算类型属性,可以被子类重写;由于存储属性不能被class修饰,所以不存在重写业务;
static修饰的类型属性(计算、存储),不可以被重写;

class Animal {
   static var age = 0
   class var num:Int
    {
        set {
            print("animal set num")
        }
        get
        {
            print("animal get num")
            return 2
        }
    }
}

class Dog:Animal{
    override static var num: Int{
        set {
            print("dog set num")
            super.num = newValue > 1 ?newValue:1
        }
        get
        {
            print("dog get num")
            return super.num
        }
    }
}

属性观察器

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

属性观察器是用于监听属性变化的,let修饰的属性是常量,常量不可以修改,所以不能给let修饰的属性增加属性观察器;

只读计算属性代表只能读取,不能修改,所以也不能增加属性观察器;

class Animal {
    var age = 0
}

class Dog:Animal{
    override var age: Int{
        didSet {
            print("dog didSet age",oldValue,age)
         }
        willSet
        {
            print("dog willSet age",newValue)
         }
    }
}

var d = Dog()
d.age = 12

结果:
dog willSet age 12
dog didSet age 0 12

如果父类本身就有属性观察器,那么调用顺序如下:

class Animal {
    var age : Int = 1{
        didSet{
            print("animal didSet age",oldValue,age)
        }
        willSet{
            print("animal willSet age",newValue)
        }
    }
}

class Dog:Animal{
    override var age: Int{
        didSet {
            print("dog didSet age",oldValue,age)
         }
        willSet
        {
            print("dog didSet age",newValue)
         }
    }
}
var d = Dog()
d.age = 12

结果:
dog didSet age 12
animal willSet age 12
animal didSet age 1 12
dog didSet age 1 12

父类的计算属性,在子类中增加属性观察器;

class Animal {
    var age : Int{
        set{
            print("animal set age",newValue)
        }
        get{
            print("animal get")
            return 22
        }
    }
}

class Dog:Animal{
    override var age: Int{
        didSet {
            print("dog didSet age",oldValue,age)
         }
        willSet
        {
            print("dog willSet age",newValue)
         }
    }
}
 
Dog.age = 12

执行结果:
animal get //先调用父类的get,主要是子类访问了oldValue,oldValue是变更之前的值,所以在变更前,先获取值,因此就会调用父类的get方法获取值
dog willSet age 12
animal set age 12
animal get
dog didSet age 22 22

final

final修饰的方法、下标、属性,禁止被重写;
final修饰的类,禁止被继承;

final修饰符只能用于类,不能修饰结构体(struct)和枚举。

多态

多态的实现原理:
在OC中,多态的实现原理,是通过Runtime实现;
在C++中,多态的实现原理,是通过虚表(虚函数表)实现;

多态:父类指针,指向子类对象;

class Animal {
    func speak(){
        print("animal speak")
    }
}
 
class Dog:Animal{
   override func speak(){
        print("dog speak")
    }
}
var animal : Animal

animal = Animal()
animal.speak()
 
animal = Dog()
animal.speak()

结构体函数调用与类函数调用的汇编分析:

结构体

struct Animal {
    func speak(){
        print("animal speak")
    }
    func eat(){
        print("animal eat")
    }
    func play(){
        print("animal play")
    }
}
var a = Animal()
a.speak()
a.eat()
a.play()
0x100003440 <+0>:  pushq  %rbp
0x100003441 <+1>:  movq   %rsp, %rbp
0x100003444 <+4>:  callq  0x100003730               ; QLYTestSwift.Animal.init() -> QLYTestSwift.Animal at main.swift:10
0x100003449 <+9>:  callq  0x100003460               ; QLYTestSwift.Animal.speak() -> () at main.swift:11
0x10000344e <+14>: callq  0x1000035b0               ; QLYTestSwift.Animal.eat() -> () at main.swift:14
0x100003453 <+19>: callq  0x100003670               ; QLYTestSwift.Animal.play() -> () at main.swift:17
0x100003458 <+24>: xorl   %eax, %eax
0x10000345a <+26>: popq   %rbp
0x10000345b <+27>: retq

结构体变量,调用结构体内部函数,底层汇编是直接调用函数;
因为结构体不存在继承,所以调用a.speak()这些函数,可以直接确定调用的就是Animal这个结构体的内部函数;

class Animal {
    func speak(){
        print("animal speak")
    }
    func eat(){
        print("animal eat")
    }
    func play(){
        print("animal play")
    }
}

var a = Animal()
a.speak()
a.eat()
a.play()

结构体换为,汇编明显变多,底层逻辑更复杂;
所以在项目使用中,不需要继承,内部只有一些简单的函数,或者是成员属性,推荐使用结构体(struct)

    0x100003200 <+0>:   pushq  %rbp
    0x100003201 <+1>:   movq   %rsp, %rbp
    0x100003204 <+4>:   pushq  %r13
    0x100003206 <+6>:   subq   $0x68, %rsp
    0x10000320a <+10>:  xorl   %eax, %eax
    0x10000320c <+12>:  movl   %eax, %edi
    0x10000320e <+14>:  callq  0x100003300               ; type metadata accessor for QLYTestSwift.Animal at <compiler-generated>
    0x100003213 <+19>:  movq   %rax, %r13
    0x100003216 <+22>:  callq  0x100003680               ; QLYTestSwift.Animal.__allocating_init() -> QLYTestSwift.Animal at main.swift:10
    0x10000321b <+27>:  movq   %rax, 0x90fe(%rip)        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x100003222 <+34>:  leaq   0x90f7(%rip), %rdi        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x100003229 <+41>:  leaq   -0x20(%rbp), %rsi
    0x10000322d <+45>:  movl   $0x20, %edx
    0x100003232 <+50>:  xorl   %eax, %eax
    0x100003234 <+52>:  movl   %eax, %ecx
    0x100003236 <+54>:  callq  0x1000073ca               ; symbol stub for: swift_beginAccess
    0x10000323b <+59>:  movq   0x90de(%rip), %r13        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x100003242 <+66>:  movq   %r13, -0x68(%rbp)
    0x100003246 <+70>:  movq   %r13, %rdi
    0x100003249 <+73>:  callq  0x100007424               ; symbol stub for: swift_retain
    0x10000324e <+78>:  leaq   -0x20(%rbp), %rdi
    0x100003252 <+82>:  callq  0x1000073e8               ; symbol stub for: swift_endAccess
    0x100003257 <+87>:  movq   (%r13), %rax
    0x10000325b <+91>:  callq  *0x50(%rax)
    0x10000325e <+94>:  movq   -0x68(%rbp), %rdi
    0x100003262 <+98>:  callq  0x10000741e               ; symbol stub for: swift_release
    0x100003267 <+103>: leaq   0x90b2(%rip), %rdi        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x10000326e <+110>: leaq   -0x38(%rbp), %rsi
    0x100003272 <+114>: movl   $0x20, %edx
    0x100003277 <+119>: xorl   %eax, %eax
    0x100003279 <+121>: movl   %eax, %ecx
    0x10000327b <+123>: callq  0x1000073ca               ; symbol stub for: swift_beginAccess
    0x100003280 <+128>: movq   0x9099(%rip), %r13        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x100003287 <+135>: movq   %r13, -0x60(%rbp)
    0x10000328b <+139>: movq   %r13, %rdi
    0x10000328e <+142>: callq  0x100007424               ; symbol stub for: swift_retain
    0x100003293 <+147>: leaq   -0x38(%rbp), %rdi
    0x100003297 <+151>: callq  0x1000073e8               ; symbol stub for: swift_endAccess
    0x10000329c <+156>: movq   (%r13), %rax
    0x1000032a0 <+160>: callq  *0x58(%rax)
    0x1000032a3 <+163>: movq   -0x60(%rbp), %rdi
    0x1000032a7 <+167>: callq  0x10000741e               ; symbol stub for: swift_release
    0x1000032ac <+172>: leaq   0x906d(%rip), %rdi        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x1000032b3 <+179>: leaq   -0x50(%rbp), %rsi
    0x1000032b7 <+183>: movl   $0x20, %edx
    0x1000032bc <+188>: xorl   %eax, %eax
    0x1000032be <+190>: movl   %eax, %ecx
    0x1000032c0 <+192>: callq  0x1000073ca               ; symbol stub for: swift_beginAccess
    0x1000032c5 <+197>: movq   0x9054(%rip), %r13        ; QLYTestSwift.a : QLYTestSwift.Animal
    0x1000032cc <+204>: movq   %r13, -0x58(%rbp)
    0x1000032d0 <+208>: movq   %r13, %rdi
    0x1000032d3 <+211>: callq  0x100007424               ; symbol stub for: swift_retain
    0x1000032d8 <+216>: leaq   -0x50(%rbp), %rdi
    0x1000032dc <+220>: callq  0x1000073e8               ; symbol stub for: swift_endAccess
    0x1000032e1 <+225>: movq   (%r13), %rax
    0x1000032e5 <+229>: callq  *0x60(%rax)
    0x1000032e8 <+232>: movq   -0x58(%rbp), %rdi
    0x1000032ec <+236>: callq  0x10000741e               ; symbol stub for: swift_release
    0x1000032f1 <+241>: xorl   %eax, %eax
    0x1000032f3 <+243>: addq   $0x68, %rsp
    0x1000032f7 <+247>: popq   %r13
    0x1000032f9 <+249>: popq   %rbp
    0x1000032fa <+250>: retq   

可以多继承,所以a.speak这些函数,执行的是哪一个类的内部函数,是不确定的,所以它调用的函数地址是动态变化的,需要根据指定的对象属于哪一个类才能确定;

总结:
1、类跟结构体调用方法是有区别的;
2、结构体调用方法,在编译时,就能确定调用的是谁的函数;
3、类调用方法,只有在程序运行过程中,才能确定调用的是哪一个类的函数;