Swift5.0 - day7-扩展、访问控制、内存管理

本文涉及的产品
访问控制,不限时长
简介: Swift5.0 - day7-扩展、访问控制、内存管理

一、扩展(Extension)



  • 1.1、扩展介绍
  • Swift中的扩展,有点类似于OC中的分类(Category)
  • 扩展可以为枚举、结构体、类、协议添加新功能;可以添加方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等等
  • 扩展不能办到的事情
  • 不能覆盖原有的功能
  • 不能添加存储属性,不能向已有的属性添加属性观察器 ;原因是:不允许改变原有的内存结构
  • 不能添加父类 ;因为牵扯到继承也会改变内存结构的,所以不能添加父类
  • 不能添加指定初始化器,不能添加反初始化器
  • ...
  • 1.2、计算属性、下标、方法、嵌套类型


extension Double {
    var km: Double { self * 1_000.0 }
    var m: Double { self }
    var dm: Double { self / 10.0 }
    var cm: Double { self / 100.0 }
    var mm: Double { self / 1_000.0 }
}
extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
           task()
        }
    }
    mutating func square() -> Int { self = self * self
        return self
    }
    enum Kind { case negative, zero, positive }
    var kind: Kind {
        switch self {
        case 0: return .zero
        case let x where x > 0: return .positive default: return .negative
        }
    }
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex { 
             decimalBase *= 10 
        }
        return (self / decimalBase) % 10
    }
}
extension Array {
    subscript(nullable idx: Int) -> Element? {
        if (startIndex..<endIndex).contains(idx) {
            return self[idx]
        }
        return nil
    }
}
  • 1.3、协议、初始化器
  • 如果希望自定义初始化器的同时,编译器也能够生成默认初始化器(可以在扩展中编写自定义初始化器),如下面的 Point 除了系统生成的 4 个初始化器,还多了扩展中的 1 个初始化器
  • required初始化器也不能写在扩展中


class Person {
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}
extension Person : Equatable {
   static func == (left: Person, right: Person) -> Bool { 
       left.age == right.age && left.name == right.name
   }
   convenience init() {
       self.init(age: 0, name: "") 
   }
}
struct Point {
   var x: Int = 0
   var y: Int = 0 
}
extension Point {
   init(_ point: Point) {
      self.init(x: point.x, y: point.y) 
   }
}
var p1 = Point()
var p2 = Point(x: 10)
var p3 = Point(y: 20)
var p4 = Point(x: 10, y: 20)
var p5 = Point(p4)
  • 1.4、协议
  • 如果一个类已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来让它遵守这个协议,如下


protocol TestProtocol {
    func test()
}
class TestClass {
    func test() {
       print("test")
    }
}
extension TestClass: TestProtocol {}
  • 编写 一个 函数,判断一个整数是否为奇数?


extension BinaryInteger {
    func isOdd() -> Bool {
        self % 2 != 0
    }
}

提示:整数是继承于BinaryInteger,在扩展里面写比较好,这样只要是遵守 BinaryInteger 协议的都可以调用


  • 扩展可以为协议提供默认实现,也间接实现 [可选协议] 的效果


protocol TestProtocol {
    func test1()
}
extension TestProtocol {
    func test1() { 
        print("TestProtocol test1")
    }
    func test2() {
        print("TestProtocol test2") 
    }
    // 扩展类方法
    static func test3() {
        print("TestProtocol test3") 
    }
}
var cls = TestClass()
cls.test1() // TestClass test1
cls.test2() // TestClass test2
var cls2: TestProtocol = TestClass()
cls2.test1() // TestClass test1
cls2.test2() // TestProtocol test2

提示:这里主要说下 cls2.test2() 为什么打印 TestProtocol test2

  • 这是一个细节在协议中没有声明一个方法,在协议扩展里面实现了一个新的方法如上面extension TestProtocol里面的 test2(),但是在遵守协议的类里面实现了协议中没声明的方法test2(),在 var cls2: TestProtocol = TestClass(),特指出协议TestProtocol,那么在 cls2.test2()调用的时候,如下协议里面没声明 test2(),就会去调用协议扩展里面实现的test2() 方法


  • 1.5、泛型


class Stack<E> {
   var elements = [E]()
   func push(_ element: E) {
      elements.append(element) 
   }
   func pop() -> E { 
      elements.removeLast() 
   }
   func size() -> Int { 
      elements.count 
   }
}
  • 扩展中依然可以使用原类型中的泛型类型 extension Stack {


func top() -> E { elements.last! } }
  • 符合条件才扩展


extension Stack : Equatable where E : Equatable {
    static func == (left: Stack, right: Stack) -> Bool { 
        left.elements == right.elements
    } 
}


二、访问控制(Access Control)



  • 2.1、访问权限的 5 个级别在访问权限这块,Swift提供了 5 个不同的访问级别(以下是从 高 -> 低 排列,实体指被访问级别修饰的内容,模块等同于文件)
  • open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open 只能用在类、类成员上)
  • public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
  • internal:只允许在定义实体的模块中进行访问,不允许在其他模块中进行访问


提示:internal只允许在当前的项目访问,不允许成为一个库,在其他模块进行使用

  • fileprivate:只允许在定义实体的原文件中访问

提示:fileprivate 代表只能在当前的 .swift 文件中进行访问

  • private:只允许在定义实体的封闭声明中访问

提示:private:只允许在封闭的实体内访问的意思是,如下:age 只能在person 的大括号内进行访问

class person {
    private var age: Int = 0
}


提示:绝大部分实体默认都是 internal 级别


  • 2.2、访问级别的使用准侧,一个实体不可以被更低访问级别的实体定义,如下
  • 变量/常量类型 变量/常量,如下:Person的类型(fileprivate)是小于 person 变量类型(internal)的



image.png

  • 参数类型、返回值类型 函数,如下, 默认:Int、Double 都是 public


fileprivate  func test(_ num: Int) -> Double  {
      return 2.0
}


  • 父类 子类,这个比较好理解,因为我们访问子类的时候,必然会用到父类
  • 父协议 子协议
  • 原类型 typealias


image.png


原始值类型、关联值类型 枚举类型

image.png


  • 定义类型 A 时用到其他类型 类型A
  • 2.3、元组类型的访问级别是所有成员类型最低的那个,也就是下面的 data1data2 的级别要小于右边元祖中最小的级别


internal struct Dog {}
fileprivate class Person {}
// (Dog, Person)的访问级别是fileprivate 
fileprivate var data1: (Dog, Person) 
private var data2: (Dog, Person)
  • 2.4、泛型类型
    泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个


internal class Car {}
fileprivate class Dog {}
public class Person<T1, T2> {}
fileprivate var p = Person<Car, Dog>()

Person<Car, Dog>的访问级别是 fileprivate

  • 2.5、成员、嵌套类型类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
  • 一般情况下,类型为 privatefileprivate,那么成员\嵌套类型默认也是 privatefileprivate
  • 一般情况下,类型为 internalpublic,那么成员\嵌套类型默认是internal


public class PublicClass {
    public var p1 = 0 // public
    var p2 = 0 // internal
    fileprivate func f1() {} // fileprivate private
    func f2() {} // private
}
class InternalClass { // internal
    var p = 0 // internal
    fileprivate func f1() {} // fileprivate private
    func f2() {} // private
}
fileprivate class FilePrivateClass { // fileprivate
    func f1() {} // fileprivate
    private func f2() {} // private
}
private class PrivateClass { // private
    func f() {} // private
}
  • 2.6、成员的重写
  • 子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别
  • 父类的成员不能被成员作用域外定义的子类重写
  • 2.7、观察编译代码
  • 下面的代码能过不能通过,要看放的位置,如果:在一个文件内编译器不报错,不在同一个文件内会报错


private class Person {}
fileprivate class Student : Person {}
private struct Dog {
    var age: Int = 0
    func run() {}
}
fileprivate struct Person {
    var dog: Dog = Dog()
    mutating func walk() {
         dog.run()
         dog.age = 1
    }
}
  • 特别指出一下下面的代码: private struct Dog 里面的 agerun() 其实也是 private ,只是在访问域是 和  Dog一个等级,所以在 Person 里面也可以访问


class test {
     private struct Dog {
         var age: Int = 0
         func run() {}
     }
     fileprivate struct Person {
         var dog: Dog = Dog()
         mutating func walk() {
             dog.run()
             dog.age = 1
         }
     }
}

提示:如果我们可以在 var age: Int = 0前面加 private,那么 dog.age = 1 会直接报错


  • 2.8、getter、setter


class Person {
    private(set) var age = 0
    fileprivate(set) public var weight: Int {
        set {}
        get { 10 }
    }
    internal(set) public subscript(index: Int) -> Int {
        set {}
        get { index }
    }
 }
var person = Person()
person.age = 100
print(person.age)

提示:person.age = 100 会直接报错,因为我们在 private(set) var age = 0 设置的是 private(set),这样属性只能访问不能修改


  • 定义一个全局变量,只能在该文件内进行修改,其他文件内只能读,如下


fileprivate(set) public var num = 10
  • 2.9、初始化器
  • 如果一个 public 类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器;因为public类的默认初始化器是internal级别;如下如果 Person 是在一个 .dylib 里面的,那么想要访问 var person = Person(),Person 类里面的 init() {} 前面必须加  public


public  class Person {
     public init() {
     }
}
var person = Person()
  • required 初始化器 ≥ 它的默认访问级别
  • 如果结构体有 private\fileprivate 的存储实例属性,那么它的成员初始化器也是private\fileprivate;否则默认就是internal


struct Point {
      fileprivate var x = 0
      var y = 0
}
var point = Point(x: 10,y: 20)

提示:在其他文件 var point = Point(x: 10,y: 20) 会报错,因为fileprivate var x = 0 设置后,整个指定初始化器都是 fileprivate


  • 2.10、枚举类型的 case
  • 不能给enum的每个case单独设置访问级别
  • 每个case自动接收enum的访问级别;public enum定义的case也是public


public enum  Season {
    case spring
    case summer
}
  • 2.11、协议
  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别;public协议定义的要求也是public
  • 协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别


public protocol Runnable {
    func run()
}
fileprivate class Person : Runnable {
    fileprivate func run() {}
}

提示:fileprivate func run() {} 的权限要大于等于 类 Person 和  协议Runnable  中的一个


  • 2.12、扩展
  • 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别,如下:run()的权限就是 fileprivate


class Person {}
fileprivate extension Person {
     func run() {
     }
}
  • 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样 ,如下: func run() 写在 类 和 扩展里面没啥区别


class Person {
    func run() {
     }
}
extension Person {
}
  • 可以单独给扩展添加的成员设置访问级别,如下面给  func run() 设置 fileprivate


class Person {}
extension Person {
   fileprivate  func run() {
     }
}
  • 不能给用于遵守协议的扩展显式设置扩展的访问级别,如下:不能在 extension Person 前面设置访问级别


protocal Runnable {}
class Person {}
extension Person:  Runnable {
    func run() {
     }
}
  • 在同一文件中的扩展,可以写成类似多个部分的类型声明
  • 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
  • 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它


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() 
     }
}

提示:如果上面的段代码在同一个文件内,那么后面两个代码的功能类似写在第一段代码里面,所有虽然写的 private,但是可以访问


  • 2.13、讲方法赋值给let或者var
    方法也可以像函数那样,赋值给一个let或者var


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


三、内存管理


  • 3.1、内存管理
  • 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
  • Swift的ARC中有3种引用
  • 强引用(strong reference):默认情况下,引用都是强引用
  • 弱引用(weak reference):通过weak定义弱引用


提示:弱引用

  • 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil
  • ARC自动给弱引用设置nil时,不会触发属性观察器
  • 无主引用(unowned reference):通过unowned定义无主引用
  • 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的 unsafe_unretained)
  • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
  • Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated


  • 3.2、weak 和 unowned 的使用限制
    weakunowned 只能用在类实例上面,类是放在 堆空间


protocol Livable : AnyObject {}
class Person {}
weak var p0: Person?  
weak var p1: AnyObject?  //  AnyObject  代表所有类的实例
weak var p2: Livable? // 写上 : AnyObject 代表这个协议只能被类遵守
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Livable?
  • 3.3、autoreleasepool,有时候为了缓解内存压力,我们可以把创建实例放在自动释放池里面


public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
autoreleasepool {
     let p = MJPerson(age: 20, name: "Jack")
     p.run()
}


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


  • 3.5、闭包的循环引用
  • 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行 retain 操作)
  • 下面的代码会造成循环引用,导致 Person 对象无法释放(看不到 Person 的 deinit 被调用)


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


提示:

  • 对象 p 强引用闭包表达式,而闭包表达式又强引用对象 p,从而造成相互强引用(循环引用)
  • 解决循环引用的办法- 捕获列表
  • 办法一:[weak 对象],这样就代表闭包表达式对 对象p 进行弱引用


p.fn = { 
     [weak p] in 
     p?.run()
}
  • 办法二:[unowned 对象],这样就代表闭包表达式对 对象p 进行弱引用


p.fn = { 
     [unowned p] in 
     p.run()
}
  • 如果闭包有参数,比如上面的 var fn: ((Int) -> ())?,那么解决循环引用如下,age 是参数的名字,捕获列表  [unowned p] 要写在参数列表的前面,否则会报错


p.fn = { 
     [unowned p](age) in 
     p.run()
}
  • 如果想在定义闭包属性的同时引用 self,这个闭包必须是 lazy的(因为在实例初始化完毕之后才能引用 self),下面的闭包 fn 内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self


class Person {
    lazy var fn: (() -> ()) = {
         [weak self] in
         self?.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
}
func test() {
    let person = Person()
    person.fn()
}
test()
  • 如果 lazy 属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)


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

提示 : lazy var getAge: Int = { self.age }() 虽然闭包表达式对 self 进行了强引用,但是这个闭包表达式是直接 () 执行的,对self的强引用也就结束了,等同于 lazy var getAge: Int = self.age,所以不会产生循环引用


  • 3.6、@escaping
  • 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
  • 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内,函数结束之前闭包就会调用结束
  • 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过 @escaping 声明,也就说函数结束后闭包也可能没有进行调用


import Dispatch
typealias Fn = () -> ()
// fn是非逃逸闭包
func test1(_ fn: Fn) { fn() }
// fn是逃逸闭包
var gFn: Fn?
func test2(_ fn: @escaping Fn) { gFn = fn }
// fn是逃逸闭包
func test3(_ fn: @escaping Fn) {
    DispatchQueue.global().async {
       fn()
    }
}
  • DispatchQueue.global().async也是一个逃逸闭包


class Person {
    var fn: Fn
    // fn是逃逸闭包
    init(fn: @escaping Fn) {
        self.fn = fn
    }
    func run() {
        // DispatchQueue.global().async也是一个逃逸闭包
        // 它用到了实例成员(属性、方法),编译器会强制要求明确写出self 
        DispatchQueue.global().async {
            self.fn()
        }
}

提示:DispatchQueue.global().async { self.fn() } 里面使用self不会造成循环引用,因为是闭包表达式强引用 self,而 self 没有对闭包表达式进行强引用,单向强引用不会造成循环引用


  • 3.7、逃逸闭包的注意点:逃逸闭包不可以捕获 inout 参数


typealias Fn = () -> ()
func other1(_ fn: Fn) { fn() }
func other2(_ fn: @escaping Fn) { fn() }
func test(value: inout Int) -> Fn {
     other1 { value += 1 } 
     // error: 逃逸闭包不能捕获inout参数
     other2 { value += 1 }
     func plus() { value += 1 }
     // error: 逃逸闭包不能捕获inout参数 
     return plus
}


相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
目录
相关文章
|
存储 Swift iOS开发
31 Swift 继续聊聊协议扩展
Swift 继续聊聊协议扩展
107 0
|
4月前
|
XML Ubuntu Linux
部署08---扩展-Win10配置WSL(Ubuntu)环境,WSL系统是什么意思,是Windows系统上的一个子系统, xml的一大特点是直链系统,直接链接你的CPU,硬盘和内存,如何用 WSL部署
部署08---扩展-Win10配置WSL(Ubuntu)环境,WSL系统是什么意思,是Windows系统上的一个子系统, xml的一大特点是直链系统,直接链接你的CPU,硬盘和内存,如何用 WSL部署
|
6天前
|
区块链 Swift 数据安全/隐私保护
Swift 访问控制
Swift 访问控制
15 1
|
7天前
|
Swift 索引 容器
Swift 泛型-扩展泛型类型
Swift 泛型-扩展泛型类型
16 2
|
8天前
|
Swift
Swift 扩展
Swift 扩展
16 1
|
3月前
|
存储 编译器 Swift
Swift笔记:Swift中的扩展语法
Swift笔记:Swift中的扩展语法
99 1
|
3月前
|
网络安全 数据安全/隐私保护 网络架构
|
5月前
|
存储 Swift 索引
Swift开发——索引器扩展
扩展用于向已存在的类型(例如,类、结构体、枚举和协议等)中添加新的功能,扩展甚至可以向系统类型(包括无法查阅代码的类型)中添加新的功能,但是扩展不能覆盖原类型中已有的方法,扩展也不能向类中添加新的存储属性。
56 6
Swift开发——索引器扩展
|
4月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
62 1
|
4月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
55 0