• Swift 元类型、self、Self、AnyObject、Any和AnyClass


    1.元类型

    元类型是指所有类型的类型,包括类、结构体、枚举和协议。
    类、结构体或枚举类型的元类型是相应的类型名紧跟.Type。
    协议类型的元类型——并不是运行时适配该协议的具体类型——是该协议名字紧跟.Protocol。
    比如,类SomeClass的元类型就是SomeClass.Type,协议SomeProtocol的元类型就是SomeProtocal.Protocol。
    你可以使用后缀self表达式来获取类型。比如,SomeClass.self返回SomeClass本身,而不是SomeClass的一个实例。同样,SomeProtocol.self返回SomeProtocol本身,而不是运行时适配SomeProtocol的某个类型的实例。还可以对类型的实例使用type(of: someInstance)表达式来获取该实例在运行阶段的类型

    1. protocol TestProtocol {
    2. func test()
    3. }
    4. class SomeBaseClass {
    5. class func printClassName() {
    6. print("SomeBaseClass")
    7. }
    8. }
    9. class SomeSubClass: SomeBaseClass {
    10. var title = "标题"
    11. override class func printClassName() {
    12. print("SomeSubClass")
    13. }
    14. }
    15. let someInstance: SomeBaseClass = SomeSubClass()
    16. print(type(of: someInstance))
    17. print(type(of: SomeSubClass.self))
    18. print(type(of: TestProtocol.self))
    19. SomeSubClass
    20. SomeSubClass.Type
    21. TestProtocol.Protocol

    2. .self和self

    T.self:T是实例对象,当前T.self返回的就是实例对象本身;T是类,当前T.self返回的就是类型本身

    1. let p = SomeSubClass()
    2. print("\(p.self) === \(type(of: p.self))")
    3. print("\(SomeSubClass.self)--------\(type(of: SomeSubClass.self))")
    4. TestDemo1.ViewController.SomeSubClass === SomeSubClass
    5. SomeSubClass--------SomeSubClass.Type

    实例方法中self是实例对象的本身;在类方法中self是类型本身。

    1. class SomeSubClass: SomeBaseClass {
    2. var title = "标题"
    3. class func test(){
    4. print("类方法:\(self)")
    5. }
    6. func test(){
    7. print("对象方法:\(self)")
    8. }
    9. }
    10. 对象方法:TestDemo1.ViewController.SomeSubClass
    11. 类方法:SomeSubClass

    3.Self

    Self类型不是特定类型,而是为了方便引用当前类型,而无需重复或知道该类型的名称。在协议声明或者协议成员声明中,Self类型是指最终符合协议的类型。

    1. func get() -> Self {
    2. return self
    3. }
    4. static let age = 18
    5. func test1(){
    6. print("Self指代类型本身:\(Self.age)")
    7. }

    4.AnyObject、Any和AnyClass

    AnyObject可以代表任意 class 类型(用来替代OC中的 id)

    public typealias AnyObject
    

    由定义就可以看出它就是一个接口,所有的 class 都隐式地实现了这个接口。所以 AnyObject 只适用于 class 类型。但是 swift 中的基本类型都是 struct 类型,并不能用 AnyObject 来表示。所以官方又提出了一个更特殊的 Any 类型,它除了 class 以外还可以表示其他类型,可以说是任意类型(包括 struct,enum,func等)。
    例如:

    1. let swiftArr = ["a", "b", "c"]
    2. let swiftStr = "hello world"
    3. var array = [AnyObject]()
    4. array.append(swiftArr as AnyObject)
    5. array.append(swiftStr as AnyObject)

    这里我们显示的将 swift 中的 String 和 Array 转成了 AnyObject。实际上 array 里面的元素已经变成了 NSString 和 NSArray 了。

    当然我们还有另外的方式解决此问题,用 Any。

    1. let swiftArr1 = ["a", "b", "c"]
    2. let swiftStr1 = "hello world"
    3. var array1 = [Any]()
    4. array1.append(swiftArr1)
    5. array1.append(swiftStr1)

    可以看到结果全部是 swift 中的原生类型

    值得注意的是 Any 类型使用的时候需要使用 as 关键字做类型转换, 例如:

    1. let string = mixed.first as? String {
    2. print("The first element of mixed is \(string)")
    3. }

    AnyClass是AnyObject.Type的别名而已

    public typealias AnyClass = AnyObject.Type
    

    表示任意类的元类型,任意类的类型都隐式遵守这个协议,一般我们使用不多

    5.可选类型(Optional)

    您可以通过追加将数据类型简单地表示为 Optional。 方法是类型附加! 或 ?。 如果可选变量中包含一个值,则将其值返回为 Optional ,否则返回nil。

    1. var someValue:Int?
    2. var someAnotherValue:Int!
    3. print(someValue)
    4. print(someAnotherValue)

    注意:访问null的未包装可选对象时发生致命错误崩溃

    1. var someValue:Int!
    2. var unwrappedValue:Int = someValue

    当您运行该程序时,您将得到致命错误的崩溃:解开Optional值时意外发现nil,因为代码unwrappedValue:Int = someValue试图将Optional someValue中的值分配给变量unwrappedValue。
    但是,somevalue 是一个包含 nil 值的可选类型。试图将 nil 值分配给变量 unwrappedValue (这不是一个Optional)将导致崩溃。
    可以用if判断nil+iflet+guard三种方式处理上面崩溃问题

    6.类型别名(Typealias)

    类型别名不会创建新类型。它们只是为现有类型提供一个新名称。

    typealias name = existing type
    

    在Swift中,大多数类型都可以使用typealias。它们可以是:
    1.内置类型(例如:String, Int)
    2.用户定义的类型(例如:类,结构,枚举)
    3.复杂类型(例如:闭包)

    1. func someMethod(oncomp:(Int)->(String)){
    2. }
    3. typealias CompletionHandler = (Int)->(String)
    4. func someMethod(oncomp:CompletionHandler){
    5. }

    7.关联类(associatedtype)

    associatedtype定义关联类型,相当于类型的占位符,让实现协议的类型来指定具体的类型

    1. protocol Food {
    2. }
    3. protocol Animal {
    4. associatedtype F: Food
    5. func eat(_ food: F)
    6. }
    7. struct Meat: Food {
    8. }
    9. struct Tiger: Animal {
    10. func eat(_ food: Meat) {
    11. print("eat \(food)")
    12. }
    13. }
    14. /*
    15. 具有关联类型的协议类型,只能当做泛型约束使用
    16. 错误代码:
    17. func isTiger(animal: Animal) -> Bool {
    18. }
    19. */
    20. // 具有关联类型的协议类型,只能当做泛型约束使用
    21. func isTiger<A: Animal>(animal: A) -> Bool {
    22. if animal is Tiger {
    23. return true
    24. } else {
    25. return false
    26. }
    27. }

    8.类型修饰词

    1.available

    可用来标识计算属性、函数、类、协议、结构体、枚举等类型的生命周期。(依赖于特定的平台版本 或 Swift 版本)。它的后面一般跟至少两个参数,参数之间以逗号隔开。

    1. if #available(iOS 11.0, *) {
    2. scrollView.contentInsetAdjustmentBehavior = .never
    3. } else {
    4. automaticallyAdjustsScrollViewInsets = false
    5. }
    6. 还有一种用法是放在函数、结构体、枚举、类或者协议的前面,表示当前类型仅适用于某一平台
    7. @available(iOS 12.0, *)
    8. func adjustDarkMode() {
    9. /* code */
    10. }
    11. @available(iOS 12.0, *)
    12. struct DarkModeConfig {
    13. /* code */
    14. }
    15. @available(iOS 12.0, *)
    16. protocol DarkModeTheme {
    17. /* code */
    18. }

    2.@discardableResult

    带返回的函数如果没有处理返回值会被编译器警告。但有时我们就是不需要返回值的,这个时候我们可以让编译器忽略警告,就是在方法名前用@discardableResult声明一下。可以参考Alamofire中request的写法:

    1. @discardableResult
    2. public func request(
    3. _ url: URLConvertible,
    4. method: HTTPMethod = .get,
    5. parameters: Parameters? = nil,
    6. encoding: ParameterEncoding = URLEncoding.default,
    7. headers: HTTPHeaders? = nil)
    8. -> DataRequest
    9. {
    10. return SessionManager.default.request(
    11. url,
    12. method: method,
    13. parameters: parameters,
    14. encoding: encoding,
    15. headers: headers
    16. )
    17. }

    3.@inlinable

    这个关键词是可内联的声明,它来源于C语言中的inline。C中一般用于函数前,做内联函数,它的目的是防止当某一函数多次调用造成函数栈溢出的情况。因为声明为内联函数,会在编译时将该段函数调用用具体实现代替,这么做可以省去函数调用的时间。
    内联函数常出现在系统库中,OC中的runtim中就有大量的inline使用
    需要注意内联声明不能用于标记为private或者fileprivate的地方,这很好理解,对私有方法的内联是没有意义的。内联的好处是运行时更快。因为是编译时做替换,这增加了编译的开销,会相应的延长编译时间。

    4.@warn_unqualified_access

    通过命名我们可以推断出其大概含义:对“不合规”的访问进行警告。这是为了解决对于相同名称的函数,不同访问对象可能产生歧义的问题。
    比如说,Swift 标准库中Array和Sequence均实现了min()方法,而系统库中也定义了min(::),对于可能存在的二义性问题,我们可以借助于@warn_unqualified_access。

    1. extension Array where Self.Element : Comparable {
    2. @warn_unqualified_access
    3. @inlinable public func min() -> Element?
    4. }
    5. extension Sequence where Self.Element : Comparable {
    6. @warn_unqualified_access
    7. @inlinable public func min() -> Self.Element?
    8. }
    9. extension Array where Element: Comparable {
    10. func minValue() -> Element? {
    11. return min()
    12. }
    13. }

    我们会收到编译器的警告:Use of 'min' treated as a reference to instance method in protocol 'Sequence', Use 'self.' to silence this warning。它告诉我们编译器推断我们当前使用的是Sequence中的min(),这与我们的想法是违背的。因为有这个@warn_unqualified_access限定,我们能及时的发现问题,并解决问题:self.min()。

    5.@objc

    把这个特性用到任何可以在 Objective-C 中表示的声明上——例如,非内嵌类,协议,非泛型枚举(原始值类型只能是整数),类和协议的属性、方法(包括 setter 和 getter ),初始化器,反初始化器,下标。 objc 特性告诉编译器,这个声明在 Objective-C 代码中是可用的。
    @objc还有一个用处是当你想在OC的代码中暴露一个不同的名字时,可以用这个特性,它可以用于类,函数,枚举,枚举成员,协议,getter,setter等。

    1. 当在OC代码中访问enabled的getter方法时,是通过isEnabled
    2. class ExampleClass: NSObject {
    3. @objc var enabled: Bool {
    4. @objc(isEnabled) get {
    5. // Return the appropriate value
    6. }
    7. }
    8. }

    6.@objcMembers

    因为Swift中定义的方法默认是不能被OC调用的,除非我们手动添加@objc标识。但如果一个类的方法属性较多,这样会很麻烦,于是有了这样一个标识符@objcMembers,它可以让整个类的属性方法都隐式添加@objc,不光如此对于类的子类、扩展、子类的扩展都也隐式的添加@objc,当然对于OC不支持的类型,仍然无法被OC调用

    7.@frozen 和@unknown default

    @frozen即冻结,保证之后该值类型不会再变。其实我们常用的类型像Int、Float、Array、Dictionary、Set等都已被“冻结”。需要说明的是冻结仅针对struct和enum这种值类型,因为他们在编译器就确定好了内存布局。
    对于没有标记为frozen的枚举例如AVPlayerItem.Status,则认为该枚举值在之后的系统版本中可能变化。对于可能变化的枚举,我们在列出所有case的时候还需要加上对@unknown default的判断,这一步会有编译器检查。

    1. switch currentItem.status {
    2. case .readyToPlay:
    3. /* code */
    4. case .failed:
    5. /* code */
    6. case .unknown:
    7. /* code */
    8. @unknown default:
    9. fatalError("not supported")
    10. }
    1. lazy

    lazy是懒加载的关键词,当我们仅需要在使用时进行初始化操作就可以选用该关键词。

    1. lazy var dayLabel: UILabel = {
    2. let label = UILabel()
    3. label.text = self.todayText()
    4. return label
    5. }()

    使用lazy你可能会发现它只能通过var初始而不能通过let,这是由lazy 的具体实现细节决定的:它在没有值的情况下以某种方式被初始化,然后在被访问时改变自己的值,这就要求该属性是可变的。

    9.unowned weak

    weak相当于oc里面的weak,弱引用,不会增加循环计数。主体对象释放时被weak修饰的属性也会被释放,所以weak修饰对象就是optional。

    ·unowned相当于oc里面的unsafe_unretained,它不会增加引用计数,即使它的引用对象释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向nil。如果此时为无效引用,再去尝试访问它就会crash。

    1. lazy var someClosure: () -> Void = { [weak self] in
    2. // 被weak修饰后selfoptional,这里是判断self非空的操作
    3. guard let self = self else { retrun }
    4. self.doSomethings()
    5. }

    10.some

    some是Swift5.1新增的特性。它的用法就是修饰在一个 protocol 前面,默认场景下 protocol 是没有具体类型信息的,但是用some 修饰后,编译器会让 protocol 的实例类型对外透明。

    可以通过一个例子理解这段话的含义,当我们尝试定义一个遵循Equatable协议的value时:

    1. // Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
    2. var value: Equatable {
    3. return 1
    4. }
    5. var value: Int {
    6. return 1
    7. }

    编译器提示我们Equatable只能被用来做泛型的约束,它不是一个具体的类型,这里我们需要使用一个遵循Equatable的具体类型(Int)进行定义。但有时我们并不想指定具体的类型,这时就可以在协议名前加上some,让编译器自己去推断value的类型:

    1. var value: some Equatable {
    2. return 1
    3. }
    1. struct ContentView: View {
    2. var body: some View {
    3. Text("Hello World")
    4. }
    5. }

    9.属性修饰词和权限控制词

    swift提供了5个不同的访问级别,权限最高的是open,其次依次是public、internal、fileprivate,最低是private。默认使用的是internal。
    open & public
    使用open和public定义的实体,在它们定义的module的任意文件中皆可使用。其他module如果import了此实体,则其他module的源文件也可使用。一般在framework中指定的公开接口里,使用open和public级别。
    open 和 public 的区别
    public或更低权限的类,只能在其定义的module中子类化
    public或更低权限的类的成员,只能在其定义的module中被重写或子类化
    open权限的类,可在其定义的module,或者import了其的module中子类化
    open权限的类的成员,可在其定义的module,或者import了其的module中重写或子类化
    internal
    intenal修饰的实体在其定义的module中皆可使用,但是在其他module中无法使用。一般定义APP或者framework内部结构的时候,会使用internal级别
    fileprivate
    fileprivate定义的实体,只能在其源文件中使用,当其只在整个源文件中使用的时候,使用fileprivate级别
    private
    private定义的实体,只能在定义的范围内,和其在同一文件的extension中使用,当其只在当前声明范围内使用的时候,使用private级别

    使用原则
    子类:子类的访问级别不可以高于父类,但子类重写的方法的访问级别可以高于父类
    枚举:枚举的每个值都和它们所属枚举有相同的权限,且不能单独为某个值定义权限
    协议:协议可以指定访问级别,并且对于协议的每一项来说,都和协议的访问级别相同,不能单独为协议的某个方法指定不同的访问级别

  • 相关阅读:
    Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单
    sql server如何卸载干净?来看这里
    SpringBoot Web开发----简单功能分析
    SDRAM 控制器(三)——自动刷新模块
    深度解析Java JDK 1.8中Stream流的源码实现:带你探寻数据流的奥秘
    Spring面试题:(七)Spring AOP思想及实现
    【C++】unordered_set与unordered_map的封装
    并发编程详解 一文读懂并发编程 线程 线程三大特性 volatile synchronized final CAS AQS Semaphore信号量 线程池
    iNFTnews|传统Web2品牌商如何进军元宇宙?
    Nginx网络服务的配置
  • 原文地址:https://blog.csdn.net/xiaobo0134/article/details/127901170