Swift集合类型协议浅析(下)

728 阅读9分钟
原文链接: mp.weixin.qq.com

☝点击上方蓝字,关注我们!

本文字数:1964

预计阅读时间:8分钟

导读

本篇是Swift集合类型协议浅析系列文章的下篇,在这篇文章中,我们将继续围绕集合类型协议展开讨论,侧重点更多地关注于String相关的周边协议。

StringProtocol

代表一个字符串,这个字符串是由字符构成的集合,StringProtocol协议抽象了字符串的使用场景,比如uppercased()、lowercased()、comparable和collection等。在标准库中,只有String和SubString类型遵循StringProtocol协议,可以直接使用==对SubString和String进行判断,不需要类型转换。

1let helloSwift = "Hello Swift"2let swift = helloSwift[helloSwift.index(helloSwift.startIndex, offsetBy: 6)...]34// comparing a substring to a string5swift == "Swift"  // true

也可以遍历substring,或者从substring截取子字符串。在标准库里也有一小部分函数使用StringProtocol类型作为参数。比如把一个字符串转换为整型就是:init(text: StringProtocol)。虽然你可能不关心是string和substring,但是使用StringProtocol作为参数类型,调用者就不用进行类型转换,对他们会友好很多。

CustomStringConvertible & CustomDebugStringConvertible

使用文本方式打印输出对象是一个常见场景,Swift提供了多种方式:

  • print使用String(describing:)初始化;

  • debugPrint使用String(reflecting:);

  • dump使用Mirror(reflecting:)反射机制。

Function

Protocol

Required   Property

print

CustomStringConvertible

description

debugPrint

CustomDebugStringConvertible

debugDescription

dump

CustomReflectable

customMirror

来看一个例子:

 1struct BankCard { 2    let bankName: String 3    let cardNumber: Int 4} 5let bankcard = BankCard(bankName: "CBC",cardNumber: 123232324565) 6// BankCard(bankName: "CBC", cardNumber: 123232324565) 7 8extension BankCard: CustomStringConvertible { 9    var description: String {10        return "\(self.bankName) \(self.cardNumber)"11    }12}13print(bankcard)14// CBC 123232324565
1extension BankCard: CustomDebugStringConvertible {2    var debugDescription: String {3        return """4        BankName: \(self.bankName)5        CardNumber: \(self.cardNumber)6        """7} }

dump

 1func dump<T>(_ value: T, name: String? = nil, indent: Int = 0, maxDepth: Int = .max, maxItems: Int = .max) -> T 2 3/* * - T:是要打印的参数,是一个范型,也就是支持输出各种类型  4 5* - name: 默认是空白,如果加上则会在打印内容前加入这个name 6 7* - indent:缩进,默认是0,如果设置则会向前缩进相应的空白  8 9* - maxDepth:最大深度,默认全部打印,可以根据层级需要设置这个参数 1011* - maxItems:最大条数,默认是全部打印,如果需要限制内容,可以设置这个参数1213 */
 1let names = ["apple", "orange", "banana"] 2dump(names) 3 4print(names) 5 6let iPhones = ["iPhoneX": 9688, "iPhone8 plus": 7888, "iphone8": 6888] 7dump(iPhones) 8 9//输出10/*11▿ 3 elements12  - "apple"13  - "orange"14  - "banana"15["apple", "orange", "banana"]16▿ 3 key/value pairs17  ▿ (2 elements)18    - key: "iPhoneX"19    - value: 968820  ▿ (2 elements)21    - key: "iPhone8 plus"22    - value: 788823  ▿ (2 elements)24    - key: "iphone8"25    - value: 688826*/
  • 打印log的时候,可以取代print,打印更详细的内容;

  • 需要在控制台打印数组或者字典的时候,用dump输出的东西更立体,不会像print一样打印出来是一行,对于特别大的数组或者字典很好用;

  • debug的时候能直接打印出对象的信息,我们debug的时候常常会打断点,然后查看对象里的参数的值,用dump相当于把参数的那个界面全部展开并打印到了控制台上。

LosslessStringConvertible

遵循此协议的类型,可以被转换为String,同时与此相反,可以再从String转换回原始类型,没有任何信息丢失;

协议的继承关系如上图所示,很多系统类型已经实现了LosslessStringConvertible,所以才能与String之间互相转换,如Int、Int8、Int32、Int64、Bool、Character、Double、Float等。

遵循协议需要实现init?(_ description: String)和description。

 1extension FlightCode: LosslessStringConvertible { 2    public init?(_ description: String) { 3        let components = description.split(separator: " ") 4        guard components.count == 2, 5            let airlineCode = components.first, 6            let number = components.last, 7            let flightNumber = Int(number) 8        else { 9            return nil10        }11        self.airlineCode = String(airlineCode)12        self.flightNumber = flightNumber13    }14}15let flight = FlightCode(airlineCode: "AA",16                        flightNumber: 1)1718String(flight)19// "AA 1"2021FlightCode(String(flight))22// FlightCode(airlineCode: "AA", flightNumber: 1)

ExpressibleByUnicodeScalarLiteral,ExpressibleByExtendedGraphemeClusterLiteral,ExpressibleByStringLiteral

String字面量使用引号表示,当封闭的值包含单个字符集群时,同样的语法也可以表示字符集群文字;当封闭的值包含单个字符值时,同样的语法也可以表示Unicode标量文字。

1// ExpressibleByUnicodeScalarLiteral 2let unicodeScalar: Unicode.Scalar = "A"34// ExpressibleByExtendedGraphemeClusterLiteral 5let character: Character = "A"67// ExpressibleByStringLiteral 8let string: String = "A"

可扩展的字形集群

每一个Swift的Character类型实例都表示了单一的扩展字形集群。扩展字形集群是一个或者多个有序的Unicode标量(当组合起来时)产生的单个人类可读字符。举例来说,字母é以单个Unicode标量é(LATIN SMALL LETTER E WITH ACUTE,或者U+00E9)表示。总之,同样的字母也可以用一对标量——一个标准的字母e(LATINSMALL LETTER E,或者说U+0065),以及COMBINING ACUTE ACCENT标量(U+0301)表示。COMBINING ACUTE ACCENT标量会以图形方式应用到它前边的标量上,当Unicode文本渲染系统渲染时,就会把e转换为é来输出。

在这两种情况中,字母é都会作为单独的Swift Character值以扩展字形集群来表示。在前者中,集群包含了一个单独的标量;后者,则是两个标量的集群。

1let eAcute: Character = "\u{E9}" // é2let combinedEAcute: Character = "\u{65}\u{301}" // e followed by 3// eAcute is é, combinedEAcute is é

扩展字形集群是一种非常灵活的把各种复杂脚本字符作为单一Character值来表示的方法。比如说韩文字母中的音节能被表示为复合和分解序列两种,这两种表示在Swift中都完全合格于单一Character值:

1let precomposed: Character = "\u{D55C}" // 한2let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ3// precomposed is 한, decomposed is 한

继承关系:

所以初始化一个Unicode.Scalar通过包含多个scalar标量的字面量,或者多个字符字面量期望转成一个字符,都会失败。

1("ABC" as Unicode.Scalar) // Error2("ABC" as Character) // Error

通过字面量初始化特定类型

 1extension URL: ExpressibleByStringLiteral { 2    public init(stringLiteral value: String) { 3        guard let url = URL(string: "\(value)") else { 4            preconditionFailure("This url: \(value) is not invalid") 5        } 6        self = url 7    } 8} 910let url:URL = "https://www.baidu.com"1112print(url)1314//let urls = URL.init(string: "https://www.baidu.com")

上面这个例子扩展了URL类型,使其能够直接通过String字面量转换为URL,十分简洁,但是这种方式也有其弊端,开发中容易引起歧义。

ExpressibleByStringInterpolation

字符串插值是Swift5的新特性;通常,字符串文字中的内插值被转换为字符串,使用String(describing:);通过遵循ExpressibleBy

StringInterpolation协议(这个协议继承ExpressibleByStringLiteral),这个类型可以定义StringInterpolation通过字面量改变插值行为。

ExpressibleByStringLiteral

三个方法需要实现:

1init(stringLiteral value: String)2init(extendedGraphemeClusterLiteral value: String)3init(unicodeScalarLiteral value: String)

ExpressibleByStringInterpolation

1init(stringInterpolation: StringInterpolation)

要让一个类型遵循ExpressibleByStringInterpolation,最基本的你需要:

  • 让这个类型拥有一个类型为StringInterpolation的子类型,这个子类型遵循StringInterpolationProtocol并将负责解释插值;

  • 这个子类型仅需要实现appendLiteral(_ literal: String)方法,再选择一个或多个你自己想要支持的appendInterpolation(...)签名的方法;

  • 这个StringInterpolation子类型会作为“构造器”服务于你的主类型,然后编译器会调用那些append…方法一步一步地构造对象;

  • 然后你的主类型需要实现init(stringInterpolation: StringInterpolation),它会用上一步的结果来实例化它自己。

你可以实现任何你喜欢的appenInterpolation(...)方法,这意味着你可以任意选择支持什么插值。这是一个带来巨大的可能性的超强功能。

举个例子,如果你实现了func appendInterpolation(_ string: String, pad: Int),那么意味着你将可以用类似这样的插值:"Hello (name, pad: 10), how are you?"来构造你的类型。插值只需要匹配你的StringInterpolation子类型其中一个支持的appendInterpolation方法签名。

参考:

[1].https://swift.gg/2019/04/22/swift5-stringinterpolation-part1/

[2].https://academy.realm.io/cn/posts/try-swift-soroush-khanlou-sequence-collection/

[3].https://swift.gg/2017/02/20/why-is-dictionary-not-a-mutablecollection/


狐友技术团队其他精彩文章

Swift集合类型协议浅析(上)

分布式追踪系统概述及主流开源系统对比

不了解GIF的加载原理?看我就够了!

安卓系统权限,你真的了解吗?

Swift之Codable实战技巧


加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛