Swift 基础(一)

85 阅读7分钟
1.class 和 struct
class 类 引用类型
  • 可以被继承,子类可以使用父类的方法和属性
  • 变量赋值时候,通过指针copy, 浅拷贝
  • 存储在堆空间,需要考虑内存泄漏(循环引用),操作属性相关是指针操作,移动删除添加等
  • 如果class 继承Object , 拥有runtime 机制, 可以在运行时阶段对类进行检测和解释
  • 使用deinit析构函数释放资源(可以参考OC dealloc)
struct 结构体 值类型
  • 不能够被继承
  • 变量在复制的时候,直接产生一个新的副本,属于深拷贝
  • 存储在栈空间,不需要考虑内存泄漏情况,使用完就回立即释放
  • 方法调用时候,是直接调用,在编译期间就已经确认了方法地址
2.Swift 中 strong, weak ,unowned
  • Swift 内存管理同OC管理机制一样,通过引用技术,当引用计数retaincount 为 0 的时候进行释放资源 OC 调用dealloc , Swift 调用 deinit
  • 都是属性修饰关键字
  • strong:强引用,修饰属性的时候,默认使用strong , 对象的引用为强引用,引用技术retaincount 加 1
  • weak: 弱引用,如果对象或者属性 被weak修饰,指针引用的是是弱引用,引用计数不改变(不会自动+1),释放的时候 ,进程会自动设置为nil
  • unowned:无主引用, 同weak 使用方式和功能一样,唯一不同的是,在释放的时候对象销毁的时候,不会自动设置nil, 会产生野指针,再次访问该对象会出现崩溃,一般用在该对象不会销毁情况,解决循环引用问题
  • weak、unowned 都用在解决循环引用问题,unowned效率高一些,很多公司统一使用 weak (包括我在内)
3.copy on write 写时复制

这个针对值类型数据生效,值类型在复制对象与源对象内存指针指向的是同一个内存地址,只有当修改对象的时候,才会在内存中重新创建一个新对象

  • 系统标准库提供:Struct、String、Array、Dictionary、Set 都是用Copy on write技术、这样可以提升性能,存储在栈空间。
  • 只有在写操作的时候,先对原始数据复制一个副本,然后再修改副本

原理:

源码在 OptimizationTips.rst 里发现如下代码, 使用泛型

final class Ref<T> {
  var val : T
  init(_ v : T) {val = v}
}

struct Box<T> {
    var ref : Ref<T>
    init(_ x : T) { ref = Ref(x) }

    var value: T {
        get { return ref.val }
        set {
          if (!isKnownUniquelyReferenced(&ref)) {
            ref = Ref(newValue)
            return
          }
          ref.val = newValue
        }
    }
}

isKnownUniquelyReferenced , 用来检查实例是不是唯一引用

在属性赋值的时候,调用set方法,里面判读是否有多个 reference ,如果有多个就进行拷贝,没有则不会

4.Swift 标准库 Array,String,Dictionary,Set,设计为值类型
  • 值类型跟引用类型对比
  • 值类型可以高效的使用内存,效率比引用类型高
  • 值类型是在栈上操作使用完就回被释放掉,引用类型在堆上操作引用计数为0释放
  • 栈操作只涉及到了指针的移动,堆操作设计指针移动,合并,重链接
5.Swift高阶函数 mapflatMapcompactMapfilterreduce

map

​ 数组中的每一个元素调用一次闭包函数,并返回该元素所映射的值

let array = [8,9,10,11]

//修改数字里面内容
let tmp1 = array.map{_ in 2}

//数组内数字都转换为指定字符串
let tmp2 = array.map{_ in "$0"}

// 数组内数字 * 2之后 转换为字符串
let strs = array.map{"\($0 * 2)"}

//生成二位数组,每个数字重复5次
let maps = array.map { Array(repeating: $0, count: 5) }

print("tmp1 = \(tmp1)")
print("tmp2 = \(tmp2)")
print("strs = \(strs)")
print("maps = \(maps)")

//输出结果
tmp1 = [2, 2, 2, 2]
tmp2 = ["$0", "$0", "$0", "$0"]
strs = ["16", "18", "20", "22"]
maps = [[8, 8, 8, 8, 8], [9, 9, 9, 9, 9], [10, 10, 10, 10, 10], [11, 11, 11, 11, 11]]

flatMap

​ 与map类似,数组中的每个元素调用闭包中的规则,返回一个新的数组

flapmap 会把数组中的多维数组进行降维操作,都会变成一维数组,可以过滤元素 nil

let array1 = [[8,9],[10,11],[8,9,10,11]]

let mapNums = array1.flatMap { $0 }

print("mapNums = \(mapNums)")

//输出结果
mapNums = [8, 9, 10, 11, 8, 9, 10, 11]

compactMap

compactMap 在 Swift 4.1 之后对flapmap 的一个重载方法的重命名,同样数组每个元素都要执行一下闭包里面个规则,返回一个新的数组,不同之处在于会出去规则之后的 nil 值,对可以选项进行解包操作

let array2 = ["2","3","c","5"]

let map2 = array2.map { Int($0) }
let flapmap2 = array2.flatMap{ Int($0) }
let compactMap2 = array2.compactMap { Int($0) }
print("map2 = \(map2)")
print("flapmap2 = \(flapmap2)")
print("compactMap2 = \(compactMap2)")

//输出结果
map2 = [Optional(2), Optional(3), nil, Optional(5)]
flapmap2 = [2, 3, 5]
compactMap2 = [2, 3, 5]

filter

过滤数组中元素,筛选出满足闭包中条件元素

let array3 = ["2","3","c","5"]

let res = array3.filter{$0 == "2"}

print("res = \(res)")
//输出结果
res = ["2"]

reduce

对集合中每个元素和叠加器做对应操作,根据闭包中规则整合数组中的数据

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

initialResult : 初始值, (Result($0), Element) Element 需要处理的元素,Result 处理后返回给Result 作为下次闭包的参数

简要概述: initialResult 作为起始值, 然后执行闭包里面规则,运算完之后 把结果返回,作为下次参数执行闭包的参数,然后遍历整个数组

let res1 = array.reduce(2, +)
print("res1 = \(res1)")

let res2 = array.reduce(2) { result, num in
    print("result2 = \(result) -- num2 = \(num)")
    return result + num
}
print("res2 = \(res2)")

let res3 = array.reduce(2){
    print("result3 = \($0) -- num3 = \($1)")
    return $0 + $1
}
print("res3 = \(res3)")

// 输出结果
res1 = 40
result2 = 2 -- num2 = 8
result2 = 10 -- num2 = 9
result2 = 19 -- num2 = 10
result2 = 29 -- num2 = 11
res2 = 40

result3 = 2 -- num3 = 8
result3 = 10 -- num3 = 9
result3 = 19 -- num3 = 10
result3 = 29 -- num3 = 11
res3 = 40
6.defer 使用场景

​ 使用defer 代码块,在函数方法执行完之前,执行defer中代码,或者抛出错误也需要执行一些操作,相当于延时操作,一般用来做资源释放和销毁使用

​ 在同一个函数中,如果有多个defer语句要执行,执行顺序是倒序的

class Car {
    let array = [1,2,3,4]
    init(){}
    func testDefer(){
        print("开始")
        defer{
            print("defer1")
        }
        
        defer{
            print("defer2")
        }
        
        defer{
            print("defer3")
        }
        
        print("结束")
    }
}

let car1 = Car()
car1.testDefer()

//输出
开始
结束
defer3
defer2
defer1

上面说函数退出之前执行不是绝对的

[!IMPORTANT]

A defer statement is used for executing code just before transferring program control outside of the scope that the defer statement appears in.

这段大概意思是说,在当前作用域退出的时候会执行defer中代码

class Car {
    let array = [1,2,3,4]
    init(){}
    
    func testDefer(){
        print("开始")
        defer{
            print("defer1")
        }
        
        defer{
            print("defer2")
        }
        
      //每次作用域执行完,执行defer
        for _ in 0 ..< 3{
            print("循环开始")
            defer{
                print("defer3\n")
            }
            print("循环结束")
        }
        
        if array[3] == 4{
            print("条件满足开始")
            defer{
                print("defer4")
            }
            print("条件满足结束")
        }
        
        print("结束")
    }
}

let car1 = Car()
car1.testDefer()

//输出
开始
循环开始
循环结束
defer3

循环开始
循环结束
defer3

循环开始
循环结束
defer3

条件满足开始
条件满足结束
defer4
结束
defer2
defer1
7.String 、NString 两者区别

关系

  • String : Swift中原生字符串类型,值类型
  • NSStringFoundation 中 Objective-C 类型,引用类型

互相转换

  • NSString 转换为 String

    • 使用 as String 进行转换:

      let nsString: NSString = "Hello"
      let swiftString: String = nsString as String
      
    • 使用构造函数进行转换

      let nsString: NSString = "Hello"
      let swiftString: String = String(nsString)
      
  • String 转换为 NSString

    • 使用 as NSString 或者 String 的构造函数进行转换

      let swiftString: String = "Hello"
      let nsString: NSString = swiftString as NSString
      
    • 使用构造函数进行转换

      let swiftString: String = "Hello"
      let nsString: NSString = NSString(string: swiftString)
      

不同点

  • String 值类型,在传递和赋值时会进行复制, NSString 是引用类型,通过引用传递和赋值
  • String 支持原生操作,例如 遍历、拼接、查找、替换等。NSString 需要使用OC方法
  • String 是 Unicode 兼容的,它的字符编码采用 UTF-8,而 NSString 的字符编码采用 UTF-16。
  • 各自Api不同
8.self 与 Self
  • self

self关键字,用在当前实例或者类型实例,可以使用self访问当前实例属性、方法、或者下标,在闭包中要使用self 显示的引用

  • Self

Self 是一个关联类型标识符,用于泛型约束中。它表示实际使用了该泛型类型的类型。当定义泛型时,可以使用 Self 关键字来指代当前使用泛型的类型本身。

protocol MyProtocol {
    static func create() -> Self
}

struct MyStruct: MyProtocol {
    static func create() -> MyStruct {
        return MyStruct()
    }
}
9.subscript 为 类 、结构体 添加下标

使用subscript语法添加下标,可以用在实例对象和类实例中

class Person {
    var body = ["eye","arm","hair","nose"]
    init() {
        
    }
    subscript(index : Int) -> String{
        set(val){
            body[index] = val
        }
        get{
            return body[index]
        }
    }
  
   //类添加下标方法
    class subscript(index : Int) -> String{
        set(val){
            jobs[index] = val
        }
        get{
            return jobs[index]
        }
    }
}

let p = Person()
p[1] = "eyes"
print("p[0] = \(p[0])")
print("p[1] = \(p[1])")

//输出
p[0] = eye
p[1] = eyes
10.延迟存储属性Lazy

使用Lazy修饰的存储属性必须使用 var,因为let 需要初始化,在第一次访问的时候才被赋值,并不能保证线程安全, 延迟初始化可以提高性能,因为它避免了在对象初始化时不必要的计算。

class SomeClass {
    init() {
        
    }
    lazy var nameLabel: UILabel = {
        let lb = UILabel()
        lb.text = "张三"
        print("初始化label")
        return lb
    }()
}

let someCls = SomeClass()

print("第一次访问 = \(someCls.nameLabel.text)")
print("第二次访问 = \(someCls.nameLabel.text)")

//输出
初始化label
第一次访问 = Optional("张三")
第二次访问 = Optional("张三")