Swift - 标红的修饰词

标红修饰词

  • open, public, internal, fileprivate, private, forzen 访问控制
  • import
  • typealias, associatedType 重命名
  • discardableResult 可丢弃结果
  • override, convenience, required, deinit, final class继承相关
  • dynamicMemberLookup, dynamicCallable 动态成员查找,动态传参
  • propertyWrapper 属性构造器
  • resultBuilder 结果构造器
  • throws, throw, rethrows 异常
  • async, await, actor
  • autoclosure 自动闭包
  • defer 执行代码块
  • operator, prefix, infix, postfix, precedencegroup, associativity, higherThan, lowerThan, assignment 自定义运算符
  • fallthrough switch
  • available 版本控制
  • #file, #filePath, #fileID, #line, #column, #function, #dsohandle, #sourceLocation 打印相关
  • .Type, .self 元类型相关
  • nonmutating
  • #keyPath 字符串表达式
  • @objc, @nonobjc, @objcMembers OC混编
  • @requires_stored_property_inits

访问控制 Access Control

open
public
internal
fileprivate
private
  • open/public 可以在任何模块中访问使用,即可以使用 import 在其他模块中访问,其中 open 只能修饰 class 类型,public 既可以修饰 class 类型,也可以修饰 struct/enum 类型
  • internal 默认访问权限,可以在当前模块中访问,不能在其他模块中使用
  • fileprivate 可以在当前文件中被访问,在其他文件中不能被访问
  • private 可以在当前 class 以及 struct/enum 中被访问,其他类中不能被访问

@forzen

这个比较特殊,用的不多,它针对枚举或者结构体的声明使用该特性,可以显示你对该类型的修改。它只有在编译迭代库时被允许使用。未来版本的库不能通过添加、删除或重新排序枚举的case或者结构的存储实例属性来更改声明。在未冻结的类型上,这些操作都是允许的,但是它们破坏了冻结类型的 ABI兼容性。

注意: 昂编译器不处于迭代库的模式,所有的结构体和枚举都是隐性冻结,并且该特性会被忽视。

import

关于引入框架,我觉得很多人都在找类似 OCprefix 的文件,但是没找到,其实 Swift 中是存在这种方法的

@_exported import SDWebimage

众所周知,带有_ 的语句为未确定语句

将上面的代码放在 @main 函数执行的文件中进行引入,那么在当前模块中都无线再进行引入,可以直接使用

重命名与关联对象

typealias

typealias 用来对已经存在的类型重新定义新名字,通过命名,可以是代码变得更加清楚简洁。

typealias success = (_ data: String) -> Void

typealias ViewType = UIView

associatedType

associatedType 用来定义一个名字,也可以确定类型,具体使用的值需要用 typealias 来确定,一般在 protocol 中使用。

protocol Network {
	associatedType DataType // 没确定类型
	associatedType ViewType: UIView // 确定类型,实现时可以使用其子类
}

class Model: Network {
	typealias DataType = String
	typealias ViewType = UILabel
}

方法返回值

@discardableResult 一般用在有返回值的方法前,含义是,调用当前方法时,可以不使用对象去接收

func getName() -> String {
}
// 当调用该方法时需要使用对象承接
let name = getName()
// 或者
let _ = getName()

而如果不使用对象去接收时,编译器则会报告警告⚠️,因此使用 @discardableResult 将警告消除

@discardableResult func getAge() -> Int {
}

getAge()

关于继承

只有 class 类型可以被继承这个概念不用我多说吧,那么就来说说继承之后的事

  • override 覆盖父类的方法或属性,如覆盖父类的 init 方法
override init() {
	// 必须要执行父类的init方法
	// 其中子类自定义的属性要放在 super.init() 方法前执行
	super.init()
	// 子类修改父类的属性要放在 super.init() 方法后执行
}

// 覆盖属性父类属性要使用计算属性覆盖
override var name: String {
	get {
		return ""
	}
	set {
	
	}
}
  • convenience 用于修饰 init 方法,为便利构造器,可以 override convenience 覆盖父类的便利构造器,方法中必须执行当前子类的构造器
override init() {
}

override convenience init(name: String) {
	self.init()
	self.name = name
}
  • required 子类必须重写的 init 方法
required init(_ sex: String) {
	fatalError("init(_:) has not been implemented")
}
  • final 使它修饰的变量、方法、类不可继承,使用 final 可以提高性能。避免系统的动态派发(Dynamic Dispatch)
final class Person {}

动态成员查找

@dynamicMemberLookup 是在Swift 4.2 中增加的新特性,即动态成员查找。在使用了 @dynamicMemberLookup 修饰对象后(classstructenumprotocol),并且实现了 subscript(dynamicMember member: String) 方法后就可以方为不存在的属性,而属性就会作为 member 传入这个方法。

@dynamicMemberLookup
struct Person {
	subscript(dynamicMember member: String) -> String {
		let properties = ["nickname": "Zhang", "city": "BeiJing"]
		return properties[member, default: "nil"]
	}
}
// 执行
let p = Person()
print(p.city)
print(p.nickname)
print(p.name)

如果没有声明 @dynamicMemberLookup ,执行代码会报错
如果实现了多个 subscript(dynamicMember member: String) 方法时,需要在执行时执行返回类型

@dynamicMemberLookup
struct Person {
	subscript(dynamicMember member: String) -> String {
		let properties = ["nickname": "Zhang", "city": "BeiJing"]
		return properties[member, default: "nil"]
	}

	subscript(dynamicMember member: String) -> Int {
		return 18
	}
}

let p = Person()
let age: Int = p.age
print(age) // 18

@dynamicMemberLookup 内部处理

a = someValue.someMember
==>
a = someValue[dynamicMember: "someMember"]

动态传参

@dynamicCallable ,当对象被 @dynamicCallable 标记后,需要实现两个必要方法中的某一个或者两个来进行动态传参。

// 其中数组中的数据类型及返回类型可替换成其他类型
func dynamicallCall(withArguments args: [String]) -> Double

// 其中KeyValuePairs中的数据类型及返回类型可替换成其他类型
func dynamicallCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double

使用方法

@dynamicCallable
struct Random {
//    func generate(numberOfZeros: Int) -> Double {
//        let max = pow(10, Double(numberOfZeros))
//        return Double.random(in: 0...max)
//    }

	func dynamicallyCall(withArguments: [Int]) -> Double {
        return 12.0
    }
    
    func dynamicallyCall(withKeywordArguments args: [String: Int]) -> Double {
        let numberOfZeros = Double(args.first?.value ?? 0)
        let max = pow(10, Double(numberOfZeros))
        return Double.random(in: 0...max)
    }
}

// 调用
let random = Random()
// let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeros": 3])
let result = random(number: 3) // 调用 withKeywordArguments 方法
let result = random(3) // 12.0 调用 withArguments 方法
print(result)

@dynamicCallable 使用时需要注意一些重要规则

  1. 可以将其应用于 classstructenumprotocol
  2. 如果使用 withKeywordArguments: 并且不使用 withArguments: 你的类型仍然可以在没有参数标签的情况下调用 - 只不过会获得空字符串的键(key)
  3. 如果 withKeywordArguments: / withArguments: 被标记为 throwing ,则调用类型也将 throwing
  4. 不能将@dynamicCallable添加到扩展,只能添加到类型本身
  5. 你仍然可以向类型添加其他方法和属性,并正常使用。

Swift 的野心

Swift 之所以做出 @dynamicMemberLookup@dynamicCallable ,其根本原因在于 Swift 不仅仅将视线放在了 COC 的互通上,也将目光放在了将来与 PythonJavaScript 等动态语言一起工作。

属性包装器

@propertyWrapper 属性包装器的目的就是为了能够给属性一个统一的入口和出口,方便我们进行操作。比如当我们想监听属性时,需要在每个属性中做监听,而属性包装器可以将监听做成统一入口。

对于 @propertyWrapper,有两个要求:

  1. 必须使用 @propertyWrapper 进行定义
  2. 必须具有 wrappedValue 属性
@propertyWrapper
struct Random<T> {
    var wrappedValue: T
}

// 使用
@Random var r: Int = 0
// 或者
@Random(wrappedValue: 0) var r
// 或者
var r: Randow = Random(wrappedValue: 0)
/// 三种方法均可,通常使用第一种

系统定义好的属性包装器有 @State, @Binding, @Published, @ObservedObject, @StateObject, @EnvironmentObject, @Environment等。

// 关于在SwiftUI中有重大问题,将单列一篇文章来讲
@State private var height: CGFloat? // 在实际使用中有重大问题

使用限制

  • 具有包装器的属性不能再子类中覆盖
  • 具有包装器的属性不能有 lazy, @NSCoping, @NSManaged, weakunowned 修饰
  • wrappedValue, init(wrappedValue:)projectedValue 必须具有与包装类型本身相同或高级(并不生效)的访问控制级别
  • 不能再协议或扩展中声明带有包装器的属性

Where

where 在定义泛型的类中会被经常使用,

// 泛型
struct Stack<Element> {
}
// 如果要求泛型是某一特殊类型时
extension Stack where Element == String {
}
// 如果要求泛型遵循某一协议时
extension Stack where Element: Equatable {
}

// 定义非默认泛型
struct Sildeshow<Data, ID, Content>: View where Data: RandomAccessCollection, ID: Hashable, Content: View {
}
// 上面的定义可以写成
struct Sildeshow<Data: RandomAccessCollection, ID: Hashable, Content: View>: View {
}

也可以在 for循环 中使用

for i in 0...3 where i % 2 == 0 {
	print(i)
}

// 打印 0 2

结果生成器 resultBuilder

@resultBuilderSwift 5.4 的新特性,之前叫 Function Builders,它使用 buildBlock 方法可以将 多个内容 构建为 一个结果 ,该特性在 SwiftUI 中广泛使用,如 @ViewBuilder

可以使用 @resultBuilder 自定义Result Builders

@resultBuilder
struct StringBuidler {
	// 可以将多个值构建为一个结果
    static func buildBlock(_ components: String...) -> String {
    	// 多个值处理
        components.joined(separator: "\n")
    }
    
    // if 逻辑分支
    static func buildEither(first component: String) -> String {
        return "if \(component)"
    }
    // else 逻辑分支
    static func buildEither(second component: String) -> String {
        return "else \(component)"
    }
    // 当只实现或只执行if时,而结果又为false时走该逻辑分支
	static func buildOptional(_ component: String?) -> String {
        return "啥也不是"
    }
}

// 修饰方法
@StringBuidler
func buildString() -> String {
    "静夜思"
    "李白"
    "窗前明月光,疑是地上霜。"
    "举头望明月,低头思故乡。"
    
    if Bool.random() {
        "一首诗"
    } else {
        "一首词"
    }
}

// 修饰闭包参数
func builderString(@StringBuidler content: () -> String ) -> String {
    return content()
}

异常

throws

Swift 中,有很多在OC中执行没有问题但在 Swift 中使用可能会抛出异常的函数,这些函数都使用 throws 来标记,当我们执行多个 throws 方法时,可以自定义 throws 函数进行统一抛出异常。

// 例如在使用AVFoundation框架时
struct Record {
    var audioRecorder: AVAudioRecorder?
    mutating func record() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .default)
        try session.setActive(true)
        let audioUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("audio.m4a")
        let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC)]
        audioRecorder = try AVAudioRecorder(url: audioUrl, settings: settings)
    }
}

// 这时,当我们调用方法时,可以进行统一处理
var record = Record()
do {
    try record.record()
} catch {
    fatalError("record error \(error.localizedDescription)")
}

throw

throw 用于在函数内部,如果出现可能的异常,可以使用 throw 抛出这个异常。

enum TestError: Error {
    case errorOne(Int)
    case errorTwo(String)
}

func testThrow(num: Int) throws -> String {
    switch num {
    case 1:
        throw TestError.errorOne(1)
    case 2:
        throw TestError.errorTwo("2")
    default:
        return "no error"
    }
}

// 执行
do {
	let result: String = try testThrow(num: 2)
	print(result)
} catch TestError.errorOne(let num) {
	print(num)
} catch TestError.errorTwo(let str) {
	print(str)
} catch let err {
	print(err.localizedDescription)
}

rethrows

rethrows 关键字只起到传递异常的作用,在一个函数或者方法中,调用一个会throw的函数,就可以通过rethrows传递可能的异常

func testRethrow(testThrowCall: (Int) throws -> String, num: Int) rethrows -> String {
    try testThrowCall(num)
}

// 执行
do {
	let result: String = try testRethrow(testThrowCall: testThrow(num:), num: 2)
	print(result)
} catch TestError.errorOne(let num) {
	print(num)
} catch TestError.errorTwo(let str) {
	print(str)
} catch let err {
	print(err.localizedDescription)
}

异步等待 async/await/actor

async/await

async/await 功能是为了是在并发中的 Swift 代码能容易编写和理解,因为在一起一直使用闭包(Block)来处理异步操作。当你的代码又许多异步操作,并且是一个异步操作需要等待另一个异步操作时,最简单的是在一个异步操作的内部执行另一个异步操作,这种方法很快就会导致“回调地狱”。

因此Swift为语言带来了 async/await

函数可以选择成为异步的,允许开发者使用正常的控制流机制来组成涉及异步操作的复杂逻辑。编译器负责将一个异步函数翻译成适当的闭包和状态机集合。

下面的代码显示了如何声明和调用 async/await

struct Resource {}
struct LoadImage {
    func loadWebResource(_ path: String) async throws -> Resource {
        return Resource()
    }
    func decodeImage(_ r1: Resource, r2: Resource) async throws -> Image {
        return Image(systemName: "")
    }
    func dewarpAndCleanupImage(_ i: Image) async throws -> Image {
        return i
    }
    func processImageData() async throws -> Image {
        let r1 = try await loadWebResource("r1")
        let r2 = try await loadWebResource("r2")
        let image = try await decodeImage(r1, r2: r2)
        let result = try await dewarpAndCleanupImage(image)
        return result
    }
}

// 调用
let image = LoadImage()
do {
	Task {
		let i = try await image.processImageData()
	}
} catch {
	fatalError("load image error \(error.localizedDescription)")
}

注意

  • 在声明方法时,如果需要 asyncthrows 同时使用时,async 必须要在 throws 的前面。
  • 在调用方法时,如果需要 awaittry 同时使用时,try 必须要在 await 的前面。

虽然异步函数似乎大大简化了并发管理,但是它们并没有排除死锁或状态损坏的可能性。因此开发者需要注意暂停点异步函数带来的问题。为了避免死锁或数据损坏的风险,异步函数应避免调用可能阻塞其线程的函数。

那么怎么将已经定义好的闭包函数变成async函数呢?

转换时需要借助两个函数

public func withCheckedContinuation<T>(function: String = #function, _ body: (CheckedContinuation<T, Never>) -> Void) async -> T

public func withCheckedThrowingContinuation<T>(function: String = #function, _ body: (CheckedContinuation<T, Error>) -> Void) async throws -> T

两个函数的区别在于是否需要存在 throws

struct Resource {}
class LoadImage {
    func loadWebResource(_ path: String, resource: @escaping (Resource) -> Void) throws {
        resource(Resource())
    }
    func decodeImage(_ r1: Resource, r2: Resource, image: @escaping (Image) -> Void) throws {
        image(Image(systemName: "xmark"))
    }
    func dewarpAndCleanupImage(_ i: Image, image: @escaping (Image) -> Void) throws {
        image(i)
    }
    var r1: Resource!
    var r2: Resource!
    func imageData(_ complated: @escaping (Image) -> Void) throws {
        let semaphore = DispatchSemaphore.init(value: 0)
        try loadWebResource("") { [weak self] resource in
            self?.r1 = resource
            print("r1")
            semaphore.signal()
        }
        semaphore.wait(timeout: DispatchTime.distantFuture)
        try loadWebResource("") { [weak self] resource in
            self?.r2 = resource
            print("r2")
            semaphore.signal()
        }
        semaphore.wait(timeout: DispatchTime.distantFuture)
        try decodeImage(self.r1, r2: self.r2, image: { im in
            print("decode")
            complated(im)
        })
    }
    func processImageData() async throws -> Image {
        try await withCheckedThrowingContinuation({ continuation in
            let queue = DispatchQueue.init(label: "async.queue")
            queue.async {
                do {
                    try self.imageData { image in
                        continuation.resume(returning: image)
                    }
                } catch {
                    continuation.resume(throwing: error)
                }
            }
        })
    } 
}

// 调用
let image = LoadImage()
do {
	Task {
		let i = try await image.processImageData()
		print(i)
	}
} catch {
	fatalError("load image error \(error.localizedDescription)")
}

注意看上面代码中 imageData 的方法,在方法内部需要使用属性类存储相关回调的方法的值。并且还需要借助 semaphore 来进行值的赋值获取。当前类型也要修改为 class,确实比较麻烦,但是在调用的时候还是比较方便的。

actor

actor 是建立在 async/await 上的抽象,可以安全的访问可变的状态。简而言之 actor 封装了一些状态,并提供了一组方法来安全的访问它。

与类不同的是,actor 每次只允许一个任务访问其可变状态,这使得多个任务的代码可以安全的与 actor 的同一个实例进行交互

注意actor 的对象为引用类型

actor WebImage {
    let url: String
    var image: Image!
    private(set) var max: Int
    
    init(url: String, image: Image) {
        self.url = url
        self.image = image
        self.max = url.count
    }
}

// 调用
Task(priority: .background) {
	let image = await WebImage(url: "", image: Image(systemName: "xmark"))
}

自动闭包 @autoclosure

@autoclosure 是一种自动创建的闭包,用于将参数包装闭包。这种闭包不接受任何参数,当它被调用的时候,会返回传入的值,这种便利语法让你在调用的时候能够省略闭包的花括号。

函数中有一个 () -> Any 类型的参数,用 @autoclosure 修饰时,调用函数的时候可以传入一个确定的值 value,这个值会被自动包装成 (){ return value } 的闭包,就不需要显式的将闭包表达式写出来

struct Resource {
    func debugOutPrint(_ condition: Bool, _ message: @autoclosure () -> String) {
        if condition {
            print("debug = \(message())")
        }
    }
    
    func getString() -> String {
        return "Application Eror Occured"
    }
}

// 调用
let res = Resource()
res.debugOutPrint(true, "Application Error")
res.debugOutPrint(true, res.getString())

defer

defer {} 里的代码会在当前代码块返回的时候执行,无论当前代码块从哪个分支 return 的,即使抛出异常也会执行。

如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。

func function() {
    defer {
        print("first defer")
    }
    
    defer {
        print("second defer")
    }
    
    print("end of function")
}

function()
// 打印结果
// end of function
// second defer
// first defer

自定义运算符

Swift 中可以声明和实现自定义运算符。新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefixinfix 或者 postfix 修饰符

struct Point {
    var x: CGFloat
    var y: CGFloat
}

// 前缀运算符
prefix operator +++

extension Point {
    static prefix func +++ (point: Point) -> Point {
        return Point(x: point.x * 2, y: point.y * 2)
    }
}

// 执行
let point = Point(x: 2, y: 5)
+++point // x: 4.0, y: 10.0

每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。

而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。

下面例子定义了一个新的自定义中缀运算符 +- ,此运算符属于 AdditionPrecedence 优先级组,其他优先级组

infix operator +-: AdditionPrecedence

extension Point {
    static func +- (lhs: Point, rhs: Point) -> Point {
        return Point(x: lhs.x + rhs.x, y: lhs.y - rhs.y)
    }
}

// 调用
let p1 = Point(x: 2, y: 3)
let p2 = Point(x: 4, y: 2)
p1 +- p2 // x: 6.0, y: -1.0

注意: 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀和后缀运算符的时候,后缀运算符会先参与运算。

precedencegroup

precedencegroup 用于自定义优先级

precedencegroup CustomPrecedence {
    // 结合律
    associativity: none
    /// 赋值性
    assignment: 
    // 优先级设置
    // 高于加减法
    higherThan: AdditionPrecedence
    // 低于乘除法
    lowerThan: MultiplicationPrecedence
}
  • associativity 定义了结合律,即当多个同类的操作付出现的计算顺序。比如常见的加减法都是left,就是说多个加减法同时出现时,按照从左往右的顺序计算。不允许连续出现的操作符可以写成none
  • higherThan/lowerThan 用于指定自定义的优先级高于和低于另一个优先级

fallthrough

Swift fallthrough 语句让 case 之后的语句按顺序继续执行,且不论条件是否满足。
Swift 中的 switch 不会从上一个 case 分支落入到下一个case分支。只要第一个匹配到的 case 分支完成了它需要执行的语句,整个 switch 代码块完成了它的执行。

注意:在大多数语言中,switch 语句块中,case 要紧跟 break,否则 case 之后的语句会顺序运行,而在 Swift 语言中,默认是不会执行下去的,switch 也会终止。如果你想在 Swift 中让 case 之后的语句会按顺序继续运行,则需要使用 fallthrough 语句。

func getIndex(index: Int) {
    switch index {
    case 100:
        print("index value = 100")
        fallthrough
    case 10, 15:
        print("index value = 10 or 15")
        fallthrough
    case 5:
        print("index value = 5")
    default:
        print("default")
    }
}

// 执行
getIndex(index: 10)
// 打印结果
// index value = 10 or 15
// index value = 5

版本控制

available 是用于版本控制的,有 #available@available 两种,前一种是在函数或方法内部的,一般用于 ifguard 的判断。

if #available(iOS 15.0, macOS 10.0, watchOS 3.0, *) {
        
}

guard #available(iOS 14.0, macOS 11.0, watchOS 3.0, *) else {
    return
}

@available 一般用于整个方法或整个类的版本控制

@available(iOS 16.0, macOS 10.0, watchOS 2.0, *)
func avai() {}

@available(iOS 16.0, macOS 10.0, watchOS 2.0, *)
struct Person {}

如果你只想控制 iOS 的版本其他不想限制的时候,只写 iOS 的控制即可。

打印相关

  • #file 输出完整的当前文件的名字
  • #filePath 当前文件的绝对路径
  • #fileID 当前文件的相对路径,相对于当前工程
  • #line #line这个函数所在的行数
  • #column #column这个函数所在行的列数,即前面有多少个字符
  • #function 当前的方法名
  • #dsohandle 使用动态共享对象处理
  • #sourceLocation 行控制语句,可以改变#file#line等所打印的内容,其中参数 file 最好设置绝对路径
func printInfo() {
	print("some person info - on column \(#column), line \(#line), function \(#function), file \(#file), fileID \(#fileID), filePath \(#filePath), dsohandle \(#dsohandle), source ")
#sourceLocation(file: "ListDemo/Stack.swift", line: 1)
	print("some person info - on column \(#column), line \(#line), function \(#function), file \(#file), fileID \(#fileID), filePath \(#filePath), dsohandle \(#dsohandle), source ")
}

在这里插入图片描述

元类型

元类型就是类型的类型。比如说 5 是 Int 类型,此时 5 是 Int 类型的一个值。但是如果问Int类型占用多少内存空间,这个时候与具体某个值无关,而与类型的信息相关。如果要写一个函数,返回一个类型的实例内存空间大小。那么这个时候的参数是一个类型数据,这个类型数据是可以直接说明的,比如是Int 类型,也可以从一个值上取,比如 5 这个值的类型。这里的类型数据,就是一个类型的类型,术语叫元类型:Metatype

.Type.self

Swift 中的元类型用 .Type 表示,比如 Int.Type 就是 Int 的元类型。类型与值有着不同的形式,就像 Int5 的关系。元类型也是类似, .Type 是类型,类型的 .self 是元类型的值。

let intType: Int.Type = Int.self

可能平时对元类型使用的比较少,加上这两个形式有些接近,一个元类型只有一个对应的值,所以使用的时候常常写错

types.append(Int.Type)
types.append(Int.self)

如果分清了 Int.Type 是类型的名称而不是值,就不会再弄错了。因为你肯定不会这么写

numbers.append(Int)

type(of: T)

type(of: T) 是通过实例对象获取元类型的方式。

func type<T, Metatype>(of value: T) -> Metatype

因此,也可以通过实例对象来获取元类型

let intType: Int.Type = type(of: 5)
  • .self 取到的是静态的元类型,声明的时候是什么类型就是什么类型
  • type(of:) 取到的是运行时的元类型,也就是这个实例的类型

当我们访问静态变量的时候,其实也是通过元类型进行访问的,只是编译器帮我们省略了 .self。下面的两个写法是等价的

Int.max
Int.self.max

AnyClass

AnyClass 就是对 AnyObject 的元类型起的别名

public typealias AnyClass = AnyObject.Type

nonmutating

先来看一段代码

let pointer = UnsafeMutablePointer<Any>.allocate(capacity: 1)
pointer[0] = 1

let array = [Any](repeating: 0, count: 1)
array[0] = 1

同样是 let 修饰,同样用下标访问,同样是 struct ,为什么 UnsafeMutablePointer 不会报错?

二者的区别在于 subscript 方法

// Array
@inlinable public subscript(index: Int) -> Element

// UmsafeMutablePointer
@inlinable public subscript(i: Int) -> Pointee { get nonmutating set }

当用 nonmutating 修饰 setter方法 的时候,编译器会知道该方法不会改变常量变量因此不会报错

使用 nonmutating 有一定的危险性,因为它跳过了编译器的安全检查机制。不过我们可以在合适的情况下使用它,写出更加灵活的代码

比如给 UserDefaults 添加 subscript 方法

protocol UserDefaultsSubscipt {
    subscript(key: String) -> Any? { get nonmutating set }
}

extension UserDefaults: UserDefaultsSubscipt {
    subscript(key: String) -> Any? {
        get { return object(forKey: key) }
        set { set(newValue, forKey: key) }
    }
}

// 使用
let defaults = UserDefaults.standard
defaults["key"] = "value"

keyPath 字符串表达式

#keyPath 字符串表达式可以访问一个引用 Objective-C 属性的字符串,通常在 key-value 编程key-value 观察的API中使用。#keyPath(属性名)

属性名必须是一个可以在 Objective-C 运行时使用的属性的引用。在编译期,key-path 字符串表达式会被一个字符串字面量替换。

class SomeClass: NSObject {
    @objc var someProperty: Int
    init(someProperty: Int) {
        self.someProperty = someProperty
    }
}

// 调用
let c = SomeClass.init(someProperty: 12)
let keyPath = #keyPath(SomeClass.someProperty)
if let value = c.value(forKey: keyPath) {
	print(value)
}
// 12

当在一个类中使用 key-path 字符串表达式时,可以省略类名,直接使用属性名来访问这个类的某个属性。

extension SomeClass {
    func getSomeKeyPath() -> String {
        #keyPath(someProperty)
    }
}

print(c.getSomeKeyPath())
// someProperty

由于 key-path 字符串表达式在编译期才创建,编译期可以检查属性是否存在,以及属性是否暴露给 Objective-C 运行时。

注意:尽管#keyPath是一个表达式,但它永远不会被求值

@objc, @nonobjc, @objcMembers OC 混编

@objc

该特性用于修饰任何可以在Objective-C中表示的声明。

该特性用在扩展中,与在没有明确标记为nonobjc特性的扩展中给每个成员添加该特性具有相同效果。

编译器隐式的将objc特性添加到Objective-C中定义的任何类的子类。但是,子类不能是泛型,并且不能继承于任何泛型类。你可以将objc特性显式添加到满足这些条件的子类,来指定其Objective-C的名称。

在以下情况中同样会隐式的添加objc特性:

  • 父类有objc特性,且重写为子类的声明。
  • 遵循带有objc特性协议的声明。
  • 带有IBAction、IBSegueAction、IBOutlet、IBDesignable、IBInspectable、NSManaged或GKInspectable特性的声明。
class ExampleClass: NSObject {
	@objc var enabled: Bool {
		@objc(isEnabled) get {
			// 返回适当的值
		}
	}
}

@nonobjc

@nonobjc 针对方法、属性、下标或构造器的声明使用该特性将覆盖隐式的 objc 特性。nonobjc 特性告诉编译器该声明不能再Objective-C代码中使用,即便它能在Objective-C中表示。

@nonobjc 用在扩展中,与在没有明确标记为 objc 特性的扩展中给每个成员添加该特性具有相同效果。

可以使用 nonobjc 特性解决标有 objc 的类中桥接方法的循环问题,太特性还允许对标有 objc 的类中的构造器和方法进行重载。

标有 nonobjc 特性的方法不能重写标有 objc 特性的方法,而标有 objc 特性的方法可以重写标有 nonobjc 特性的方法。同样,标有 nonobjc 特性的方法不能满足标有 objc 特性的协议中的方法。

@objc class Point: NSObject {
    var x: CGFloat = 1
    var y: CGFloat = 1
    @nonobjc var center: Point
    
    override init() {
        self.center = Point()
        super.init()
    }
}

@objcMembers

该特性用于类声明,以将 objc 特性应用于该类、扩展、子类以及子类的扩展的所有 Objective-C 兼容成员。

大多数代码应该使用 objc 特性,以暴露所需的声明。如果需要暴露多个声明,可以将其分组到添加 objc 特性的扩展中。objcMembers 特性为大量使用 Objective-C 运行时的内省工具的库提供了便利。添加不必要的 objc 特性会增加二进制体积并影响性能。

@requires_stored_property_inits

该特性用于类声明,以要求类中所有存储属性提供默认值作为其定义的一部分。对于从中继承的任何类都推断出 NSManagedObject 特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值