Swift 基础(二)

138 阅读13分钟
1.Swift 与 OC互相调用
  • Swift 调用OC

​ 需要创建桥接文件 Target-BriBridging-Header.h, (在swift 项目)导入或者创建OC文件系统会自动提示创建桥接文件,点击确认就可以,同样也可以自定义桥接文件,需要Xcode配置引用指定文件路径

image-20240307120526032.png

  • OC 调用Swift

​ 直接导入 #import "Target-Swift.h" 例如: #import "FFmpegDemo-Swift.h"

image-20240307132248263.png

[!NOTE]

想要OC能够引用Swift类,自定义Swift类必须要继承NSObject, 不然引用不到

Swift 中的属性或者方法都需要使用@objc进行修饰

如果类前面使用 @objcMembers 修饰,默认这个类所有属性和方法都暴露给OC

因为OC是通过Runtime机制,通过运行时进行消息发送,只有通过继承NSObject,才能够有isa指针,执行消息查找

/// @objcMembers 修饰类,不需要对内部属性和方法使用@objc修饰,默认全部暴露
/// 类如果不适用 @objcMembers,
@objcMembers class Person : NSObject {
    
    //暴露给oc,外部适用属性
    @objc var name : String?
    
    override init() {}
    
    func eat(){
        print("吃......")
    }
    
    //暴露给oc,外部调用
    @objc func walk(){
        print("散步......")
    }
}
2.swift 枚举使用,关联值、原始值

​ 使用enum关键字修饰,定义的值放在{}

  • 原始值

在定义枚举的时候,预先设置好填充的值,需要通过 rawValue 获取原始值

enum Vehicle : String{
    case bike = "bicycle"
    case car = "sedan car"
    case bus = "buses"
}

print("direct_up1 = \(Vehicle.car.rawValue)")
print("direct_up2 = \(type(of: Vehicle.bike)) ")

//输出
car1= sedan car
car2 = vehicle

当枚举被定义整数类型的时候,可以隐式赋值一次递增

enum Direct : Int{
    case up = 1
    case down
    case left
    case right
}

print("direct= \(Direct.right.rawValue)") 
direct= 4
  • 关联值

枚举可以存储任意类型的关联值, 创建指定类型的遍历,进行存值取值

enum Phone {
    case number(Int,Int)
    case string(String,String)
    case mix(Int,String)
}

let num = Phone.number(1, 2)
let str = Phone.string("A", "B")
let mix = Phone.mix(1, "C")
print("number = \(num)")

//输出
number = number(1, 2)
  • 关联值比较

模式匹配, 使用switch 的时候,需要把枚举值全部列出来

switch str{
case .number(let num1, let num2):
    print("num1 = \(num1) -- num2 = \(num2)")
    break
case .string(let str1, let str2):
    print("str1 = \(str1) -- str1 = \(str2)")
    break
case .mix(let mix1, let mix2):
    print("mix1 = \(mix1) -- mix2 = \(mix2)")
    break
}

if-let

if case .number(let int, let int2) = num, int == 1{
    print("num1 = \(int) -- num2 = \(int2)")
}else {
    print("存储值不匹配")
}

if case .mix(let int, let string) = str {
    print("num1 = \(int) -- string = \(string)")
}else {
    print("存储值不匹配")
}

直接比较

if case .string(let string, let string2) = str {
    if string == "A"{
        print("第一个值为A")
    }
    
    if string2 == "C"{
        
    }else{
        print("第二个值不为B")
    }
}
3.简单概述swift 初始化器
  • 默认初始化器

    如果没有为自定义类、结构体、枚举定义任何初始化器,Swift会自动提供一个默认初始化器,这个初始化器将所有属性初始化为 0,nil,false 等默认值

  • 自定义初始化器

    可以定义一个或者多个自定义初始化器,适应指定的初始化需求,只要他们的参数或者返回值不同即可

  • 委托初始化器

    初始化器可以调用相同类型中的其他初始化器来执行初始化工作。这种初始化称为委托初始化器(delegating initializer)

    使用 self.init 来调用同一类型中的其他初始化器。在调用其他初始化器之前,所有属性必须被初始化。

  • 可失败初始化器

    初始化器可以返回一个可选类型,如果初始化失败则返回 nil。这种初始化器被称为可失败初始化器(failable initializer)。

    在初始化器的声明后面加上问号 ? 来定义可失败初始化器。如果初始化器执行失败,则返回 nil

  • 必要初始化器

​ 在类的初始化器前加上 required 修饰符,表示所有子类必须实现该初始化器,以确保子类也能正 确初始化。

  • 便捷初始化器

    便捷初始化器的声明以 convenience 关键字开始,后跟初始化器的名。在便捷初始化器中,可以调用同一类中的其他初始化器。

    在便捷初始化器中,可以使用 self.init 来调用同一类中的其他初始化器。在调用其他初始化器之前,必须确保所有属性都已初始化。

    便捷初始化器必须在同一类中定义,不能在子类中定义。

    便捷初始化器不能直接初始化类中的属性,只能通过调用其他初始化器来完成属性的初始化。

    便捷初始化器最终必须调用同一类中的指定初始化器(designated initializer),以确保所有属性都被正确初始化。

后面有时间详细介绍一下以上几种初始化器

4.属性观察期(property observers)

属性观察期,允许你在属性即将设或者已经被设置时候,执行自定义代码。属性观察期可以应用于存储属性和计算属性

主要包括2中类型:willSetdidSet

  • willSet

willSet 观察器会在存储属性的值即将被设置之前调用。它提供一个名为 newValue 的默认参数,您可以使用该参数访问新值。如果您不需要访问新值,则可以使用下划线 _ 代替。

  • didSet

    didSet 观察器会在存储属性的值被设置之后立即调用。它提供一个名为 oldValue 的默认参数,您可以使用该参数访问旧值。如果您不需要访问旧值,则可以使用下划线 _ 代替

class TestClass{
    
    var nickName : String = ""{
        willSet{
            print("willSet = \(newValue) -- nickName = \(nickName)")
        }
        
        didSet{
            print("didSet = \(oldValue) -- nickName = \(nickName)")
        }
    
    }
    var age : Int? {
        willSet{
            print("willSet = \(newValue) -- age = \(age)")
        }
        didSet{
            print("didSet = \(oldValue) -- age = \(age)")
        }
    }
    
    init() {
        print("init")
        nickName = "jone"
        age = 9
    }
}

let testCls = TestClass()

testCls.nickName = "rose"
testCls.age = 28

//输出
init
willSet = rose -- nickName = jone
didSet = jone -- nickName = rose
willSet = Optional(28) -- age = Optional(9)
didSet = Optional(9) -- age = Optional(28)

[!CAUTION]

在初始化中设置属性不会触发willSetdidSet

class TestClass{
    
    var nickName : String = ""{
        willSet{
            print("willSet = \(newValue) -- nickName = \(nickName)")
        }
        
        didSet{
            print("didSet = \(oldValue) -- nickName = \(nickName)")
        }
    
    }
    var age : Int? {
        willSet{
            print("willSet = \(newValue) -- age = \(age)")
        }
        didSet{
            print("didSet = \(oldValue) -- age = \(age)")
        }
    }
    
    init() {
        print("init")
        nickName = "jone"
        age = 9
    }
    
    init(name: String){
        print("init__name")
        self.nickName = name
    }
}

let testCls = TestClass()
let testCls1 = TestClass(name: "jack")

//输出
init
init__name

[!IMPORTANT]

计算属性不支持willSetdidSet会报错

image-20240311134811516.png

5.泛型及泛型关联类型

函数解决问题

解决:解决代码重复性,避免参数不同,方法名相同,代码利用率低情况,提高代码复用性,更加具体清晰抽象,能够了解代码的本质意图,更加的多元化

泛型函数

可以使用泛型来编写函数,使其能够处理任意类型的数据。使用 <T> (T 不是固定的,可以用其他字母代替)来定义泛型类型参数,其中 T 是类型占位符,可以在函数体内用作实际类型。

class TestClass{
        
    init() {
        print("init")
    }
    
    func swap<T>(a : inout T, b : inout T){
        let tmp = a
        a = b
        b = tmp
    }
 
    func add<S>(num1 : S, num2 : S) -> S{
        return num1
    }
}

let testCls = TestClass()

var num1 = 10
var num2 = 20

testCls.swap(a: &num1, b: &num2)

print("num1 = \(num1) -- num2 = \(num2)")
//输出
num1 = 20 -- num2 = 10

泛型类型

结构体和类都可以添加泛型,使其能够适用于任意类型数据

//类 添加泛型
class CumstomStack<E>{
    var arrays = Array<E>()
    func push(item : E){
        arrays.append(item)
    }
    
    func pop() -> E{
        return arrays.removeLast()
    }
    
    func top() -> E?{
        arrays.last
    }
    
    func count() -> Int{
        return arrays.count
    }
    
}

let stack1 = CumstomStack<String>()
stack1.push(item: "123")
stack1.push(item: "abc")

let stack2 = CumstomStack<Int>()
stack2.push(item: 22)
stack2.push(item: 33)

结构体添加协议,结构体中的方法前面必须添加 mutating 修饰

struct stuctStack<E>{
    
    var arrays = Array<E>()
    
    //结构体方法前面需要添加 mutating
    mutating func push(item : E){
        arrays.append(item)
    }
    
    mutating func pop() -> E{
        return arrays.removeLast()
    }
    
    func top() -> E?{
        arrays.last
    }
    
    func count() -> Int{
        return arrays.count
    }
}

关联类型

定义在一个协议中的类型占位符,以便实现类型安全的协议。关联类型允许协议中的方法、属性或下标使用协议实现的具体类型. 协议中可以定义多个关联类型, 使用associatedtype 关键字修饰

protocol ProStack {
    
    //可以定义多个关联类型
    associatedtype Ele ///关联类型
    associatedtype EleType
    
    func push(item : Ele)
    func pop() -> Ele
    func top() -> Ele?
    func count() -> Int
    
    func isEmpty() -> EleType
}

class StackCls : ProStack{
    func isEmpty() -> Int {
        return 1
    }
    
    var arrys =  Array<String>()
    
    typealias Ele = String //使用之前,定义好类型
    typealias EleType = Int
    
    func push(item: String) {
        arrys.append(item)
    }
    
    func pop() -> String {
        arrys.removeLast()
    }
    
    func top() -> String? {
        arrys.last
    }
    
    func count() -> Int {
        arrys.count
    }
}
6.Swift 函数中柯里化

柯里化(Currying)是一种函数式编程的概念,它指的是将一个接受多个参数的函数转换为一系列接受单个参数的函数序列的过程

//正常两个数\三个数相加相加
func add(v1 : Int, v2 : Int) -> Int {
    return v1 + v2
}

func add(v1 : Int, v2 : Int, v3 : Int) -> Int {
    return v1 + v2 + v3
}

//柯里化两个数相加
func _curriedAdd2(_ v1 : Int) -> (Int) ->Int {
    return {  v2 in
        return v1 + v2
    }
}

//柯里化三个数相加
func _curriedAdd3(_ v1 : Int) -> (Int) ->(Int) -> Int {
    
    return {  v2 in
        return {v3 in
            return v1 + v2 + v3
        }
    }
}

let res2 = _curriedAdd2(10)(20)
print("curriedAdd2 = \(res2)")

let res3 = _curriedAdd3(10)(20)(30)
print("curriedAdd2 = \(res3)")

//输出
curriedAdd2 = 30
curriedAdd2 = 60

创建柯里化的通用函数使用泛型

1.使用一个函数作为参数 2.将函数进行一定操作运行后返回,需要返回的值是一个函数 3.返回的这个函数传入C作为参数,然后再返回给B作为参数,然后A 最后是D 4.函数体作为调用fn(a,b,c) 5.把传入进来的函数进行3次拆分,返回3个函数,每个函数带有一个参数

func currying<A,B,C>(_ fn: @escaping (A,B)->C) -> (B) ->(A) -> C {
    return {b in {a in
        return fn(a,b)
    }
    }
}

func currying1<A,B,C,D>(_ fn: @escaping (A,B,C)->D) -> (C) ->(B) -> (A) -> D {
    return {c in 
            print("currying1--c = \(c)")
       return {b in
                print("currying1--b = \(b)")
            return {a in
                    print("currying1--a = \(a)")
                return fn(a,b,c)
            }
        }
    }
}


let addFuc = currying(add)
let addFuc1 = currying1(add)

let resFuc = addFuc(10)(20)
let resFuc1 = addFuc1(10)(20)(30)

print("resFuc = \(resFuc)")
print("resFuc1 = \(resFuc1)")

//输出
currying1--c = 10
currying1--b = 20
currying1--a = 30
resFuc = 30
resFuc1 = 60
7.AnyAnyObject
  • Any 表示任意类型的实例,包括所有类型,包括函数类型。它可以用来表示任何类型的值或实例,包括类实例、结构体实例、枚举实例、函数等。使用 Any 类型时,Swift 不会对其进行任何类型检查或类型转换
var someValue: Any

someValue = 5
someValue = "Hello"
someValue = [1, 2, 3]
  • AnyObject 表示任意类类型的实例。它可以用来表示任何类的实例,但不能表示结构体、枚举或函数等非类类型的值。使用 AnyObject 类型时,Swift 会对其进行运行时类型检查和转换
var someObject: AnyObject
someObject = NSString(string: "Hello")
someObject = NSNumber(value: 42)
8.属性包装器(Property Wrapper)

属性包装器(Property Wrapper)是Swift5.1引入新的特性,允许在属性声明前添加一个属性包装器,以便简化属性实现代码,并且可以自定义属性额外功能

属性包装器可以使用在 classstructenum 中定义一个属性,标记位置在class 或者 struct 上面

[!IMPORTANT]

在类或者结构体中,必须声明一个变量名为wrappedValue 的属性,用于包装后的属性值,不过不用担心忘记写,如果没有系统会有提示

@propertyWrapper
class DefaultString{
    private var defaultStr : String
    //必须要有 wrappedValue 默认属性,否则系统会给提示
    var wrappedValue: String{
        get{
            print("DefaultString__get")
            return defaultStr
        }
        
        set{
            print("DefaultString__set")
            if newValue.count == 0{
                defaultStr = "default"
            }else{
                defaultStr = newValue
            }
        }
    }
    
    init() {
        print("DefaultString__init")
        defaultStr = "default"
    }
}

class Car{
    @DefaultString var name : String
}

let smallCar = Car()

smallCar.name = ""
print("name = \(smallCar.name)")

smallCar.name = "BMW"
print("name = \(smallCar.name)")

//输出
DefaultString__init
DefaultString__set
DefaultString__get
name = default
DefaultString__set
DefaultString__get
name = BMW

猜测探索属性包装器的实现原理(个人见解)

1.被propertyWrapper修饰的类或者结构体,本身也是一个正常的类和结构体

2.值自定义类中定义的属性类型是一个属性包装器的类,被初始

3.重写属性的setget 方法,然后关联,属性包装器类型的间接属性,进行存储

class Fruit{
   private var _name = DefaultString()
    
    var name :  String{
        get{
            return _name.wrappedValue
        }
        
        set {
            _name.wrappedValue = newValue
        }
    }
    
    init(){
        
    }
}

let fruit = Fruit()
fruit.name = "apple"
print("fruit_name = \(fruit.name)")

//输出
DefaultString__init
DefaultString__set
DefaultString__get
fruit_name = apple

有初始化的propertyWrapper参数 ,参数名字必须是 wrappedValue, 如果不是 系统会提示错误

[!CAUTION]

image-20240313134848871.png

@propertyWrapper
class DefaultString{
    private var defaultStr : String
    //必须要有 wrappedValue 默认属性,否则系统会给提示
    var wrappedValue: String{
        get{
            print("DefaultString__get")
            return defaultStr
        }
        
        set{
            print("DefaultString__set")
            if newValue.count == 0{
                defaultStr = "default"
            }else{
                defaultStr = newValue
            }
        }
    }
    
    init() {
        print("DefaultString__init")
        defaultStr = "default"
    }
  
    //参数名字必须是 wrappedValue
    init(wrappedValue : String){
        defaultStr = wrappedValue
    }

}

class Car{
    @DefaultString var name : String // 系统默认初始化
    @DefaultString var name1 = "BMW" // 通过 init(wrappedValue : String) 初始化
}

9. class 和 static 直接的区别

class 关键字可以定义一个类,并且在类中可以使用class 关键字修饰,俗称类方法,不能够被实例对象调用,子类可以重写这个方法

static关键字可以定义一个静态方法,也只能类本身调用,不能被实例对象调用,static修饰方法不能被子类覆盖

class Animal {
    class func eat(){
        print("Animal eat")
    }
    
    static func run(){
        print("Animal run")
    }
}

class Dog : Animal{
    override class func eat() {//重写父类类方法
        print("dog eat")
    }
    //这里不能重写 run 方法, 如果重写 会提示  Cannot override static method
//    override static func run() {
//        
//    }
}

Animal.eat()
Animal.run()
Dog.eat()
Dog.run()//可以调用父类润

//输出
Animal eat
Animal run
dog eat
Animal run
  • structenum 中使用

在结构体和枚举中,只能使用static 不能够使用 class

image-20240313144856953.png

struct AnimalStruct{
    static func eat(){
        print("AnimalStruct eat")
    }
}

enum AnimalEnum{
    static func eat(){
        print("AnimalEnum eat")
    }
}

AnimalStruct.eat()
AnimalEnum.eat()

//输出
AnimalStruct eat
AnimalEnum eat

class 修饰的类方法,可以被子类重写, static 修饰的类方法,不能被子类重新, 大家可以根据自己业务实际情况选择使用那种关键字修饰

最主要区别应该在方法调用上

  • class 修饰的方法 通过动态派发方式调用 运行时决定
  • static 修饰的通过静态直接派发,在编译期间就决定
10 == 和 ===

== 相等运算符

强调两个是否相等,强调类型, 例如 2个整型、浮点型等比较

自定义的结构体也可以实用 == ,不过需要遵循 Equatable 协议

struct AnimalStruct: Equatable{
    var name : String
    var age : Int
    static func eat(){
        print("AnimalStruct eat")
    }
    
    //根据自己要比较的条件,进行判断
    static func == (lhs: Self, rhs: Self) -> Bool{
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
    
    //只对比age
//    static func == (lhs: Self, rhs: Self) -> Bool{
//        return  lhs.age == rhs.age
//    }
    
    //只对比name
//    static func == (lhs: Self, rhs: Self) -> Bool{
//        return lhs.name == rhs.name
//    }
}


enum AnimalEnum{
    static func eat(){
        print("AnimalEnum eat")
    }
}

let ani1 = AnimalStruct(name: "dog",age:10)
let ani2 = AnimalStruct(name: "dog",age:10)

if ani1 == ani2{
    print("AnimalStruct 相等")
}else{
    print("AnimalStruct 不相等")
}

//输出
AnimalStruct 相等

=== 恒等运算符

检查两个同一个类类型的两个实例是否指向同一个内存

class Animal {
    class func eat(){
        print("Animal eat")
    }
    
    static func run(){
        print("Animal run")
    }
}

let aniCls1 = Animal()

let aniCls2 = Animal()

let aniCls3 = aniCls2

if aniCls1 === aniCls2{
    print("aniCls1 == aniCls2 ")
}else{
    print("aniCls1 != aniCls2 ")
}

if aniCls2 === aniCls3{
    print("aniCls2 == aniCls3")
}else{
    print("aniCls2 != aniCls3")
}

//输出
aniCls1 != aniCls2 
aniCls2 == aniCls3