访问控制、内存管理

访问控制(Access Control)

  • 访问控制权限,5个不同的访问级别(以下为由高到低排列,实体指被访问级别修饰的内容)
    • open 允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
    • public 允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
    • internal 只允许在定义实体的模块中访问,不允许在其他模块中访问
    • fileprivate 只允许在定义实体的源文件中访问
    • private 只允许在定义实体的封闭声明中访问
  • 绝大多数实体默认都是 internal 级别

访问级别的使用准则

  • 一个实体不可以被更低访问级别的实体定义,比如:
    • 变量\常量类型 >= 变量\常量
    • 参数类型、返回值类型 >= 函数
    • 父类 >= 子类
    • 父协议 >= 子协议
    • 原类型 >= typealias
    • 原始值类型、关联值类型 >= 枚举类型
    • 定义类型A时用到的其他类型 >= 类型A
    • .....

元组类型

元组类型的访问级别是所有成员类型最低的那个

internal struct Dog {}
fileprivate class Person {}
// (Dog, Person)的访问级别是 fileprivate
fileprivate var data1: (Dog, Person)
private var data2: (Dog,Person)

泛型类型

泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个

成员、嵌套类型

  • 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认范文级别
    • 一般情况下,类型为 private 或 fileprivate, 那么成员\嵌套类型默认也是 private 或 fileprivate
    • 一般情况下,类型为 internal 或 public,那么成员\嵌套类型默认是 internal
  • 成员的重写:
    • 子类重写成员的访问级别必须 >=子类的访问级别,或者>=父类被重写成员的访问级别
    • 父类的成员不能被成员作用域外定义的子类重写
public class Person {
   private var age: Int = 0
}
public class Student : Person {
// 报错,不能被重写
   override var age: Int {
       set { }
       get { 10 }
   }
}
//1 下面代码编译会报错吗 

private class Person {}
fileprivate class Student: Person {}
// 如果是在全局作用区,不会报错因为 private 就是在当前文件作用域 ,和fileprivate作用域相同,  如果在别的内部那就会报错

直接在全局作用域下定义的 private 等价于 fileprivate

getter setter

  • getter setter 默认自动接收它们所属环境的访问级别
  • 可以给 setter 单独设置一个比 getter 更低的访问级别,用以限制写的权限
class Person {
    private(set) var age = 10
}
var p = Person()
p.age
p.age = 10 // 不可以被赋值 setter

初始化器

  • 如果一个public类想在另一模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
    • 因为public类的默认初始化器时 internal级别
  • required初始化器 >= 它的默认访问级别
  • 如果结构体有 private\fileprivate 的存储实例属性,那么它的成员初始化器也是 private\fileprivate
struct Point {
    private var x = 0
    var y = 0
}
var p = Point(x:0) // 报错 不能访问

枚举类型的case

  • 不能给 enum 的每个 case 单独设置访问级别
  • 每个 case 自动接收 enum 的访问级别
    • public enum 定义的 case 也是 public

协议

  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
    • public协议定义的要求也是 public
  • 协议实现的访问级别必须 >= 类型的访问级别,或者 >=协议的访问级别

扩展

  • 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
  • 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
  • 可以单独给扩展添加的成员设置访问级别
  • 不能给用于遵守协议的扩展显式设置扩展的访问级别
  • 在同一文件中的扩展,可以写成类似多个部分的类型声明
    • 在原本的声明中声明一个私有属性,可以在同一文件中的扩展中访问它
    • 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
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()
    }
}

将方法赋值给var、let

struct Person {
    var age: Int
    func run(_ v: Int) { print("func run",age, v)}
    static func run(_ v: Int) { print("static func run" , v)}
}
// (Person) -> (Int) ->()
var fn1: (Person) -> (Int) ->() = Person.run
//  (Int) ->()
var fn2 = fn1(Person(age: 10))
fn2(20) // func run 10 20
var fn3 = Person.run
fn3(30) // static func run 30

内存管理

  • 跟 OC 一样,Swift 也是采用基于引用计数的 ARC 内存管理方案(针对堆空间)
  • Swift 中的 ARC 有3种引用
    • 强引用:默认情况下,引用都是强引用
    • 弱引用:通过 weak 定义弱引用
      • 必须是可选类型的 var,因为实例销毁后,ARC 会自动将弱引用设置为 nil
      • ARC 自动给弱引用设置为 nil 时,不会触发属性观察器
    • 无主引用:通过 unowned 定义无主引用
      • 不会产生强引用,实例销毁后仍然存储着实例的内存地址(和 oc 的 unsafe_unretained 类似)
      • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

weak、unowned 的使用限制

  • weak、unowned 只能用在类实例上面

Autoreleasepool

public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result

循环引用(Reference Cycle)

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

闭包的循环引用

  • 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
  • 可以在闭包表达式的捕获列表声明 weak 或 unowned 引用,解决循环引用问题
class Person {
    var fn: (() -> ())?
    func run() { print("run")}
    deinit {
        print("Person deinit")
    }
}
func test() {
    let p = Person()
    
    // 下面的闭包导致p被循环引用,无法释放
//    p.fn = {
//        p.run()
//    }
    p.fn = {
        [weak p] in
        p?.run()
    }
// 此时可以打印出 Person deinit p被释放了
}
test()

  • 如果想在定义闭包属性的同时引用 self ,这个闭包必须是 lazy 的(因为在实例初始化完毕之后才能引用 self )
class Person {
   var age: Int = 0
   // fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self
   lazy var fn: (() -> ()) = {
       [weak self] in
       self?.run()
   }
   // 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
   lazy var getAge: Int = {
       self.age
   }()
   func run() { print("run")}
   deinit {
       print("Person deinit")
   }
}

@escaping

  • 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
    • 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
    • 逃逸闭包: 闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过 @escaping 声明
import Dispatch
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 也是一个逃逸闭包
    DispatchQueue.global().async {
        fn()
    }
}
  • 逃逸闭包不能捕获 inout 参数
typealias Fn = () -> ()
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }

func test2( _ fn: @escaping Fn) { fn() }

func test3(value: inout Int)  {
    test1 {
        value += 1
    }
    // 逃逸闭包不能捕获
    // 逃逸闭包不能确定调用时机,捕获inout参数会捕获其地址值,所有会出问题,不允许
//    test2 {
//        value + 1
//    }
}

内存访问冲突

  • 内存访问冲突会在两个访问满足以下条件时发生:
    • 至少一个是写入操作
    • 它们访问的是同一块内存
    • 它们的访问时间重叠(比如在同一个函数内)
// 存在内存访问冲突
// 报错 打印 Simultaneous accesses to 0x100008020, but modification requires exclusive access.
var step = 1
func increment(_ num: inout Int) { num += step}
//increment(&step)
// 解决内存冲突
var copyOfStep = step
increment(&copyOfStep)
step = copyOfStep
func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}
var tulpe = (health: 10, energy: 20)
// 下面还是会访问内存冲突,因为health,energy是在一块内存里的,是整体
balance(&tulpe.health, &tulpe.energy)
  • 如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
    • 你只访问实例存储属性,不是计算属性或者雷属性
    • 结构体是局部变量而非全局变量
    • 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获
func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}
// 放到函数里就不会报错了
func test() {
    var tulpe = (health: 10, energy: 20)
    balance(&tulpe.health, &tulpe.energy)
}
test()

指针

  • Swift 中也有专门的指针类型,这些都被定性为 Unsafe (不安全的) 常见的有以下四种类型
    • UnsafePointer<Pointee>类似于const Pointee *
    • UnsafeMutablePointer<Pointee>类似于Pointee *
    • UnsafeRawPointer类似于const void *
    • UnsafeMutableRawPointer类似于`void *
var age = 10
//func test1(_ ptr: UnsafeMutablePointer<Int>) {
//    ptr.pointee = 20
//    print("test1",ptr.pointee)
//}
//func test2(_ ptr: UnsafePointer<Int>) {
//    print("test2",ptr.pointee)
//}
func test3(_ ptr: UnsafeRawPointer) {
  
    print("test3",ptr.load(as: Int.self))
}
func test4(_ ptr: UnsafeMutableRawPointer) {
  ptr.storeBytes(of: 30, as: Int.self)
}

//test2(&age) // test2 10
//test1(&age) // test1 20
//print(age) // 20
test3(&age) // test3 10
test4(&age)  
print(age) // 30
----------------------------------------------------------------
// 指针的应用示例 UnsafeMutablePointer<ObjCBool>
var arr = NSArray(objects: 11, 22, 33, 44)
// oc -> BOOL *
// swift -> UnsafeMutablePointer<ObjCBool>
arr.enumerateObjects { (element, idx, stop) in
    print(idx, element)
    if idx == 2 {
        stop.pointee = true
    }
    print(idx, element)
}
// 更好的写法
for (idx, element) in arr.enumerated() {
    print(idx, element)
    if idx == 2 {
        break
    }
}

获得指向某个变量的指针

var age = 10
var ptr1 = withUnsafePointer(to: &age) { $0 }
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
ptr2.pointee = 30
print(age) // 30
//@inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result {
//    body(to)
//}
func wihtUnsafePointer<Result, T>(to: UnsafePointer<T>, body: (UnsafePointer<T>) -> Result) ->Result{
    return body(to)
}
var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
ptr3.storeBytes(of: 33, as: Int.self)
print(ptr4.load(as: Int.self)) // 33
print(age) // 33

获得指向堆空间实例的指针

class Person {
    var age: Int
    init(age: Int) {
        self.age = age
    }
}
var person = Person(age: 21)
// ptr 存储的是 person 变量的地址值
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
print(ptr) // 0x00000001000031e0
var address = ptr.load(as: UInt.self) // 堆空间person对象地址值
var ptr2 = UnsafeMutableRawPointer(bitPattern: address)
print(ptr2) // Optional(0x000000010056b350)

创建指针

UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
deallocate()
malloc(16)
free(ptr)
var ptr = malloc(16)
ptr?.storeBytes(of: 11, as: Int.self)
ptr?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)
print(ptr?.load(as: Int.self))
print(ptr?.load(fromByteOffset: 8, as: Int.self))
free(ptr)
class Person {
    var age: Int = 0
    var name: String = ""
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    deinit {
        print("deinit")
    }
}

var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 1)
defer {
    ptr.deinitialize(count: 1)
    ptr.deallocate()
}
ptr.initialize(to: Person(age: 10, name: "json"))
print(ptr[0].age)

指针之间的转换

  • unsafeBitCast 是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
    • 也就是说你内存中原来是什么,转换之后的还是什么
    • 如果用 unsafeBitCast 强转,强转后的字节数不同,那么会报错
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 20.0

print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee)
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee)
var age = -1
var age1 = unsafeBitCast(age, to: UInt.self)
print(age, age1)
-----------------------执行结果-----------------------
(lldb) x/wg 0x7ffeefbff438
0x7ffeefbff438: 0xffffffffffffffff
(lldb) x/wg 0x7ffeefbff430
0x7ffeefbff430: 0xffffffffffffffff
-1 18446744073709551615

posted @ 2021-04-25 16:57  YALMiOS  阅读(91)  评论(0编辑  收藏  举报