Swift5.x入门17--访问控制,内存管理,指针

访问控制

  • 在访问权限控制这块,Swift提供了5个不同的访问级别,以下是从高到低的叙述,其中实体是指被访问级别修饰的内容

  • open:允许在定义实体的模块,其他模块中访问,允许其他模块进行继承,重写,open只能用在类class,类成员上,不允许用在枚举与结构体上,这里的模块是指工程编译生成的Mach-O可执行文件

  • public:允许在定义实体的模块,其他模块中访问,不允许其他模块进行继承,重写,这里的模块同上;

    image.png

  • internal:只允许在定义实体的模块中访问,不允许其他模块访问,这里的模块是指子模块,如下图所示:

    image.png

  • fileprivate:只允许在定义实体的源文件中访问,即在指定的.swift文件中访问,在其他.swift文件中不能访问;

  • private:只允许在定义实体的封闭声明中访问,例如在一个.swift文件中定义A,B两个Class类,那么A类中定义的实体只能在A类中访问,不能在B类中访问;

  • 绝大部分实体默认都是internal级别;

访问级别的使用准则

  • 一个实体不可以被更低访问级别的实体定义;
    • 变量类型的访问级别 大于等于 变量的访问级别;
    • 参数类型/返回值类型的访问级别 大于等于 函数的访问级别;
    • 父类的访问级别 大于等于子类的访问级别;
    • 父协议的访问级别 大于等于 子协议的访问级别;
    • 原类型的访问级别 大于等于 typealias的访问级别;
    • 原始值,关联值类型的访问级别 大于等于 枚举的访问级别;
    • 定义类型A所用到的其他类型的访问级别 大于等于 A的访问级别;

元组类型

  • 元组类型的访问级别是其所有成员类型最低的那个;
internal struct Dog { }
fileprivate class Person { }

fileprivate var data1: (Dog,Person)
private var data2: (Dog,Person)
var data3: (Dog,Person) //报错
  • (Dog,Person)是元组类型,其中Dog的访问级别> Person的访问级别,所以(Dog,Person)元组的访问级别为fileprivate
  • 现在用元组类型来定义变量,那么元组类型的访问级别必须
    大于等于变量data1,data2,data3的访问级别,data3默认的访问级别为internal,所以报错;

泛型

  • 泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个;
internal class Car { }
fileprivate class Dog {}
public class Person { }

fileprivate var p = Person()
  • < >中的内容表示泛型,其访问级别是Person,Car,Dog这三个访问级别中最低的那个,所以泛型的访问级别为fileprivate;

成员,嵌套类型

  • 类型的访问级别会影响成员(方法,属性,下标,初始化器),嵌套类型的默认访问级别;
  • 一般情况下,类型为privatefileprivate,那么成员,嵌套类型默认也为privatefileprivate
  • 一般情况下,类型为internalpublic,那么成员,嵌套类型默认是internal
fileprivate class Person {
    var age = 0
    func run() { }
    
    enum Season {
        case spring,summer,autum,winter
    }
}
  • 类型为fileprivate,那么其所有成员,也为fileprivate
public class Person {
    var age = 0
    func run() { }
    
    enum Season {
        case spring,summer,autum,winter
    }
}
  • 类型为public,其所有成员为internal

在main.swift文件中直接定义 代码如下:

import Foundation
private class Person { }
fileprivate class Student : Person { }
  • 根据上面的规则,父类的访问级别要大于等于子类的访问级别,按理说会报错,但编译成功,原因在于 PersonStudent,是在main.swift源文件的直接定义的,在整个main.swift源文件中都可以访问,如果做如下改动:
import Foundation
class TestClass {
    private class Person { }
    fileprivate class Student : Person { }
}
  • 就会报错,因为Person的作用域仅局限于TestClass的内部,Student是在整个main.swift源文件中都可以访问,若在main.swift源文件中直接实例Student,就不能访问其父类Person,所以会报错;
class Test {
    private struct Dog {
        var age: Int = 0
        func run(){}
    }
    
    private struct Person {
        var dog: Dog = Dog()
        mutating func walk() {
            dog.run()
            dog.age = 1
        }
    }
}
  • 编译通过,根据上面的规则,private struct Dog ,那么其成员age与run的访问级别也为private,然而dog.run,依然能在Person内部使用,原因在于成员age与run的访问级别的private是与类Dog的private的作用域是一致的,若改成下面的:
class Test {
    private struct Dog {
        private var age: Int = 0
        private func run(){}
    }
    
    private struct Person {
        var dog: Dog = Dog()
        mutating func walk() {
            dog.run()
            dog.age = 1
        }
    }
}
  • 编译报错,private func run(){},不能在Person中使用;

成员的重写

  • 子类重写成员的访问级别必须 >= 子类的访问级别 或者 >= 父类被重写成员的访问级别;
public class Person {
    fileprivate func run() {
        
    }
}

public class Student : Person {
    fileprivate override func run() {
        
    }
}

getter,setter

  • getter,setter默认自动接收它们所属环境的访问级别;
  • 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限;
class Person {
    private(set) var age: Int = 0
    fileprivate(set) public var weight: Int {
        set {
            
        }
        get {
            10
        }
    }
    internal(set) public subscript(index: Int) -> Int {
        set {
            
        }
        get {
            index
        }
    }
}

var p: Person = Person()
p.age = 10 //不能写,报错
print(p.age)

初始化器

  • 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显示提供public的无参初始化器, 因为public类的默认初始化器是internal级别;
  • required初始化器 >= 它的默认访问级别
class Person {
    fileprivate required init() {}
     
}
  • 会报错,因为class Person的默认访问级别为internal,根据上面的规则:required初始化器 >= 它的默认访问级别,而现在required初始化器的访问级别为fileprivate < internal,所以报错;

  • 如果结构体有private/fileprivate的存储属性,那么它的成员初始化器也是private/fileprivate,默认为internal

struct Point {
    fileprivate var x = 0
    var y = 0
}

var p: Point = Point(x: 10, y: 20)

枚举类型的case

  • 不能给enum的每个case设置访问级别;
  • 每个case自动接收enum的访问级别;
public enum Season {
    case spring,summer,autum,winter
}
  • enum是public
  • 所有的case也都是public

协议

  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别,跟枚举类似;
  • 协议实现的访问级别 >= 类型的访问级别 或者 >= 协议的访问级别
//协议的访问级别public
public protocol Runnable {
    func run()
}
//类型的访问级别fileprivate
fileprivate class Person : Runnable{
    //协议实现的访问级别internal
    internal func run() {
        
    }
}

扩展

  • 如果有显示设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别;
  • 如果没有显示设置扩展的访问级别,扩展添加的成员的默认访问级别与直接在类型中定义的成员一样;
  • 可单独给扩展的成员设置访问级别;
  • 不能给用于遵守协议的扩展显示设置扩展的访问级别;
protocol Runnable {
    func run()
}

class Person{
    
}

fileprivate extension Person : Runnable { //报错
    
}
  • 在同一文件中的扩展,可以写成类似多个部分的类型声明;
  • 在原本的声明中定义一个私有成员,可以在同一文件的扩展中访问它;
  • 在扩展中定义一个私有成员,可以在同一文件的其他扩展中,原本声明中访问它;
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()
    }
}

将方法赋值给let或者var

struct Person {
    var age: Int
    func run(_ v: Int) {
        print("run",age,v)
    }
    
    static func run(_ v: Int) {
        print("static run",v)
    }
}

var fn1 = Person.run
fn1(20)

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

//(Person) -> (Int) -> ()
var fn = Person.run
//fn1是带有Person实例的run方法
//(Int) -> ()
var fn1 = fn(Person(age: 10))
//最后传入参数20,调用run方法
fn1(20)

内存管理

  • 与OC一样,Swift也是采取基于引用计数的ARC内存管理方案;
  • Swift的ARC中有3种引用:
    • 强引用:默认情况下,都是强引用;
    • 弱引用:通过weak来定义弱引用,必须是可选类型的var,因为实例销毁后,ARC会自动将若引用置为nil;ARC自动给弱引用设置为nil时,是不会触发属性观察器的;
  • 无主引用(unowned reference):通过unowned定义无主引用,其不会产生强引用,实例对象销毁之后引用依然指向实例的内存地址,类似于OC中的unsafe_unretained,试图在实例对象销毁后,访问无主引用,会导致野指针错误;
  • weakunowned只能应用于类的实例上面;
protocol Liveable : AnyObject {
    
}

class Person {
    
}
 
weak var p0: Person?
weak var p1: AnyObject?
weak var p2: Liveable?

unowned var p3: Person?
unowned var p4: AnyObject?
unowned var p5: Liveable?

AutoRelease

循环引用

  • weakunowned都能解决循环引用的问题,但unowned要比weak少一些性能消耗;
  • 在实例对象的生命周期中会变成nil的,使用weak
  • 实例对象初始化赋值之后,再也不会变成nil的,使用unowned

闭包的循环引用

  • 闭包表达式默认会对用到的外层对象产生额外的强引用(即对外层对象进行了retain操作)
class Person {
    var fn: (() -> ())?
    func run() {
        print("Person run")
    }
    deinit {
        print("Person deinit")
    }
}

func test() {
    let p: Person = Person()
    p.fn = {
        p.run()
    }
}

test()
  • 闭包fn,会对p进行强引用,导致p无法被释放,造成内存泄漏;
  • 解决方案:在闭包表达式的捕获列表声明weakunowned引用,解决循环引用问题;
class Person {
    var fn: (() -> ())?
    func run() {
        print("Person run")
    }
    deinit {
        print("Person deinit")
    }
}

func test() {
    let p: Person = Person()
    p.fn = {
        [weak p] in
        p?.run()
    }
}

test()
  • 或者
class Person {
    var fn: (() -> ())?
    func run() {
        print("Person run")
    }
    deinit {
        print("Person deinit")
    }
}

func test() {
    let p: Person = Person()
    p.fn = {
        [unowned p] in
        p.run()
    }
}

test()
  • 或者
class Person {
    var fn: (() -> ())?
    func run() {
        print("Person run")
    }
    deinit {
        print("Person deinit")
    }
}

func test() {
    let p: Person = Person()
    p.fn = {
        [weak wp = p] in
        wp?.run()
    }
}

test()
  • 先看一个实例代码:
class Person {
    lazy var fn: (() -> ()) = {
        [weak weakSelf = self] in
        weakSelf?.run()
    }
    func run() {
        print("Person run")
    }
    deinit {
        print("Person deinit")
    }
}

func test() {
    var p: Person = Person()
    p.fn()
}

test()
  • 上述闭包内部用到实例的成员,必须强制写上self,否则会报错,因为这样可以提醒开发者,可能会造成循环引用;
  • lazy var fn: (() -> ()) = { },只有使用到fn,才会去初始化加载fn,也就是说调用p.fn(),才会初始化fn,可能会造成循环引用;
  • 再看一段代码:
class Person {
    var age: Int = 0
    lazy var getAge: Int = {
        self.age
    }()
    deinit {
        print("Person deinit")
    }
}

func test() {
    var p: Person = Person()
    print(p.getAge)
}

test()
  • 如果将lazy删除,self.age会报错,这是因为self实例还没初始化,就调用getAge,若加上lazy,只有当实例初始化后才去调用getAge,就不会报错了;
  • 如果lazy属性是闭包调用的结果,那么就不用考虑循环引用问题,因为闭包调用后,闭包的生命周期就结束了;

@escaping

  • 非逃逸闭包,逃逸闭包,一般都是当作参数传递给函数;
  • 非逃逸闭包:闭包调用发生在函数结束之前,闭包作用域在函数之内;
func test(_ fn: () -> ()) {
    fn()
}

test {
    print("1111")
}
  • fn是非逃逸闭包,其调用在test函数内部;
  • 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping进行声明;
import Dispatch

typealias Fn = () -> ()

var gFn: Fn?

func test1(_ fn: @escaping Fn) {
    gFn = fn
}

func test2(_ fn: @escaping Fn) {
    DispatchQueue.global().async {
        fn()
    }
}
  • fn是逃逸闭包,其调用不在test1,test2的函数内部;
  • 逃逸闭包是不能捕获inout类型的参数的,因为逃逸闭包的调用是不确定的,而inout类型的参数是传入变量的内存地址给逃逸闭包,有可能变量已经销毁了,逃逸闭包再去修改变量所指向的内存;

局部作用域

  • 在Swift中用do { }定义局部作用域;
class Dog {
    var age: Int = 10
    func run() {
        
    }
}

do {
    let dog1: Dog = Dog()
    dog1.age = 20
}

do {
    let dog2: Dog = Dog()
    dog2.age = 20
}
  • dog1在第一个do{}作用域内,dog2在第二个do{}作用域内,超出作用域,就会被释放回收;

内存访问冲突

  • 既然是内存访问冲突,说明至少有两个访问,访问同一资源;
  • 内存访问冲突的发生条件:
    • 两个访问,至少有一个写入操作;
    • 两个访问,访问的是同一块内存;
    • 两个访问的访问时间重叠(比如在同一个函数内)
  • 看下面一段代码:
var step = 1

func increment(_ num: inout Int) {
    num += step
}

increment(&step)
  • 报错:Simultaneous accesses to 0x100008020, but modification requires exclusive access
  • num += step即读取,写入同一块内存,且访问时间重叠,造成内存访问冲突,解决方案如下:
var step = 1

func increment(_ num: inout Int) {
    num += step
}

var copyStep = step
increment(©Step)
step = copyStep
  • 再看一段代码:
func balance(_ x: inout Int,_ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

var num1 = 42
var num2 = 30

balance(&num1, &num2) //OK
balance(&num1, &num1) //Error
  • balance(&num1, &num1)会报错,因为传参都是num1的内存地址,当执行y = sum - x就会出现内存访问冲突;

  • 那么若传入一个结构体的两个不同成员,给balance函数,肯定会报错的,因为结构体成员都是存储在结构体中的,是同一块内存,所以会造成内存访问冲突;

  • 若满足下面的条件,就说明重叠访问结构体的成员是安全的:

  • 只访问实例存储属性,不是计算属性或类属性;

  • 结构体是局部变量不是全局变量;

  • 结构体没有被闭包捕获 或者 只被非逃逸闭包捕获;

指针

  • 在Swift中有专门的指针类型,这些都被定性为不安全的,常见的有以下四种类型:
  • UnsafePointer 类似于const Pointee *
  • UnsafeMutablePointer 类似于Pointee *
  • UnsafeRawPointer 类似于const void *
  • UnsafeMutableRawPointer 类似于 void *
  • 其中表示泛型,Mutable表示可修改;
var age = 10

func test1(_ ptr: UnsafeMutablePointer)  {
    //修改地址中的值
    ptr.pointee = 20
    print("test1",ptr.pointee)
}

func test2(_ ptr: UnsafePointer) {
    //访问地址中的值
    print("test2",ptr.pointee)
}

test1(&age)
test2(&age)
print(age) //20
var age = 10

func test3(_ ptr: UnsafeRawPointer) {
    print("test3",ptr.load(as: Int.self))
}

func test4(_ ptr: UnsafeMutableRawPointer) {
    ptr.storeBytes(of: 30, as: Int.self)
}

test3(&age)//10
test4(&age)
print(age)//30
  • 由于UnsafeRawPointer是任意类型的指针,所以从它内存中取数据时,必须指定数据类型,它才知道取多少字节的数据,所以必须传入数据类型参数;

指针的应用实例

var arr = NSArray(objects: 11,22,33,44)
print(arr)

//UnsafeMutablePointer stop
arr.enumerateObjects { (element, idx, stop) in
    print(idx,element)
    //idx = 2时,停止遍历
    if idx == 2 {
        stop.pointee = true
    }
}
  • stop是UnsafeMutablePointer 指针类型;
获取指向某个变量的指针
//age的地址值为:0x100008188
var age = 10

//UnsafePointer 类型
//ptr的值为:0x100008188
var ptr1 = withUnsafePointer(to: &age) { $0 }
print(ptr1.pointee)

var ptr3 = withUnsafePointer(to: &age) { (p) -> UnsafePointer in
    return p
}


//UnsafeMutableRawBufferPointer
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
ptr2.pointee = 20

print(age)

var ptr4 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
ptr4.storeBytes(of: 40, as: Int.self)

var ptr5 = withUnsafePointer(to: &age) { UnsafeRawPointer($0)}
print(ptr5.load(as: Int.self)) //40
  • 获取变量a的引用指针;
  • 其中ptr1余ptr2是等价写法;
获取指向堆空间的指针
class Person {
    
}

//person是堆空间的地址值 即实例对象的地址值
var person: Person = Person()
//personPtr是全局区的地址值,也就是引用变量的地址值
var personPtr = withUnsafePointer(to: &person) { $0 }
print(personPtr)
print("1111")
Snip20210805_126.png
class Person {
    
}

//person是指针变量 其值是堆空间的地址值
var person: Person = Person()
//personPtr是全局区的地址值,也就是引用指针变量person的地址值
var personPtr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
print(personPtr)
//address是person对象堆空间的地址值
var address = personPtr.load(as: UInt.self)
//ptr2是指向address堆空间的指针变量
var ptr2 = UnsafeMutableRawPointer(bitPattern: address)
print(ptr2)
print("1111")
  • var ptr2 = UnsafeMutableRawPointer(bitPattern: address),根据堆空间获取一个指向堆空间的指针变量;
创建指针
//申请堆空间
var ptr = malloc(16)
//存 前8个字节
ptr?.storeBytes(of: 10, as: Int.self)
//存 后8个字节
ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self)
//取 前8个字节
print((ptr?.load(as: Int.self))!) //10
//取 后8个字节
print((ptr?.load(fromByteOffset: 8, as: Int.self))!) //20
//销毁堆内存空间
free(ptr)

//另一种写法 -----------------------------------------------------

//申请堆空间
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
//存 前8个字节
ptr.storeBytes(of: 10, as: Int.self)
//存 后8个字节
ptr.advanced(by: 8).storeBytes(of: 20, as: Int.self)
//取 前8个字节
print(ptr.load(as: Int.self)) //10
//取 后8个字节
print(ptr.advanced(by: 8).load(as: Int.self)) //20
//销毁堆内存空间
ptr.deallocate()

//另一种写法 -----------------------------------------------------

//申请堆空间 8 * 2 = 16个字节
var ptr = UnsafeMutablePointer.allocate(capacity: 2)
//存 前8个字节
ptr.initialize(to: 10)
//存 后8个字节
ptr.successor().initialize(to: 20)
//取 前8个字节
print(ptr.pointee) //10
//取 后8个字节
print((ptr+1).pointee) //20
print(ptr[1]) //20
print(ptr.successor().pointee) //20
//销毁堆内存空间
ptr.deinitialize(count: 2)
ptr.deallocate()
class Person {
    var age: Int
    var name: String
    init(age: Int,name: String) {
        self.age = age
        self.name = name
    }
    deinit {
        print("Person deinit")
    }
}

var ptr = UnsafeMutablePointer.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "li"))
(ptr+1).initialize(to: Person(age: 20, name: "liyan"))
(ptr+2).initialize(to: Person(age: 30, name: "liyanyan"))

ptr.deinitialize(count: 3)
ptr.deallocate()

指针之间的转换

//任意类型指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
//转成int类型指针
var ptr2 = ptr.assumingMemoryBound(to: Int.self)

ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr+8).assumingMemoryBound(to: Double.self).pointee = 22.0
//ptr.deallocate()
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr+8).assumingMemoryBound(to: Double.self).pointee = 22.0
//var ptr2 = unsafeBitCast(ptr, to: UnsafeMutablePointer.self)

print(unsafeBitCast(ptr, to:  UnsafeMutablePointer.self).pointee)//11
print(unsafeBitCast(ptr, to: UnsafeMutablePointer.self).pointee)//22.0
ptr.deallocate()
  • unsafeBitCast忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据;
class Person {
    
}

var person = Person()

//personObjectAddress存放的就是person的堆空间地址值
var personObjectAddress = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(personObjectAddress)

你可能感兴趣的:(Swift5.x入门17--访问控制,内存管理,指针)