一.Enum
1.Enum基本信息
Swift中通过enum
关键字来声明一个枚举
enum LGEnum {
case one
case two
case three
}
在C
或者OC
中默认受整数支持,也就意味着下面的例子中:A,B,C分别默认代表0,1,2
typedef NS_ENUM(NSInteger, LGEnum) {
A,
B,
C
};
Swift中的枚举则更加灵活,并且不需要给枚举中的每一个成员都提供值(所谓“原始”值)。这个值可以是字符串、字符、任意的整数值,或者浮点类型。
enum Color: String {
case red = "Red"
case green = "Green"
case blue = "Blue"
}
enum LGEnum: Double {
case one = 10.0
case two = 20.0
case three = 30.0
case four = 40.0
}
隐式RawValue
分配是建立在Swift类型推断
机制上的
enum Week: Int {
case mon, tue, wed, thu, fri = 10, sat, sun
}
print(Week.mon.rawValue) //0
print(Week.tue.rawValue) //1
print(Week.wed.rawValue) //2
print(Week.thu.rawValue) //3
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //11
print(Week.sun.rawValue) //12
- 原始值是从0,1,2,3开始的,和OC一致
- 当指定原始值后,后面数据会从指定原始值做累加操作
将RawValue
类型Int
改为String
enum Week: String {
case mon, tue, wed, thu, fri = "10", sat, sun
}
print(Week.mon.rawValue) //mon
print(Week.tue.rawValue) //tue
print(Week.wed.rawValue) //wed
print(Week.thu.rawValue) //thu
print(Week.fri.rawValue) //10
print(Week.sat.rawValue) //sat
print(Week.sun.rawValue) //sun
- 编译器默认给每个枚举成员分配了一个原始值,也就是枚举成员字符串
通过SIL
分析rawValue原始值默认为枚举成员字符串
的原因
Swift代码
enum Week: String {
case mon, tue, wed, thu, fri = "10", sat, sun
}
let m = Week.mon.rawValue
SIL代码
//关于枚举的定义
enum Week : String {
case mon, tue, wed, thu, fri, sat, sun
init?(rawValue: String) //可失败的初始化器
typealias RawValue = String //给String起了个别名RawValue
var rawValue: String { get } //获取raValue其实就是获取rawValue的get方法
}
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1mSSvp // id: %2
%3 = global_addr @$s4main1mSSvp : $*String // user: %8
%4 = metatype $@thin Week.Type
//%5其实就是声明了一个Week.mon的参数
%5 = enum $Week, #Week.mon!enumelt // user: %7
// function_ref Week.rawValue.getter
%6 = function_ref @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String // user: %7
//传入%5(Week.mon)返回rawValue
%7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
store %7 to %3 : $*String // id: %8
%9 = integer_literal $Builtin.Int32, 0 // user: %10
%10 = struct $Int32 (%9 : $Builtin.Int32) // user: %11
return %10 : $Int32 // id: %11
} // end sil function 'main'
// rawValue的getter方法
// Week.rawValue.getter
sil hidden @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $Week):
debug_value %0 : $Week, let, name "self", argno 1 // id: %1
//switch_enum,模式匹配。这里逻辑其实很简单就是,匹配传入的枚举值来执行不同的代码块
switch_enum %0 : $Week, case #Week.mon!enumelt: bb1, case #Week.tue!enumelt: bb2, case #Week.wed!enumelt: bb3, case #Week.thu!enumelt: bb4, case #Week.fri!enumelt: bb5, case #Week.sat!enumelt: bb6, case #Week.sun!enumelt: bb7 // id: %2
//bb1代码块的逻辑非常简单,就是创建了一个字符串常量"mon",返回回去。
//其它的代码块逻辑与bb1一致
bb1: // Preds: bb0
%3 = string_literal utf8 "mon" // user: %8
%4 = integer_literal $Builtin.Word, 3 // user: %8
%5 = integer_literal $Builtin.Int1, -1 // user: %8
%6 = metatype $@thin String.Type // user: %8
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
%8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
br bb8(%8 : $String) // id: %9
bb2: // Preds: bb0
%10 = string_literal utf8 "tue" // user: %15
%11 = integer_literal $Builtin.Word, 3 // user: %15
%12 = integer_literal $Builtin.Int1, -1 // user: %15
%13 = metatype $@thin String.Type // user: %15
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%14 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
%15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
br bb8(%15 : $String) // id: %16
}
关于SIL
中的常量mon
,也就是case值对应的常量存放在Mach-o
的哪个位置
- 其实这个问题很简单,常量字符串肯定存放在
__TEXT.__cstring
里,也就是硬编码中
Arm64汇编验证mon
的存放位置
基地址为0x0000000100498000
mon
在内存中的地址为0x0000000100498000 + 0x7D48 = 0x10049FD48
进入汇编调试
- 此时生成字符串传入的参数就是
x0
,也就是mon
在内存中的地址0x000000010049fd48
2.Enum原始值&枚举值
//枚举值(Week类型)
print(Week.mon)
//原始值(String类型)
print(Week.mon.rawValue)
//通过原始值创建枚举值
print(Week(rawValue: "mon")!)
SIL分析Week.init
// function_ref _allocateUninitializedArray<A>(_:)
// _allocateUninitializedArray,分配一个连续的内存空间
%5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
//StaticString,创建连续的内存空间来存储case与之匹配的字符串
%6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
//匹配传进来的rawValue,返回枚举值
- 原理就是从一个连续的内存空间存储case与之匹配的字符串
3.Enum关联值
enum Shape {
case circle(radious: Double)
case rectangle(width: Double, height: Double)
}
let shape = Shape.circle(radious: 10)
switch shape {
case .circle(let radious):
print("Circle radious: \(radious)") // Circle radious: 10.0
case .rectangle(let width, var height): //这里也使用使用var
height += 10.0
print("Rectangle width: \(width) height:\(height)")
default:
print("other shape")
}
4.Enum的其它用法
//3.可以遵循协议
protocol ShapeProtocol {
func getPerimeter() -> Double
}
extension Shape: ShapeProtocol {
func getPerimeter() -> Double {
switch cirCleshape {
case .circle(let radious):
return 2 * Double.pi * radious
case let .rectangle(width, height):
return 2 * (width + height)
}
}
}
//4.可以使用extension扩展方法
extension Shape {
func printInfo() {
switch cirCleshape {
case .circle(let radious):
print("Circle radious: \(radious)") // Circle radious: 10.0
case .rectangle(let width, var height): //这里也使用使用var
height += 10.0
print("Rectangle width: \(width) height:\(height)")
}
}
}
enum Shape {
case circle(radious: Double)
case rectangle(width: Double, height: Double)
//2.计算属性,但是枚举不能添加属性
var area: Double {
get {
switch self {
case .circle(let radious):
return Double.pi * radious * radious
case .rectangle(let width, let height):
return width * height
}
}
set {
switch self {
case .circle:
self = Shape.circle(radious: sqrt(newValue/Double.pi))
case .rectangle:
self = Shape.circle(radious: sqrt(newValue/Double.pi))
}
}
}
//1.异变方法,修改自身
mutating func changeShape(shape: Shape) {
self = shape
}
}
5.枚举的大小
1.No-Payload enums
也就是没有关联值的枚举
enum Week {
case Mon
case Tue
case Wed
case Thu
case Fri
case Sat
case Sun
}
print(MemoryLayout<Week>.size) //1
print(MemoryLayout<Week>.stride) //1
print(MemoryLayout<Week>.alignment) //1
- 默认以
UInt8
去存枚举值,因此枚举值大小/步长/对齐方式都是1字节 - 最多能存
256个case
,如果枚举值超了,此时的UInt8
会升为UInt16
,当然在实际开发中也不可能会有那么多枚举值
通过LLDB
读取枚举在内存中的值
(lldb) frame variable -L a
0x00000001000080d9: (swiftTest.Week) a = Mon
(lldb) x/b 0x00000001000080d9
0x1000080d9: 0x00
(lldb) frame variable -L b
0x00000001000080da: (swiftTest.Week) b = Tue
(lldb) x/b 0x00000001000080da
0x1000080da: 0x01
(lldb) frame variable -L c
0x00000001000080db: (swiftTest.Week) c = Wed
(lldb) x/b 0x00000001000080db
0x1000080db: 0x02
(lldb)
2.Single-Playload enums
只有一个成员负载
enum LGEnum {
case one(Bool)
case two
case three
case four
}
/*
分析为什么挂载了一个负载Bool,还是一字节,和未挂载一样
我们知道1字节为8位0b00000000,最大可存256个值(Uint8),也就是0~255
挂载值Bool在内存中只会占取1位,因此还有剩下的7位去存放我们的枚举值。(128个)
因此当枚举的case小于128时,会用Int8去存。当然如果大于等于128,此时的Int8就会升级成Int16
*/
print(MemoryLayout<LGEnum>.size) //1
print(MemoryLayout<LGEnum>.stride) //1
print(MemoryLayout<LGEnum>.alignment) //1
enum LGEnum_Int {
case one(Int)
case two
case three
case four
}
/*
分析为什么挂载了Int,占用了9字节
因为Int占用8字节,不能与case值占用的1字节共用。因此需要额外的8字节来存储Int
所以就是8+1=9字节
stride步长,也就是对齐后的大小,这里是8字节对齐,因此内存对齐后就是16
aligment,对齐大小,当前这里就是Int的大小8字节
*/
print(MemoryLayout<LGEnum_Int>.size) //9
print(MemoryLayout<LGEnum_Int>.stride) //16
print(MemoryLayout<LGEnum_Int>.alignment) //8
3.关于Single-Playload enums
多个关联值内存对齐问题
enum Enum_Aligment {
case one(Int,Int,Bool,Int)
}
print(MemoryLayout<Enum_Aligment>.size) //32
print(MemoryLayout<Enum_Aligment>.stride) //32
enum Enum_AligmentTwo {
case one(Int,Int,Int,Bool)
}
print(MemoryLayout<Enum_AligmentTwo>.size) //25
print(MemoryLayout<Enum_AligmentTwo>.stride) //32
/*
这里的步长其实很好理解,也就是基于8字节对齐后的大小
这里的Bool位置不同,影响这size的大小,那么这里其实也好理解,原理和结构体内存对齐是一回事。
对于Enum_Aligment来说,前2个Int开辟了16字节内存大小,来到第三个Bool值,开辟了1字节的内存大小存放Bool。
当来到第4个Int时,此时的index不能满足被8整除,因此会向后偏移到能被8整除的下标才能存放Int。
所以Bool和最后一个Int中间还有7字节的剩余空间来存放case值。所以size为32
对于Enum_AligmentTwo来说,前3个Int都好理解,开辟24字节内存空间存放。然后开辟1字节存放Bool也是没问题,所以size为25
*/
4.Mutil-Playload enums
enum LGEnum_Multi {
case one(Bool)
case two(Bool)
case three
case four
case five
case six
}
//探究关于内存
//tag Index(低4位)
//tag Value(高4位)
//暂时还没有在源码找到关于枚举内存的规律
let x1 = LGEnum_Multi.one(false) //00
let x2 = LGEnum_Multi.one(true) //01
let x3 = LGEnum_Multi.two(false) //40
let x4 = LGEnum_Multi.two(true) //41
let x5 = LGEnum_Multi.three //80
let x6 = LGEnum_Multi.four //81
let x7 = LGEnum_Multi.five //c0
let x8 = LGEnum_Multi.six //c1
/*
其实看到x1~x8,大概可以总结一个规律。当然没有在源码中找到验证
如果挂载了Bool值来说
那么对于同一个tagValue来说,低4位的0/1来判断是否有关联值
如果没有挂载的话,就是80到81,也就是上面的x5和x6
有挂载值的tagValue,相邻类型是差距了4倍。也就是x1(00)-x3(40)
没有挂载值的tagValue,如果前一个内存低4位是0,将低4位加1存入下一个枚举值。也就是x5(80)-x6(81)
如果前一个类型内存低4位是1,相邻类型是差了2倍。也就是x6(81)-x7(c0)
*/
enum LGEnum_Multi_Int {
case one(Int)
case two(Int)
case three
case four
}
let i1 = LGEnum_Multi_Int.one(10) // 0x10000c118: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i2 = LGEnum_Multi_Int.one(30) // 0x10000c128: 1e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
let i3 = LGEnum_Multi_Int.three // 0x10000c138: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
/*
这里的前8字节都是存放的Int,也就是挂载值
偏移8字节开始存放的是case值,也就是枚举值。这里可以看到i1和i2都是0,而i3的枚举值却是0x2
在源码中也没有找到关于枚举在内存中是怎么存储中
*/
5.关于Single-Playload enums
和Multi-Playload enums
的源码
Enum.cpp
void
swift::swift_initEnumMetadataSinglePayload(EnumMetadata *self,
EnumLayoutFlags layoutFlags,
const TypeLayout *payloadLayout,
unsigned emptyCases) {
size_t payloadSize = payloadLayout->size;
//获取挂载额外的bits空间
unsigned payloadNumExtraInhabitants
= payloadLayout->getNumExtraInhabitants();
//没有使用的额外存储空间
unsigned unusedExtraInhabitants = 0;
// If there are enough extra inhabitants for all of the cases, then the size
// of the enum is the same as its payload.
size_t size;
//如果额外的存储空间>=case占用的空间
if (payloadNumExtraInhabitants >= emptyCases) {
size = payloadSize;
//没有使用的额外存储空间 = 额外的剩余空间 - case占用的空间
unusedExtraInhabitants = payloadNumExtraInhabitants - emptyCases;
} else {
//这里相当于是挂载了一个Int, 8 + 1 = 9
size = payloadSize + getEnumTagCounts(payloadSize,
emptyCases - payloadNumExtraInhabitants,
1 /*payload case*/).numTagBytes; ;
}
auto vwtable = getMutableVWTableForInit(self, layoutFlags);
//关于内存对齐
size_t align = payloadLayout->flags.getAlignment();
...
}
void
swift::swift_initEnumMetadataMultiPayload(EnumMetadata *enumType,
EnumLayoutFlags layoutFlags,
unsigned numPayloads,
const TypeLayout * const *payloadLayouts) {
// Accumulate the layout requirements of the payloads.
size_t payloadSize = 0, alignMask = 0;
bool isPOD = true, isBT = true;
for (unsigned i = 0; i < numPayloads; ++i) {
const TypeLayout *payloadLayout = payloadLayouts[i];
payloadSize
= std::max(payloadSize, (size_t)payloadLayout->size);
alignMask |= payloadLayout->flags.getAlignmentMask();
isPOD &= payloadLayout->flags.isPOD();
isBT &= payloadLayout->flags.isBitwiseTakable();
}
6.关于一些常用的LLDB
指令
po 打印信息
p 打印详细的信息
bt 打出堆
register read 读取寄存器
x或memory read 读取内存段
x/4g 读取4段8字节内存段
x/4w 读取4段4字节内存段
p/x 以16进制打印
p/t 以二进制打印
p *$0 打印变量($0)的值
字节大小
b
byte 1字节
h
half word 2字节
w
word 4字节
g
giant word 8字节
6.Indirect关键字
1.关于indirect
/*
枚举是值类型,在编译时期大小就能确定
但是下列写法确定不了枚举的大小
indirect表达递归的枚举类型,编译器会在堆上分配内存空间
*/
indirect enum BanaryTree<T> {
case empty
case node(left: BanaryTree, right: BanaryTree, value: T)
}
var code = BanaryTree<Int>.node(left: BanaryTree<Int>.empty, right: BanaryTree<Int>.empty, value: 10)
LLDB
查看
(lldb) frame variable -L code
0x000000010000c178: (swiftTest.BanaryTree<Int>) code = node {
0x00000001012111a0: node = {
0x00000001012111a0: left = empty
0x00000001012111a8: right = empty
0x00000001012111b0: value = 10
}
}
(lldb) x/8g 0x000000010000c178
0x10000c178: 0x0000000101211190 0x0000000100008150
0x10000c188: 0x0000000000000000 0x0000000000000000
0x10000c198: 0x0000000000000000 0x0000000000000000
0x10000c1a8: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000101211190
0x101211190: 0x00000001000080a0 0x0000000000000003
0x1012111a0: 0x0000000000000000 0x0000000000000000
0x1012111b0: 0x000000000000000a 0x00037ff843559c20
0x1012111c0: 0x0000000000000007 0x00007ff841d98240
(lldb)
- 当我们读取
0x0000000101211190
时,可以看到读取的值就是一个HeapObject
- 也就意味着,我们在枚举上使用递归时,加上
indirect
关键字后,编译器会在堆区申请内存地址来进行存放
SIL
分析
SIL中有一个可以看出,调用了alloc_box,也就是执行swift_allocObject
汇编
查看是否执行了swift_allocObject
2.关于indirect
放到case
前面
enum BanaryTree<T> {
case empty
indirect case node(left: BanaryTree, right: BanaryTree, value: T)
}
当我们把
indirect
放到case
前面时,只有这个case值
使用引用类型,也就是会放到堆空间上存储当枚举使用了
indirect
关键字后,整个枚举会变为引用类型,也就是在堆空间存储的
二.Optional
1.认识可选值
class LGTeacher {
var age: Int?
}
当前的age
就称为可选值
var age: Int? 等同于 var age: Optional<Int>
2.Optional的本质
在Optional.swift
中
@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
}
- 实际上就是一个枚举中,关联一个值
根据源码使用自己的MyOptional
enum MyOptional<Value> {
case none
case some(Value)
}
func getOddValue(value:Int) -> MyOptional<Int> {
if value % 2 == 0 {
return .some(value)
}else {
return .none
}
}
var arr = [0, 1, 2, 3, 4, 5]
for element in arr {
let value = getOddValue(value: element)
switch value {
case .some(let value):
print("Odd: \(value)")
case .none:
break
}
}
通过
模式匹配
拿出当前的值如果将
MyOptional<Int>
改为Int?
,其它代码完全不用改变,也能正常使用。通过代码也印证了Optional
的本质
3.可选值的绑定(解包)
如果每一个可选值都用模式匹配的方式来获取值在书写代码上就比较繁琐,我们还可以使用if let
的方式来进行可选值的绑定
if let value = value {
}
- 这里
value类型
只有是Int?
时才能使用if let
解包,如果是MyOptional<Int>
,编译器是不允许这样做的
当然这里还可以使用guard let
来解包。注意:只能在func
中使用
guard let value = value else {
return
}
4.可选链
可选链
其实就是类似于OC
向一个nil对象
发送消息什么都不会发生。在Swift
中借助可选链可以达到类似的效果
let str: String? = "abc"
let upperStr = str?.uppercased()
print(upperStr) //Optional("ABC")
var str1: String?
let upperStr1 = str1?.uppercased()
print(upperStr1) //nil
可选链的调用
let str: String? = "abc"
let upperStr = str?.uppercased().lowercased()
print(upperStr) //Optional("abc")
对于闭包也适用
var closure: (() -> Void)?
closure?() //closure为nil不执行
5.??运算符(空合并运算符)
a ?? b
对可选类型a
进行空判断,如果a
包含一个值就进行解包
,否则就返回一个默认值b
- 表达式
a
必须是Optional
类型 - 默认值
b
的类型必须与a
存储值的类型保持一致
var age: Int?
print(age ?? 0) //0
age = 10
print(age ?? 0) //10
关于optional
源码中的运算符??
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
rethrows -> T? {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
6.隐式解包
一般在开发的时候,如果确定某个变量的值在使用的时候是一直存在的,可以使用隐式解包!
int age: Int!
后续在使用到
age
时,虽然它是可选值,但是编译器已经帮我们隐式解包了,也就是告诉编译器它是肯定有值的。这种隐式解包
操作,可以大大减少代码中的解包操作最常见的用法就是从
Storyboard
或Xib
生成的IBOutlet
属性
三.运算符
1.运算符重载
比如实现2个向量的加、减、负
运算符
- 运算符重载必须使用
static
关键字
struct Vector {
let x: Double
let y: Double
}
extension Vector {
//运算符重载必须使用static
//prefix声明前缀运算符
static prefix func - (vector: Vector) -> Vector {
return Vector(x: -vector.x, y: -vector.y)
}
static func + (vector0: Vector, vector1: Vector) -> Vector {
return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
}
static func - (vector0: Vector, vector1: Vector) -> Vector {
return vector0 + -vector1
}
}
let vector0 = Vector(x: 10, y: 10)
let vector1 = Vector(x: 5, y: 5)
print(-vector0) //Vector(x: -10.0, y: -10.0)
print(vector0+vector1) //Vector(x: 15.0, y: 15.0)
print(vector0-vector1) //Vector(x: 5.0, y: 5.0)
2.自定义运算符
-
prefix
前缀运算符 -
infix
中缀运算符 -
postfix
后缀运算符
1.声明自定义prefix
或postfix
运算符
struct Vector {
let x: Double
let y: Double
}
//1.声明prefix或postfix自定义运算符
//pow
prefix operator *==
//sqar
postfix operator /==
//2.prefix或postfix实现运算符功能
extension Vector {
static prefix func *==(vector: Vector) -> Vector {
return Vector(x: pow(vector.x, 2), y: pow(vector.y, 2))
}
static postfix func /==(vector: Vector) -> Vector {
return Vector(x: sqrt(vector.x), y: sqrt(vector.y))
}
}
print(*==Vector(x: 10, y: 10)) //Vector(x: 100.0, y: 100.0)
print(Vector(x: 100, y: 100)/==) //Vector(x: 10.0, y: 10.0)
2.声明infix
运算符
struct Vector {
let x: Double
let y: Double
}
//1.声明一个`infix`运算符
//实现2次+=
infix operator +=+=
//2.实现运算符功能
extension Vector {
static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
}
}
print(Vector(x: 10, y: 10) +=+= Vector(x: 5, y: 5)) //Vector(x: 20.0, y: 20.0)
如果当前自定义运算符类型为infix,还可以指定优先级组
,也就是结合原则。
//也就是将当前声明的运算符遵循一个优先级原则,当然这个优先级组可以是系统的的也可以是自定义的
infix operator +=+=: MyCustomOperator
//也可以遵循加法优先级原则
//infix operator +=+=: AdditionPrecedence
//3.自定义优先级组
precedencegroup MyCustomOperator {
//优先级高于
higherThan: AdditionPrecedence
//优先级低于
lowerThan: MultiplicationPrecedence
//左结合
associativity: left
}
验证自定义的优先级组
struct Vector {
let x: Double
let y: Double
}
extension Vector {
//运算符重载必须使用static
//prefix声明前缀运算符
static prefix func - (vector: Vector) -> Vector {
return Vector(x: -vector.x, y: -vector.y)
}
static func + (vector0: Vector, vector1: Vector) -> Vector {
return Vector(x: vector0.x + vector1.x, y: vector0.y + vector1.y)
}
static func - (vector0: Vector, vector1: Vector) -> Vector {
return vector0 + -vector1
}
}
//1.声明一个`infix`运算符
//实现2次+=
//也就是将当前声明的运算符遵循一个优先级原则,当然这个优先级组可以是系统的的也可以是自定义的
infix operator +=+=: MyCustomOperator
//2.实现运算符功能
extension Vector {
static func +=+=(vector0: Vector, vector1: Vector) -> Vector {
return Vector(x: vector0.x + vector1.x + vector1.x, y: vector0.y + vector1.y + vector1.y)
}
}
//3.自定义优先级组
precedencegroup MyCustomOperator {
//优先级高于
higherThan: AdditionPrecedence
//优先级低于
lowerThan: MultiplicationPrecedence
//左结合
associativity: left
}
let x = Vector(x: 5, y: 5)
let y = Vector(x: 10, y: 10)
//此时的+=+=优先级比+搞,因此会先执行+=+=
let z = x + y +=+= x
print(z) //Vector(x: 25.0, y: 25.0)