Swift

Swift笔记

2021-01-27  本文已影响0人  我是一只攻城狮_ifYou

5.函数

1.基本定义
func sum(num1:Int, num2: Int)-> Int {
    return num1 + num2
 }

调用:
sum (num1: 1, num2 :2)
2.相关注意点
当函数体内的表达式是简单的,可以不写return
func sum(num1:Int, num2: Int)-> Int {
     num1 + num2
 }
func sum(_ num1:Int, _num2: Int))-> Int {
     num1 + num2
 }

func sum(by num1:Int, num2: Int))-> Int {
     num1 + num2
 }
可变参数定义: 在参数类型后面加上...
    func sum(_ number:Int...) -> Int {
        var total = 0
        for num in numbers {
            total += number
        }
        return total
 }
    sum(10,11,12)
注:调用时,传入的实参需要加&,该函数可以令number的值修改为20
 
     var number = 10
     
     func sum (num: inout Int) {
        num = 20
     }
     sum(&number)

注: swift有main函数,只是不需要我们自己写

1.func sum(Int num1,Int num2)-> Int {
        return num1 + num2
   }
   函数类型为:(Int,Int) -> Int

2.func add() -> {

  }
  函数类型为:() ->()
 
 定义变量: var fn: (Int,Int) -> Int = sum
 fn(1,2) 结果为3 (注:此时调用函数是不需要参数标签的)

注:返回值是函数类型的函数,叫做高阶函数

注: release模式下,没太大必要使用@inline

含义:将函数定义在函数内部
    func forward (_ isAdd :Bool) -> (Int) ->Int {
        func add (_ num :Int) ->Int {
            return num + 1
        }
 
        func minix (_ num:Int)-> Int {
            return num - 1
        }
        
        return isAdd == True? add:minix
    }
 
    forward(true)(3) 结果为:4
    forward(false)(3) 结果为:2

6.枚举

enum Season {
     case Spring
     case Summer
     case Autumn
     case Winter
}
等价于:
enum Season {
     case Spring,Summer,Autumn,Winter
}

print(Season.spring)
enum Score {
    case Point(Int)
    case grade(Character)
 }
 var score = Score.Point(90)
 score = .grade("B")
enum AAA :String {
    case aaa
    case bbb
 }
表示成员的关联值类型为String
enum Season : String {
    case spring = "1",summer = "2",autumn = "3",winter = "4"
 }
MemoryLayout<Season>.size = 1
MemoryLayout<Season>.stride = 1
MemoryLayout<Season>.alignment = 1
//因为外部无法修改spring等枚举值,所以spring为原始值,只需要1个字节进行存储即可
enum test {
    case num1(Int, Int, Int)
    case num2(Int,Int)
    case num3(Int)
    case num4(Bool)
    case num5
 }
MemoryLayout<test>.size = 25
MemoryLayout<test>.stride = 32
MemoryLayout<test>.alignment = 8
enum test {
    case num
 }
MemoryLayout<test>.size = 0
MemoryLayout<test>.stride = 1
MemoryLayout<test>.alignment = 1
enum test {
    case num(Int)
 }
MemoryLayout<test>.size = 8
MemoryLayout<test>.stride = 8
MemoryLayout<test>.alignment = 8
enum test {
    case num(Int)
    case num2
 }
MemoryLayout<test>.size = 8+1 = 9
MemoryLayout<test>.stride = 16
MemoryLayout<test>.alignment = 8

7.可选项

let a:Int? = nil
let b:Int? = 2
if let c = a ?? b {
    print(c)
}
类似于if a!= nil || b != nil
 
if let c = a, let d =b {
    print(c,d)
}
类似于if a!= nil && b != nil

8.结构体与类

9.属性

  1. 类似于成员变量这个概念
  2. 存储在实例的内存中
  3. 结构体、类可以定义存储属性
  4. 枚举不可以定义存储属性
  class Person {
      var age:Int = 0
      //计算属性
      var num :Int {
          set {
              age = newValue *2
          }
          get {
              return newValue
          }
      }
  }

tips: 枚举的原始值就是通过只读计算属性实现的,不需要存储原始值(rawValue)

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会初始化

struct Point {
  var x = 0
  var y = 0
  lazy var z = 0
}
let p = Point ()
print(p.z) //会报错
因为当创建p时,结构体的内存已经固定,而当p.z调用时,才会令z赋值为0,此时需要改变对应结构体的内存,令结构体Point的z值改变,但p又是使用let,无法改变内存,矛盾,所以只有var才可以访问,但此时依然可以访问x和y

可以为非lazyvar定义的存储属性设置属性观察者

struct Circle {
  var radius :Double {
      willSet {
        print(newValue)
      }
      didSet {
        print(oldValue, radius)
      }
  }
  init() {
      self.radius = 1.0
  }
}
radius依然是个存储属性,因为括号里面是willSet和didSet
var num : Int {
    set {
      print (newValue)
    }
    get {
      return 10
    }
}
num = 11 // 11
print (num) // 10
可以通过static定义类型属性,如果是类,也可以用关键字class
struct Car {
    static var count: Int = 0 //初始值一定要有
    init() {
        Car.count += 1
    }
}

注:存储实例属性时可以没有初始值,但存储类型属性必须要有初始值,因为类型没有像实例那样的init初始化器来初始化存储属性
public class fileManager {
  public static let share = fileManager()
  private init() {
  
  }
}
1.类型存储属性本质上就是一个全局变量,只是编译器在限制了对象的访问范围.
2.使用static修饰的变量,只能说,是变量位于全局区

10.方法

枚举、结构体、类都可以定义实例方法、类型方法

class Car {
  static var count = 0
  init() {
    Car.count += 1
  }
  static func getCount() -> Int { count} //此时,在类型方法中,只能调用类型属性,实例属性无法在类型方法里调用
}

let c0 = Car()
let c1 = Car()
let c2 = Car()
print (Car.getCount()) //3 

/* self: 在实例方法中,self代表实例,
         在类型方法中,self代表类型
*/
struct Point { 
  var x = 0.0
  var y = 0.0
  @discardableResult  mutating func moveX(alterX : Double) ->Double {
      x += alterX
      return x
  }
}
var p  = Point ()
p.moveX(alterX: 10)

@discardableResult 可以消除函数调用后返回值未被使用的警告
enum stateSwitch {
  case low,middle
  mutating func next() {
    switch self {
      case .low:
            self = .middle
      case .middle:
            self = .low
    }
  }
}
  class Point {
    var x = 0.0
    var y = 0.0
    subscript (index: Int) ->Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue
           }
        }
        get {
          if index == 0 {
                return x
            } else if index == 1 {
                return y
           }
          return 0
        }
    }
  }
p[0] = 22.2
struct Point {
  var x = 0
  var y = 0
  class PointManager {
      var point = Point()
      subscript (index: Int) -> Point {
          set {
              point = newValue
          }
          get {
              return point
          }
      }
  } 
}

var pm = PointManager ()
pm[0].x = 2
//以上调用不会报错,等价于pm[0] = Point(2,pm[0].y)
//若不加上上面的set,则会报错
//若将point从结构体改为类,则以上调用也不会报错

11.继承

class Animal {
    func speak() {
          print("speak")
    }
    subscript (index:Int) ->Double {
          return index
    }
}

class dog : Animal {
    //重写实例方法
    override func speak() {
          super.speak()
          print("speak")
    }
    //重写下标
    override subscript (index:Int) ->Double {
          return super[index] +1
    }
}
class Circle {
    var radius : Int = 1
}
class subCircle : Circle {
    override var radius: Int {
          willSet {
                print(newValue)
          }
          didSet {
                print(oldValue,radius) //此时子类只是为父类增加属性观察器,并没有将父类的存储属性重写为计算属性.所以此时调用属性不需要super
          }
    }
}

12.初始化

//指定初始化器
init (Parameters) {
    //初始化代码
}
//便捷初始化器
convenience init (Parameters) {
    //初始化代码
}

//枚举的初始化器调用
enum Season :Int {
  case Spring = 0,Summer,Autumn,Winter
}
Season(rawValue:1)

13.可选链

14.协议

protocol Drawble {
    func draw()
    var x: Int { get set }
    var y: Int { get }
    subscript { index: Int } -> Int { get set }
}

protocol Drink {
    var z: Int { set get }
}

class Person : Drawble , Drink {

}
protocol Livable { }
protocol Runner { }
class Person { }

//接收Person或者子类的实例
func fn0(obj: Person) {}
//接收遵守Livable协议的实例
func fn1(obj: Livable) {}
//接收同时遵守Livable、Runner协议的实例
func fn2(obj: Livable & Runner) {}
//接收同时遵守Livable、Runner协议,并且是Person或者其子类的实例
func fn3(obj: Livable & Runner & Person) {}
//接收同时遵守Livable、Runner协议,并且是Person或者其子类的实例,也可以这么写
typealias RealPerson = Livable & Runner & Person
func fn3(obj: RealPerson) {}
enum Season : CaseIterable {
    case spring, summer, autumn, winter
}
let season  = Season.allCases
print (season.count) // 4
//元类型调用的初始化器必须是required修饰的
class Animal { required init () { }}
class Cat: Animal { }
class Dog: Animal { }
func create(_ clses: [Animal.Type]) -> [Animal] {
  var arr = [Animal]()
  for cls in clses {
      arr.append(cls.init())
  }
return arr
}
print (create([Cat.self,Dog.self]))
  1. swift还有个隐藏的基类:Swift._SwiftObject
  2. Self代表当前类型,一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型),但如果Self用在类中,要求返回时调用的初始化是required
protocol Runner {
   func test() -> Self
}
class Person : Runner {
   required init() { }
   func test() -> Self {
       type(of: self).init()
   }
}

//以下4句代码本质是一样的
var p0 = Person()
var p1 = Person.self()
var p2 = Person.init()
var p3 = Person.self.init()
  1. XX.self的区别
    若只想表达元类型,则需要使用X.self

15.错误处理

class MyError : Error  { }

func divide (_ num1:Int, _ num2:Int) throws -> Int {
    if num2 == 0 {
        throw MyError()
    } 
    return num1 / num2
}
func test () {
print("1")
do {
    print("2")
    print(try divide(200,0))
    print("3")
} catch let SomeError.illegalArg(msg) {
    print("参数异常")
} catch let SomeError.illegalArg(msg) {
    print("数组越界")
}
print("4")
}
test()
  1. 可以使用try?try!调用可能会抛出Error的函数,这样就不用去处理Error
func test () {
  print("1")
  print("2")
  print(try? divide(200,10)) // Optional(20),Int?
  print(try? divide(200,0)) // nil
  print(try! divide(200,10)) // 20,Int
  print("3")
}
test()
var a = try? divide(20,0)
var b = Int?
do {
    b = try divide(20,0)
} catch {
    b = nil
}
//a与b是等价的

6.rethrows声明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它将会向上抛

func exec (_ fn: (Int,Int) throws ->Int,_ num1: Int, _ num2 : Int ) rethrows {          
    print (try fn(num1,num2))
}
try exec(divide, 20, 0)

rethrows 仅代表抛出的异常是因为传入的参数导致异常,而与函数(闭包)内部的逻辑导致异常无关
throws 代表抛出的异常是因为函数(闭包)内部的逻辑可能导致的异常
其他二者并无本质区别,作用其实差不多

  1. defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
    defer语句将延迟至当前作用域结束之前执行
func open (_ filename: String) -> Int {
    print("open")
    return
}
func close (_ file: Int) {
    print("close")
}

func processFile (_ filename: String) throws {
    let file = open(filename)
    defer {
        close (file)
    }
    try divide (20,0)
    // close将会在这里调用
}

defer语句的执行顺序与定义顺序相反

func fn1 () {}
func fn2 () {}
func fn3 () {
    defer { fn1() }
    defer { fn2() }
}
fn3()

//执行顺序为fn2,fn1
  1. 断言
func divide (_ v1:Int, _ v2:Int) {
    assert(v2 != 0, "除数不能为0")
    return v1 / v2
}
divide (20,0)

9.fatalError函数
如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这里是无法通过do-catch捕捉的错误),使用了fatalError函数,就不需要再写return

func test (_ num : Int) {
    if num > 0 {
        return 1
    }
    fatalError("num不能小于0")
}

在某些不得不实现,但不希望别人调用的方法,可以考虑内部使用fatalError函数

class Person { required init() {} } 
class Student : Person { 
     required init() { fatalError("不要使用这个初始化!")}
     init (score: Int) {}
}

16.泛型

func swapValue<T> (_ a: inout T, _ b: inout T) {
    (a,b) = (b,a)
}

// 同理,泛型也可以写多个
func swapValue<T,T1,T2,T3> (_ a: inout T, _ b: inout T) {
    (a,b) = (b,a)
}

//当需要将泛型赋值给一个函数时,只需要明确函数的具体类型即可
var fn: (inout Int, inout Int) -> () =  swapValue
fn(&1,&2)
为类时,进行类对象创建,由于需要明确具体的类型,但又无法通过初始化器等方式明确具体类型,则需要在创建时明确类型,即加<类型名>
为函数时,因为函数在调用时就知道内部泛型是什么类型,所以不需要明确类型
class stack<E> {
    var elements = [E]()
    func pop() ->E {
        elements.removeLast()
    }
    func top() ->E {
        elements.last!
    }
}

泛型的继承:

class stack<E> {
    var elements = [E]()
    func pop() ->E {
        elements.removeLast()
    }
    func top() ->E {
        elements.last!
    }
}
class subStack<E> : Stack<E> { }

枚举中的泛型:

enum Score <T> {
    case point(T)
    case grade(String)
}
let score3 = Score<Int>.grade("A")
枚举中,就算没有用到泛型,如上,没有使用到point,但当为score3分配内存时,还是需要知道泛型T的类型的,所以需要如上写法

associatedtype关键字
以上定义泛型的方式成为泛型参数,但泛型参数只能用在类,结构体,枚举中,不能用在协议中,协议想使用泛型需要使用associatedtype(关联类型)

  1. 作用:给协议中用到的类型定义一个占位名称(协议中可以拥有多个关联类型)
protocol Stackable {
    associatedtype Element // 关联类型
    mutating func push(_ element: Element)
    mutating func pop() -> Element
}

class StringStack : Stackable {
    typealias Element = String //可不写
    //这一步即明确遵守协议的类型(有关联类型,在实现协议时都需要指明类型的名称)
    var elements = [String]()
    func push(_ element: String) { elements.append(element)}
    func pop()-> String { elements.removeLast()}
}

var ss = StringStack()
ss.push("aaa")
ss.pop()

//可以通过where对泛型进行进一步的约束,就是一个有条件约束的泛型
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2:S2) -> Bool where S1.Element == S2.Element,S1.Element : Hashable {
      return false
}

不透明类型

17.关于String与Array

关于String
1.内存地址从:
代码区--->常量区--->全局区(数据段)--->堆空间--->栈空间--->动态库
2.常通过对Mach-O文件进行窥探,字符串是位于常量区
3.字符串的两种内存方式:

4.append操作

5.符号的延迟绑定时通过dyld_stub_binder完成
6.注:jmpq *0xb31(%rip)格式的汇编指令,占用6个字节

18.高级运算符

溢出运算符 : &+,&-,&*
溢出的结果依然在取值的范围内,实际上是一个循环取值
重载运算符 : 类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做运算符重载
1.类似于函数重载,当运算符重载写在结构体中时,需要令重载函数修饰为static
2.在函数定义前加上关键字prefix(前缀函数)或者关键字postfix(后缀函数)
3.要想得知2个实例是否等价,一般做法是遵守Equatable协议,重载==运算符,与此同时,等价于重载了!=运算符

enum Answer: Equatable {
    case wrong(Int)
    case right
}

var s1 = Answer.wrong(10)
var s2 = Answer.wrong(10)

print(s1 == s2) //结果为true

4.Swift为以下类型提供默认的Equatable实现

5.引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===!==
6.要想比较2个实例的大小(Comparable)

struct Student: Comparable {
  var age: Int
  var socre: Int
  init(score: Int, age: Int) {
      self.socre = score
      self.age = age
  }
  
  static func < (lhs: Student, rhs: Student) -> Bool {
      (lhs.socre < rhs.socre) || (lhs.socre == rhs.socre && lhs.age < rhs.age)
  }
  
  static func > (lhs: Student, rhs: Student) -> Bool {
      (lhs.socre > rhs.socre) || (lhs.socre == rhs.socre && lhs.age > rhs.age)
  }
  
  static func <= (lhs: Student, rhs: Student) -> Bool {
      !(lhs > rhs)
  }
  
  static func >= (lhs: Student, rhs: Student) -> Bool {
      !(lhs < rhs)
  }
}
  1. 自定义运算符
    在全局作用域使用operator进行声明
// MARK - 定义
prefix operator +++  //前缀运算符
postfix operator ---  // 后缀运算符
infix operator +- : PlusMinusPrecedence // 中缀运算符 : 优先级组

// MARK - 优先级组
precedencegroup PlusMinusPrecedence { 
    associativity: none //结合性(left/right/none)
    higherThan: AdditionPrecedence //比谁的优先级高
    lowerThan: MultiplicationPrecedence //比谁的优先级低
    assignment: true //代表在可选链操作中拥有跟赋值运算符一样的优先级
}

struct IntType {
    var num1: Int
    var num2: Int
    init(num1:Int, num2: Int) {
        self.num1 = num1
        self.num2 = num2
    }
    
    static func +- (lhs: IntType, rhs: IntType) -> Int {
        (lhs.num1 + rhs.num1) * (lhs.num2 - rhs.num2)
    }
}

let opera1 = IntType(num1: 10, num2: 5)
let opera2 = IntType(num1: 5, num2: 1)
print(opera1 +- opera2)

19.扩展

Swift中的扩展,有点类似于OC中的分类

  1. 扩展可以为枚举结构体协议添加新功能
    可以添加方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等等

  2. 协议不能办到的事情:

    • 不能覆盖原有的功能
    • 不能添加存储属性,不能向已有的属性添加属性观察者
    • 不能添加父类
    • 不能添加指定初始化器,不能添加反向初始化器

1.扩展是不允许影响原来类型的内存结构,所以不能添加存储属性
2.扩展是不允许添加父类,因为涉及到继承,也有可能影响原来类型的内存结构

注:便捷初始化器和指定初始化器只有在类中有具体的区分,在结构体中只有初始化器这一说,只能定义为init方式

  1. 如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展的方式让它遵守这个协议
protocol testProtocol {
    func run ()
}

class Person {
    func run (){
        print("run")
    }
}

extension Person: testProtocol{}
  1. Swift的扩展可以给协议扩展方法
    • 扩展可以给协议提供默认实现,也间接实现可选协议的效果
    • 扩展可以给协议扩充协议中从未声明过的方法
    protocol testProtocol {
        func run ()
    }
    
    extension testProtocol {
        func run () {
            print("testProtocol_run")
        }
    
        func talk() {
            print("testProtocol_talk")
        }
    }
    
    class Person : testProtocol {
        func run() {
            print("Person_run")
        }
    
        func talk() {
            print("Person_talk")
        }
    }
    
    let p : testProtocol = Person()
    p.run() // Person_run
    p.talk() // testProtocol_talk
    
    talk方法出现这个结果是因为,在协议中是没有talk的声明的,因而不能保证今后实例中会包含这个talk的实现,就不会去实例中寻找,优先从扩展中寻找
    
  2. 带条件的扩展
class Stack<E> {
    var elements = [E]()
    func push(_ element:E) {
        elements.append(element)
    }
    
    func pop() {
        elements.removeLast()
    }
    
    func size() -> Int {
        return elements.count
    }
}

//扩展中依然可以使用原类型中的泛型类型
extension Stack {
    func top() -> E {
        return  elements.last!
    }
}

//符合条件才扩展
extension Stack : Equatable where E: Equatable {
    static func == (left: Stack, right: Stack) -> Bool {
        left.elements == right.elements
    }
}

20.访问控制

  1. 在访问控制这块,Swift提供了5个不同的访问级别(以下是从高到低排序,实体指被访问级别修饰的内容)
    • open: 允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
    • public: 允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
    • internal: 只允许在定义实体的模块中访问,不允许在其他模块中访问
    • fileprivate: 只允许在定义实体的源文件中访问
    • private: 只允许在定义实体的封闭声明中访问

绝大部分实体默认都是internal级别
系统类型默认是public级别

  1. 一个实体不可以被更低访问级别的实体定义,比如:
    • 变量/常量类型 >= 变量/常量
    • 参数类型、返回值类型 >= 函数
    • 父类 >= 子类
    • 父协议 >= 子协议
    • 原类型 >= typealias
    • 原始值类型、关联值类型 >= 枚举类型
    • 定义类型A时使用到的其他类型 >= 类型A
    fileprivate class Person { }
    
    internal var person : Person
    //会报错,因为不满足条件1
    
  2. 元组
    • 元组类型的访问级别是所有成员类型最低的那个
    internal struct Dog {}
    
    fileprivate struct Cat {}
    
    //(Dog,Cat)的访问级别是fileprivate
    fileprivate var data1: (Dog , Cat)
    private var data2: (Dog, Cat)
    
  3. 泛型
    • 泛型类型的访问类型是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个
    internal class Car {}
    fileprivate class Dog {}
    public class Person<T1,T2> {}
    
    //Person<Car,Dog>的访问级别是fileprivate
    fileprivate var p = Person<Car,Dog>()
    
  4. 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
    • 一般情况下,类型为privatefileprivate,那么成员/嵌套类型默认也是privatefileprivate
    • 一般情况下,类型为internalpublic,那么成员/嵌套类型默认是internal
  5. 直接在全局作用域下定义的private等价于fileprivate
  6. getter/setter
    • gettersetter默认自动接收它们所属环境的访问级别
    • 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
    fileprivate(set) public var num = 10
    
    class Person {
        private(set) var age = 0
        fileprivate(set) public var weight: Int {
            set {}
            get { return 10 }
        }
        internal(set) public subscript(index: Int) -> Int {
            set {}
            get { index }
        }
    }
    
    • getter是不能比setter更低的访问级别的,只能有private(set),这个操作
  7. 初始化器
    • 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器,因为public类的默认初始化器是internal级别
    • required初始化器必须跟它所属类拥有相同的访问级别
    • 如果结构体有private/fileprivate的存储实例属性,那么它的成员初始化器也是private/fileprivate,否则默认就是internal (其中有关required的部分替换为required初始化器 >= 它的默认访问级别)
    • 若在结构体中,成员变量中至少有一个成员变量使用private修饰,则所有带成员变量的默认初始化器都不能使用,只能使用无参的默认初始化器
  8. 枚举类型的case
    • 不能给enum的每个case单独设置访问级别
    • 每个case自动接收enum的访问级别(public enum定义的case也是public)
  9. 协议
    • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别(public协议定义的要求也是public)
    • 协议实现的访问级别必须>=类型的访问级别,或者>=协议的访问级别
    • 即协议的访问控制与枚举一致
  10. 扩展
    • 如果有显示设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
    • 如果没有显示设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
    • 可以单独给扩展添加的成员设置访问级别
    • 不能给用于遵守协议的扩展显示设置扩展的访问级别
  11. 在同一文件中的扩展,可以写成类似多个部分的类型声明
    • 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
    • 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
    public class Person {
        private func run0 () {}
        private func eat0 () {
            run1()
        }
    }
    
    extension Person {
        private func run1() {}
        private func eat1() {
            run0()
        }
    }
    
    extension Person {
        private func eat2 (){
            run1()
        }
    }
    
  12. 有关varlet定义函数变量
    struct Person {
        var age: Int
        func run(_ v: Int) {
            print("func run",age,v)
        }
        static func run(_ v: Int) {
            print("static func run", v)
        }
    }
    
    //当存在类型方法和实例方法重名时,默认是调用类型方法
    var fn = Person.run
    fn(20) //static func run 20
    
    
    //可以显示声明调用实例方法类型来调用
    //即需要调用一次person实例,才能获取到真正的对象,通过该对象去调用对应的对象方法
    var fn1: (Person) -> (Int) -> () = Person.run
    fn1(Person(age: 20))(30) //func run 20 30
    
  13. 方法的重写与协议的访问控制类似,即重写的访问级别>= 类的访问级别,或者>=被重写方法的访问级别
    • 父类的成员不能被成员作用域外定义的子类重写
    class Person {
        private func run (){}
    }
    
    class Student: Person {
        override func run (){} //会报错
    }
    
    //若将Student的类定义在Person中,则可以
    

21.内存管理

跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)

1. Swift的ARC中有3种引用

注: weakunowned只能用在类实例上面

Swift中自动释放池的创建:
class Person {
    var name : String
    var age : Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    func run() {
        print("Person Run")
    }
}

autoreleasepool {
    let p = Person(name: "Jack", age: 20)
    p.run()
}

  1. 循环引用问题

    1. weakunowned都能解决循环引用问题,unowned要比weak少一些性能消耗
      • 在生命周期中可能会变为nil的使用weak
      • 初始化赋值后再也不会变为nil的使用unowned

    2.闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)

    以下代码会产生循环引用,导致Person对象无法释放(看不到Person的deinit被调用)
    class Person {
        var fn: (() -> ())?
        func run() {
            print("Person Run")
        }
        deinit {
            print("deinit")
        }
    }
    
    func test() {
        let p = Person()
        p.fn = {
            p.run()
        }
    }
    
    test()
    

    上述代码的解决循环引用的方式是: 通过添加捕获列表,解决循环引用问题

    class Person {
        var fn: (() -> ())?
        func run() {
            print("Person Run")
        }
        deinit {
            print("deinit")
        }
    }
    
    func test() {
        let p = Person()
        p.fn = {
            [weak p] in
            p?.run()
        }
    }
    
    test()
    

    Person类函数定义有参数类型时,在捕获列表后紧跟(),声明参数即可,即:

    class Person {
        var fn: ((Int) -> ())?
        func run() {
           print("Person Run")
        }
        deinit {
            print("deinit")
        }
    }
    
    func test() {
        let p = Person()
        p.fn = {
            [weak p](num: Int) in
            p?.run()
        }
    }
    
    test()
    

    另外,也可以在捕获列表中定义新的变量,或者重新定义变量名等

    p.fn = {
        [weak wp = p, unowned up = p, a = 10 + 20]() in
        wp?.run()
    }
    

    如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的,因为在实例初始化完毕之后才能引用self

    class Person {
        lazy var fn: (() -> ()) = {
            [weak self] in
            self?.run()
        }
        func run() {
            print("Person Run")
        }
        deinit {
            print("deinit")
        }
    }
    上述的闭包fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self
    

    如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题,因为闭包调用后,闭包的生命周期就结束了

    class Person {
        var age: Int = 0
        lazy var getAge: Int = {
            self.age
        }()
        deinit {
            print("deinit")
        }
    }
    

    其中,第一个Person需要加lazy关键字,是因为在初始化时可知,self需要在第二阶段才能使用,因而在定义fn时直接使用self是不允许的,因而需要在调用时懒加载初始化fn,此时,可以等到使用fn时再初始化
    以上第一个与第二个的区别:第二个在闭包后面添加了(),即直接直接执行了闭包,即本质上第二个Person中,getAge不是闭包表达式,而是一个int类型,此时getAge中可以不用显示写self.

  2. 逃逸闭包与非逃逸闭包

    1. 一般都是当做参数传递给函数
    • 非逃逸闭包: 闭包调用发生在函数结束前,闭包调用在函数作用域内
    • 逃逸闭包: 闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
    typealias Fn = () -> ()
    //fn 是非逃逸闭包
    func test1 (_ fn: Fn) {
        fn()
    }
    
    var gFn: Fn?
    // fn 是逃逸闭包
    func test2 (_ fn: @escaping Fn) {
       gFn = fn
    }
    
    // fn 是逃逸闭包
    func test3 (_ fn: @escaping Fn) {
        DispatchQueue.global().async {
            fn()
        }
    }
    
    其中,GCD的异步函数是逃逸闭包
    
    1. 举例:在GCD函数中,若直接使用self,则无论如何都会都会执行到函数结束才会销毁self,这就有可能产生例如空指针等问题,因而如果使用捕获列表,则可以进行判断,若此时self已经被释放,则不会执行后续代码
    2. 逃逸闭包不可以捕获inout参数,非逃逸闭包则可以捕获inout参数
  3. 内存访问冲突

    1. 内存访问冲突会在两个访问同时满足下列条件时发生:
      • 至少一个是写入操作
      • 它们访问的是同一块内存
      • 它们的访问时间重叠(比如在同一个函数内)
    //存在内存访问冲突
    //Thread 1: Simultaneous accesses to 0x100008020, but modification requires exclusive access
    var step = 1
    func increment (_ num: inout Int) {
        num += step
    }
    increment(&step)
    

    解决方式:

    var step = 1
    func increment (_ num: inout Int) {
        num += step
    }
    
    var copyOfStep = step
    increment(&copyOfStep)
    step = copyOfStep
    
    1. 如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
      • 你只访问实例存储属性,不是计算属性或者类属性
      • 结构体是局部变量而非全局变量
      • 结构体要么没有被闭包捕获要么只被非逃逸闭包捕获
    2. 以下代码如果是在全局区,则会报错
    var tulpe = (health: 10, energy:20)
    balance(&tulpe.health, &tulpe.energy)
    
    var holly = Player(name: "Jack", health: 10, energy:20)
    balance(&holly.health, &holly.energy)
    

22.指针

  1. Swift中也有专门的指针类型,这些都被定性为"Unsafe"(不安全的),常见的有以下4种类型:
    • UnsafePointer<Pointee>类似于const Pointee *
    • UnsafeMutablePointer<Pointee>类似于Pointee *
    • UnsafeRawPointer类似于const void
    • UnsafeMutableRawPointer类似于void
  2. 相关操作(API)
    1. pointee: 取出指针里面的值
    func test1 (_ ptr: UnsafeMutablePointer<Int>) {
        // int *
        ptr.pointee = 20
        print(ptr.pointee)
    }
    
    2.load: 取出对应字节的值
    func test2 (_ ptr: UnsafeRawPointer) {
        // const void *
        var d = ptr.load(as: Int.self)
        print(d)
        此时,d就是int类型的
    }
    
    1. storeBytes: 存储对应字节的值
    func test3 (_ ptr: UnsafeMutableRawPointer) {
        // void *
        ptr.storeBytes(of: 30, as: Int.self)
    }
    
    1. 数组的遍历操作
    var arr = NSArray(objects: 11,22,33,44)
    for (idx,element) in arr.enumerated() {
        print((idx,element))
        if idx == 3 {
            break
        }
    }
    
    1. 正常情况下,是无法将指针类型赋值给一个变量的,需要通过定义withUnsafePointer这个函数
    var age = 20
    var ptr = withUnsafePointer(to: &age) { $0 }
    若要赋值的变量今后是可以修改的,需要使用withUnsafeMutablePointer这个函数
    
    指向某个变量的指针:
    var age = 20
    var ptr = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
    
    var ptr = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer( $0 ) }
    
    withUnsafePointerwithUnsafemMutablePointer中闭包的返回类型就是该指针类型的返回类型,即上述age是什么类型,该返回就是什么类型
    var p = Person(name: "Jack")
    var ptrP = withUnsafePointer(to: &p) { $0 }
    此时,ptrP是指向Person对象p的指针
    今后,ptrP.pointee等价于p
    
    1. 指向堆空间实例的指针:
    var ptr1 = UnsafeMutableRawPointer(bitPattern: 0x000000010000ef40)
    
    此时,ptr1就是后面传的内存地址
    1. address就是p堆空间的地址值
    class Person {
        var name: String
        init(name: String) {
            self.name = name
          }
    }
    
    var p = Person(name: "Jack")
    var ptr2 = withUnsafePointer(to: &p) {UnsafeRawPointer($0)}
    var address = ptr2.load(as: UInt.self)
    
    1. ptr2获取了person堆空间的地址值
    class Person {
        var name: String
        init(name: String) {
            self.name = name
        }
    }
    
    var p = Person(name: "Jack")
    var ptr2 = withUnsafePointer(to: &p) {UnsafeRawPointer($0)}
    var address = ptr2.load(as: UInt.self)
    var ptr3 = UnsafeMutableRawPointer(bitPattern: address)
    
    也可以直接使用unsafeBitCast实现
    var ptr4 = unsafeBitCast(p, to: UnsafeRawPointer.self)
    
    1. 有关指针的存、取、创建、销毁
    //创建
    var ptr = malloc(16)
    
    //存
    ptr?.storeBytes(of: 11, as: Int.self)
    ptr?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)
    
    //取
    ptr?.load(as: Int.self)
    ptr?.load(fromByteOffset: 8, as: Int.self)
    
    //销毁
    free(ptr)
    
    1. 只有带mutable的才能使用allocate去调用内存
    var ptr2 = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
    ptr2.deallocate()
    
    1. advanced函数,直接将指针偏移字节
    prt.advanced(by: 8).storeBytes(of: 22, as Int.self)
    
    上述直接将22存储到ptr后面的8个字节
    var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
    ptr.initialize(to: 11)
    ptr.successor().initialize(to: 22)
    //successor为后继,即ptr.successor取的是ptr的后8个字节
    ptr.successor().successor().initialize(to: 33)
    
    //如果是通过initialize初始化,则一定需要deinitialize方式,不然会有内存泄漏
    ptr.deinitialize(count: 3)
    ptr.deallocate()
    其中:ptr + 1与ptr[1] 与ptr.successor()都等价
    
    1. 指针类型转换
    • 方式1:
    因为ptr是rawPointer,所以ptr+8确实是+8个字节,而泛型的的指针,假如是int,则+1即+8个字节(1个int有8个字节)
    
    var ptr2 = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
    ptr2.assumingMemoryBound(to: Int.self).pointee = 11
    (ptr2 + 8).assumingMemoryBound(to: Double.self).pointee = 22.0
    
    • 方式2:
    var ptr3 = unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self)
    
    unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据,类似于C++ 中的reinterpret_cast,相当于直接将二进制数据搬过去,只是数据类型发生了变化
上一篇下一篇

猜你喜欢

热点阅读