方法
方法分为:
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(index: Int) -> Int {
return super[index]+1
}
}
class ErHa :Dog{
var iq = 0
}
var a = Animal()
a.speak()
print(a[6])//结果:6
/* a 第一次声明的时候,是Animal类型,
在此执行Dog类型,
父类类型指向子类类型,这是一种多态
*/
a = Dog()
a.speak()
print(a[6])//结果:7
重写类型方法、下标
类型方法可以通过关键字:static
、class
来定义;
注意⚠️:
被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(index: Int) -> 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、类调用方法,只有在程序运行过程中,才能确定调用的是哪一个类的函数;