一.String源碼
1.Swift中String在內(nèi)存中是如何存儲的
這里我們定義了一個空字符串,想要通過內(nèi)存信息來查看關(guān)于String的內(nèi)存信息
var empty = ""
print(empty)
/*
(lldb) po withUnsafePointer(to: &empty){print($0)}
0x00000001000102c8
0 elements
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0x0000000000000000 0xe000000000000000
0x1000102d8: 0x0000000000000000 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
(lldb)
*/
發(fā)現(xiàn)只有一個0xe000000000000000
信息,其它的都不清楚
因此,我們只能通過源碼來分析
找到標(biāo)準(zhǔn)庫里面的String.swift
@frozen
public struct String {
public // @SPI(Foundation)
var _guts: _StringGuts
@inlinable @inline(__always)
internal init(_ _guts: _StringGuts) {
self._guts = _guts
_invariantCheck()
}
// This is intentionally a static function and not an initializer, because
// an initializer would conflict with the Int-parsing initializer, when used
// as function name, e.g.
// [1, 2, 3].map(String.init)
@_alwaysEmitIntoClient
@_semantics("string.init_empty_with_capacity")
@_semantics("inline_late")
@inlinable
internal static func _createEmpty(withInitialCapacity: Int) -> String {
return String(_StringGuts(_initialCapacity: withInitialCapacity))
}
/// Creates an empty string.
///
/// Using this initializer is equivalent to initializing a string with an
/// empty string literal.
///
/// let empty = ""
/// let alsoEmpty = String()
@inlinable @inline(__always)
@_semantics("string.init_empty")
public init() { self.init(_StringGuts()) }
}
-
String
是一個結(jié)構(gòu)體類型 - 里面有一個成員變量
_StringGuts
探究_StringGuts
,打開StringGuts.swift
@frozen
public // SPI(corelibs-foundation)
struct _StringGuts: UnsafeSendable {
@usableFromInline
internal var _object: _StringObject
@inlinable @inline(__always)
internal init(_ object: _StringObject) {
self._object = object
_invariantCheck()
}
// Empty string
@inlinable @inline(__always)
init() {
self.init(_StringObject(empty: ()))
}
}
-
_StringGuts
是一個結(jié)構(gòu)體類型 - 在
_StringGuts
的初始化函數(shù)中又傳入了_StringObject
探究_StringObject
,進入StringObject.swift
@inlinable @inline(__always)
internal init(empty:()) {
// Canonical empty pattern: small zero-length string
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
//arm/x86執(zhí)行的邏輯
self.init(
count: 0,
variant: .immortal(0),
discriminator: Nibbles.emptyString,
flags: 0)
#else
self._countAndFlagsBits = 0
self._object = Builtin.valueToBridgeObject(Nibbles.emptyString._value)
#endif
_internalInvariant(self.smallCount == 0)
_invariantCheck()
}
- 如果是
i386
或者是arm
架構(gòu)執(zhí)行前一個邏輯,調(diào)用自身的init函數(shù)
探究self.init(count: 0, variant: .immortal(0), discriminator: Nibbles.emptyString, flags: 0)
internal struct _StringObject{
...
enum Nibbles {}
@usableFromInline
internal var _count: Int
@usableFromInline
internal var _variant: Variant
@usableFromInline
internal var _discriminator: UInt8
@usableFromInline
internal var _flags: UInt16
@inlinable @inline(__always)
init(count: Int, variant: Variant, discriminator: UInt64, flags: UInt16) {
_internalInvariant(discriminator & 0xFF00_0000_0000_0000 == discriminator,
"only the top byte can carry the discriminator and small count")
//大小,代表當(dāng)前字符串的大小
self._count = count
//枚舉,來判斷字符串類型
self._variant = variant
//識別器,區(qū)分當(dāng)前字符串是否為ASCII碼
self._discriminator = UInt8(truncatingIfNeeded: discriminator &>> 56)
//標(biāo)志
self._flags = flags
self._invariantCheck()
}
...
}
-
_StringObject
也是一個結(jié)構(gòu)體類型,存放的一些變量。 - 從目前來看
String
就是值類型
關(guān)于discriminator: Nibbles.emptyString
extension _StringObject.Nibbles {
// The canonical empty string is an empty small string
@inlinable @inline(__always)
internal static var emptyString: UInt64 {
//是否是ascii
return _StringObject.Nibbles.small(isASCII: true)
}
}
@inlinable @inline(__always)
internal static func small(isASCII: Bool) -> UInt64 {
return isASCII ? 0xE000_0000_0000_0000 : 0xA000_0000_0000_0000
}
// Discriminator for large, immortal, swift-native strings
// 代表原生的大的字符串
@inlinable @inline(__always)
internal static func largeImmortal() -> UInt64 {
return 0x8000_0000_0000_0000
}
- 這也就是我們剛開始時,觀察
empty
字符串內(nèi)存空間里有一個0xe000000000000000
- 如果我們將字符改為中文字符串,例如"我",那么此時這里就會返回
0xA000_0000_0000_0000
-
large string
會返回0x8000_0000_0000_0000
總結(jié):識別器就是區(qū)分當(dāng)前字符串存儲類型`
2.小字符串
源碼中small string
的注釋
On 64-bit platforms, small strings have the following per-byte layout. When
stored in memory (little-endian), their first character ('a') is in the lowest
address and their top-nibble and count is in the highest address.
┌───────────────────────────────┬─────────────────────────────────────────────┐
│ _countAndFlags │ _object │
├───┬───┬───┬───┬───┬───┬───┬───┼───┬───┬────┬────┬────┬────┬────┬────────────┤
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼────┼────┼────┼────┼────┼────────────┤
│ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ 1x10 count │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────┴────────────┘
On 32-bit platforms, we have less space to store code units, and it isn't
contiguous. However, we still use the above layout for the RawBitPattern
representation.
┌───────────────┬───────────────────┬────────┬─────────┐
│ _count │_variant .immortal │ _discr │ _flags │
├───┬───┬───┬───┼───┬───┬───┬───┬───┼────────┼────┬────┤
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
├───┼───┼───┼───┼───┴───┴───┴───┴───┼────────┼────┼────┤
│ a │ b │ c │ d │ e f g h │1x10 cnt│ i │ j │
└───┴───┴───┴───┴───────────────────┴────────┴────┴────┘
- 對于64位來說,從低位到高位存放字符串?dāng)?shù)據(jù)。最高位存放的是
count
(字符串個數(shù)) - 對于小字符串來說,并沒有將該空間(16字節(jié))占滿的字符串
那么我們現(xiàn)在來嘗試驗證一下
var str = "ab"
print(str)
/*
(lldb) po withUnsafePointer(to: &str){print($0)}
0x00000001000102c8
0 elements
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0x0000000000006261 0xe200000000000000
0x1000102d8: 0x0000000000000000 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
(lldb)
*/
- 低位存放
6261
也就是ab字符串的值。類似于NSString
的內(nèi)存方式 - 高位存放的是識別器數(shù)據(jù)和字符串count
3.大字符串
var str = "abcdefghijklmnopq"
print(str)
/*
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0xd000000000000011 0x800000010000a570
0x1000102d8: 0x0000000000000000 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
(lldb)
*/
此時的內(nèi)存布局就變了,第二個8字節(jié)的高64位我們應(yīng)該清楚表示的是一個large string
源碼中large string
的注釋
Large strings can either be "native", "shared", or "foreign".
Native strings have tail-allocated storage, which begins at an offset of
`nativeBias` from the storage object's address. String literals, which reside
in the constant section, are encoded as their start address minus `nativeBias`,
unifying code paths for both literals ("immortal native") and native strings.
Native Strings are always managed by the Swift runtime.
Shared strings do not have tail-allocated storage, but can provide access
upon query to contiguous UTF-8 code units. Lazily-bridged NSStrings capable of
providing access to contiguous ASCII/UTF-8 set the ObjC bit. Accessing shared
string's pointer should always be behind a resilience barrier, permitting
future evolution.
Foreign strings cannot provide access to contiguous UTF-8. Currently, this only
encompasses lazily-bridged NSStrings that cannot be treated as "shared". Such
strings may provide access to contiguous UTF-16, or may be discontiguous in
storage. Accessing foreign strings should remain behind a resilience barrier
for future evolution. Other foreign forms are reserved for the future.
Shared and foreign strings are always created and accessed behind a resilience
barrier, providing flexibility for the future.
┌────────────┐
│ nativeBias │
├────────────┤
│ 32 │
└────────────┘
┌───────────────┬────────────┐
│ b63:b60 │ b60:b0 │
├───────────────┼────────────┤
│ discriminator │ objectAddr │
└───────────────┴────────────┘
discriminator: See comment for _StringObject.Discriminator
objectAddr: The address of the beginning of the potentially-managed object.
TODO(Future): For Foreign strings, consider allocating a bit for whether they
can provide contiguous UTF-16 code units, which would allow us to avoid doing
the full call for non-contiguous NSString.
- 大字符串會開辟內(nèi)存空間去存放字符串,并且在b0~b60中存放的是開辟的內(nèi)存空間地址。
- 真正的字符串內(nèi)存地址是需要將存放的
內(nèi)存地址 + nativeBias(32)
那么我們?nèi)カ@取一下
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0xd000000000000011 0x800000010000a570
0x1000102d8: 0x0000000000000000 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x10000a590
0x10000a590: 0x6867666564636261 0x706f6e6d6c6b6a69
0x10000a5a0: 0x00000020000a0071 0x0000000000000000
0x10000a5b0: 0x7365547466697773 0x7465677261542f74
0x10000a5c0: 0x74654d7373616c43 0x77732e6174616461
(lldb)
- 從該內(nèi)存中已經(jīng)能夠獲取到字符串信息了
那么之前第一個8字節(jié)0xd000000000000011
又是存放的什么信息呢?
首先想一個問題,之前小字符串的時候,高地址上存放的是識別器并且識別器的類型為ASCII
,此時的大字符串存放的是0x8000_0000_0000_0000
。導(dǎo)致了字符串類型并不清楚了。并且count存放在哪里也不清楚了(當(dāng)然通過觀察很明顯的看出0x11就是count)。
通過源碼中的注釋來解釋這個8字節(jié)的位域到底存放了哪些信息
// TODO(String docs): Combine this with Nibbles table, and perhaps small string
// table, into something that describes the higher-level structure of
// _StringObject.
All non-small forms share the same structure for the other half of the bits
(i.e. non-object bits) as a word containing code unit count and various
performance flags. The top 16 bits are for performance flags, which are not
semantically relevant but communicate that some operations can be done more
efficiently on this particular string, and the lower 48 are the code unit
count (aka endIndex).
┌─────────┬───────┬──────────────────┬─────────────────┬────────┬───────┐
│ b63 │ b62 │ b61 │ b60 │ b59:48 │ b47:0 │
├─────────┼───────┼──────────────────┼─────────────────┼────────┼───────┤
│ isASCII │ isNFC │ isNativelyStored │ isTailAllocated │ TBD │ count │
└─────────┴───────┴──────────────────┴─────────────────┴────────┴───────┘
isASCII: set when all code units are known to be ASCII, enabling:
- Trivial Unicode scalars, they're just the code units
- Trivial UTF-16 transcoding (just bit-extend)
- Also, isASCII always implies isNFC
isNFC: set when the contents are in normal form C
- Enables trivial lexicographical comparisons: just memcmp
- `isASCII` always implies `isNFC`, but not vice versa
isNativelyStored: set for native stored strings
- `largeAddressBits` holds an instance of `_StringStorage`.
- I.e. the start of the code units is at the stored address + `nativeBias`
isTailAllocated: contiguous UTF-8 code units starts at address + `nativeBias`
- `isNativelyStored` always implies `isTailAllocated`, but not vice versa
(e.g. literals)
- `isTailAllocated` always implies `isFastUTF8`
TBD: Reserved for future usage
- Setting a TBD bit to 1 must be semantically equivalent to 0
- I.e. it can only be used to "cache" fast-path information in the future
count: stores the number of code units, corresponds to `endIndex`.
NOTE: isNativelyStored is *specifically* allocated to b61 to align with the
bit-position of isSmall on the BridgeObject. This allows us to check for
native storage without an extra branch guarding against smallness. See
`_StringObject.hasNativeStorage` for this usage.
-
b63 -> isASCII
最高位存放的是否為ASCII
-
b62 -> isNFC
當(dāng)內(nèi)容為標(biāo)準(zhǔn)c時設(shè)置。如果為ASCII
總是為1 -
b61 -> isNativelyStored
是否為原生存儲 -
b60 -> isTailAllocated
存儲大字符串時為1 -
b59:48 -> TBD
占位,供以后使用 -
b47:0 -> count
,字符串長度
0xd000000000000011
-> 1101 ...
總結(jié):前8個字節(jié)其實就是存儲的flag + count
3.String.Index
let str = "Hello World"
/*
當(dāng)前字符串不支持下標(biāo)的存取方式
*/
//str[1]
//必須要使用String.Index來取字符串
print(str[str.index(str.startIndex, offsetBy: 1)]) // e
1.為什么String要這么繁瑣?
聊到這個問題我們就必須要明白Swift String
代表的是什么?一系列的Characters(字符)
,字符的表示方式有很多,比如我們最熟悉的ASCII碼
,ASCII碼
一共規(guī)定了128個字符,對于英文字符來說128個字符已經(jīng)夠用了,但是相對于其它語言來說,這是遠(yuǎn)遠(yuǎn)不夠的。
這就意味著不同國家的語言都需要有自己的編碼格式,這個時候同一個二進制文件就有可能翻譯成不同的字符,有沒有一種編碼能夠把所有的符號都納入其中,這就是我們熟悉的Unicode
,但是Unicode
只是規(guī)定了符號對應(yīng)的二進制代碼,并沒有詳細(xì)明確這個二進制代碼應(yīng)該如果存儲。
這里舉一個例子:假如我們有一個字符串"我是Kody",其中對于的Unicode、二進制信息分別為
我 -> 6212 -> 0110 0010 0001 0010
是 -> 662F -> 0110 0110 0010 1111
K -> 004B -> 0000 0000 0100 1011
O -> 006F -> 0000 0000 0110 1111
D -> 0064 -> 0000 0000 0110 0100
Y -> 0079 -> 0000 0000 0111 1001
此時的ASCII碼
對應(yīng)的Unicode
只占了1字節(jié),如果統(tǒng)一使用2字節(jié)的形式存儲的話,會造成很大的資源浪費
為了解決資源浪費的問題,就引入了UTF-8
編碼方式。
UTF-8
最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4字節(jié)表示一個符號,根據(jù)不同的符號而變化字節(jié)長度。這里簡單介紹一下UTF-8
的規(guī)則:
單字節(jié)的字符,字節(jié)的第一位設(shè)為0,對于英語文本,
UTF-8碼
只要占用一個字節(jié),和ASCII碼完全相同。(例如:“K”的UTF-8碼
與ASCII碼
一致都是0x4B
)n個字節(jié)的字符(n>1),第一個字節(jié)的前n位設(shè)為1,第n位設(shè)為0,后面字節(jié)的前兩位都設(shè)為10,這n個字節(jié)的其余空位填充該字節(jié)的
unicode碼
,高位用0補足
我 -> 11100110 10001000 10010010
是 -> 11100110 10011000 10101111
K -> 0100 1011
O -> 0110 1111
D -> 0110 0100
Y -> 0111 1001
比如存放“我”,“我”的Unicode碼
為2個字節(jié),UTF-8
使用3個字節(jié)存儲。第一個前3位存放1,第4位存放0,其余2字節(jié)的前2位存放10。剛好規(guī)則占用了1字節(jié),剩下的2字節(jié)剛好存放Unicode碼
-
Unicode
規(guī)定了字符對應(yīng)的二進制 -
UTF-8
規(guī)定了Unicode
在內(nèi)存中存儲方式
當(dāng)我們了解了Unicode
和UTF-8
后,此時我們再來分析為什么Swift中的字符串不能通過下標(biāo)(Int)來取字符?
對于Swift來說,String是一系列字符的集合,也就意味著String中的每一個元素是不等長的。那也就意味著我們在進行內(nèi)存移動的時候步長是不一樣的(每個字符的UTF-8
可能都不是一樣長的,1~4個字節(jié))。
比如說我要取str[1],那我是不是要把我
這個字段遍歷完之后才能確定是
的偏移量,這個時候無疑增加了很多的內(nèi)存消耗,也就是這個原因我們不能通過Int
作為下標(biāo)去訪問String
。
2.String.Index的本質(zhì)到底是什么?
之前了解了因為編碼的問題,導(dǎo)致的步長不同,因而不能使用Int
下標(biāo)去訪問字符串。
那么String.Index
是怎么去訪問的呢?
進入StringIndex.swift
String's Index has the following layout:
┌──────────┬───────────────────╥────────────────┬──────────╥────────────────┐
│ b63:b16 │ b15:b14 ║ b13:b8 │ b7:b1 ║ b0 │
├──────────┼───────────────────╫────────────────┼──────────╫────────────────┤
│ position │ transcoded offset ║ grapheme cache │ reserved ║ scalar aligned │
└──────────┴───────────────────╨────────────────┴──────────╨────────────────┘
└──────── resilient ────────┘
Position, transcoded offset, and scalar aligned are fully exposed in the ABI.
Grapheme cache and reserved are partially resilient: the fact that there are 13
bits with a default value of `0` is ABI, but not the layout, construction, or
interpretation of those bits. All use of grapheme cache should be behind
non-inlinable function calls. Inlinable code should not set a non-zero value to
grapheme cache bits: doing so breaks back deployment as they will be interpreted
as a set cache.
- position aka `encodedOffset`: A 48-bit offset into the string's code units
- transcoded offset: a 2-bit sub-scalar offset, derived from transcoding
<resilience barrier>
- grapheme cache: A 6-bit value remembering the distance to the next grapheme
boundary.
- reserved: 7-bit for future use.
<resilience barrier>
- scalar aligned, whether this index is known to be scalar-aligned (see below)
-
position aka encodedOffset
一個48bit值,用來記錄碼位偏移量 -
transcoded offset
一個2bit的值,用來記錄字符使用的碼位數(shù)量 -
grapheme cache
一個6bit的值,用來記錄下一個字符的邊界 -
reserved
7bit的預(yù)留字段 -
scalar aligned
一個1bit的值,用來記錄標(biāo)量是否已經(jīng)對齊過
比如說,訪問當(dāng)前的“我”,當(dāng)前的encodedOffset(碼位偏移量)
為0,當(dāng)前的transcoded offset(碼位數(shù)量)
為24(3字節(jié))
“是”的encodedOffset
為24(3字節(jié)),transcoded offset
為24
“K”的encodedOffset
為48,transcoded offset
為8
所以當(dāng)我們使用了String.Index
后,我們的字符串通過encodedOffset
和transcoded offset
去取出對應(yīng)的字符串。
String.Index
的本質(zhì):
- 當(dāng)我們在構(gòu)建
String.Index
的時候,其實就是把encodedOffset
和transcoded offset
計算出來存入String.Index
中。 - 當(dāng)我們?nèi)ト∽址臅r候,會根據(jù)
String.Index
的encodedOffset
和transcoded offset
信息去取出對應(yīng)的字符串。
// Index
extension _StringGuts {
@usableFromInline
internal typealias Index = String.Index
//源碼中的startIndex通過_encodedOffset傳入0創(chuàng)建,本身startIndex就是在字符串開始地方
@inlinable @inline(__always)
internal var startIndex: String.Index {
return Index(_encodedOffset: 0)._scalarAligned
}
//源碼中的endIndex傳入count生成
@inlinable @inline(__always)
internal var endIndex: String.Index {
return Index(_encodedOffset: self.count)._scalarAligned
}
}
因此我們該怎么去取字符串的最后一個元素呢?
let str = "Hello World"
//數(shù)組越界
//print(str[str.endIndex]) //Swift/StringRangeReplaceableCollection.swift:302: Fatal error: String index is out of bounds
print(str[str.index(str.endIndex, offsetBy: -1)]) // d
那是因為源碼中的endIndex
并沒有取count -1
而是直接存入的count
導(dǎo)致的
二.Array源碼
1.Array的數(shù)據(jù)結(jié)構(gòu)
var arr = [1, 2, 3, 4, 5]
對應(yīng)的SIL代碼
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main3arrSaySiGvp // id: %2
%3 = global_addr @$s4main3arrSaySiGvp : $*Array<Int> // user: %35
%4 = integer_literal $Builtin.Word, 5 // user: %6
// function_ref _allocateUninitializedArray<A>(_:)
//創(chuàng)建一個未初始化的Array的函數(shù)
%5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
//函數(shù)傳入%4,也就是數(shù)組的大小。返回了一個元祖%6
%6 = apply %5<Int>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
%7 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 0 // user: %34
//獲取元素開始的首地址
%8 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 1 // user: %9
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*Int // users: %12, %29, %24, %19, %14
//整型1
%10 = integer_literal $Builtin.Int64, 1 // user: %11
//將1構(gòu)建成結(jié)構(gòu)體{1}
%11 = struct $Int (%10 : $Builtin.Int64) // user: %12
//將%11整型結(jié)構(gòu)體{1}存入%9
store %11 to %9 : $*Int // id: %12
%13 = integer_literal $Builtin.Word, 1 // user: %14
//%14就是%9偏移%13(1個字節(jié))
%14 = index_addr %9 : $*Int, %13 : $Builtin.Word // user: %17
%15 = integer_literal $Builtin.Int64, 2 // user: %16
%16 = struct $Int (%15 : $Builtin.Int64) // user: %17
//將%16整型結(jié)構(gòu)體{2}存入%14
store %16 to %14 : $*Int // id: %17
%18 = integer_literal $Builtin.Word, 2 // user: %19
//%19就是%9偏移%18(2個字節(jié))
%19 = index_addr %9 : $*Int, %18 : $Builtin.Word // user: %22
%20 = integer_literal $Builtin.Int64, 3 // user: %21
%21 = struct $Int (%20 : $Builtin.Int64) // user: %22
//將%21整型結(jié)構(gòu)體{3}存入%19
store %21 to %19 : $*Int // id: %22
/*
下面的邏輯就和上面一樣依次往數(shù)組存入數(shù)據(jù)
*/
...
return %37 : $Int32 // id: %38
} // end sil function 'main'
源碼查找關(guān)于allocateUninitializedArray
,在ArrayShared.swift
中
/// Returns an Array of `_count` uninitialized elements using the
/// given `storage`, and a pointer to uninitialized memory for the
/// first element.
///
/// This function is referenced by the compiler to allocate array literals.
///
/// - Precondition: `storage` is `_ContiguousArrayStorage`.
@inlinable // FIXME(inline-always)
@inline(__always)
@_semantics("array.uninitialized_intrinsic")
public // COMPILER_INTRINSIC
func _allocateUninitializedArray<Element>(_ builtinCount: Builtin.Word)
-> (Array<Element>, Builtin.RawPointer) {
let count = Int(builtinCount)
//如果count>0,創(chuàng)建內(nèi)存空間
if count > 0 {
// Doing the actual buffer allocation outside of the array.uninitialized
// semantics function enables stack propagation of the buffer.
//allocWithTailElems_1最終會調(diào)用allocObject在堆區(qū)分配內(nèi)存空間,來去存儲數(shù)組的元素
let bufferObject = Builtin.allocWithTailElems_1(
_ContiguousArrayStorage<Element>.self, builtinCount, Element.self)
//創(chuàng)建Array,返回array和第一個元素的首地址
let (array, ptr) = Array<Element>._adoptStorage(bufferObject, count: count)
return (array, ptr._rawValue)
}
// For an empty array no buffer allocation is needed.
//count=0,創(chuàng)建空類型的數(shù)組
let (array, ptr) = Array<Element>._allocateUninitialized(count)
return (array, ptr._rawValue)
}
Array.swift
中的_adoptStorage
/// Returns an Array of `count` uninitialized elements using the
/// given `storage`, and a pointer to uninitialized memory for the
/// first element.
///
/// - Precondition: `storage is _ContiguousArrayStorage`.
@inlinable
@_semantics("array.uninitialized")
internal static func _adoptStorage(
_ storage: __owned _ContiguousArrayStorage<Element>, count: Int
) -> (Array, UnsafeMutablePointer<Element>) {
let innerBuffer = _ContiguousArrayBuffer<Element>(
count: count,
storage: storage)
//返回了array和array第一個元素的首地址
return (
Array(
_buffer: _Buffer(_buffer: innerBuffer, shiftedToStartIndex: 0)),
innerBuffer.firstElementAddress)
}
-
_adoptStorage
返回了array和第一個元素的首地址
此時為什么要返回第一個元素的首地址呢?
那肯定是數(shù)組還存放有其它信息,因此需要把第一個元素的地址返回回去,完成其它操作(比如說賦值)。
通過源碼總結(jié)的Array數(shù)據(jù)結(jié)構(gòu)
-
count
存放的是數(shù)組元素的個數(shù) -
_capacityAndFlags
容量和標(biāo)志位
通過LLDB驗證
//Array:Struct{class}
//表現(xiàn)得像值類類型,其實是一個引用類型。值存放在堆區(qū)
var arr = [1, 2, 3, 4, 5]
print("end")
/*
(lldb) po withUnsafePointer(to: &arr) {print($0)}
0x00000001000102b0
0 elements
(lldb) x/8g 0x00000001000102b0
0x1000102b0: 0x0000000100720c10 0x0000000000000000
0x1000102c0: 0x0000000000000000 0x0000000000000000
0x1000102d0: 0x0000000000000000 0x0000000000000000
0x1000102e0: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000100720c10
0x100720c10: 0x00007ff84333e568 0x0000000000000003
0x100720c20: 0x0000000000000005 0x000000000000000a
0x100720c30: 0x0000000000000001 0x0000000000000002
0x100720c40: 0x0000000000000003 0x0000000000000004
(lldb) cat 0x00007ff84333e568
invalid command 'cat 0x00007ff84333e568'.
(lldb) cat address 0x00007ff84333e568
address:0x00007ff84333e568, e368full type metadata for Swift._ContiguousArrayStorage<Swift.Int> <+16> , ($ss23_ContiguousArrayStorageCySiGMf), External: NO libswiftCore.dylib.__DATA.__data +e368
(lldb)
*/
關(guān)于truncatingIfNeeded
//truncatingIfNeeded:大數(shù)轉(zhuǎn)小數(shù)時會截掉多余的二進制位
let x: Int16 = 0b0000_0011_0101_1100
let x1 = Int8(truncatingIfNeeded: x) // 0b0101_1100
/*
truncatingIfNeeded:小數(shù)轉(zhuǎn)大數(shù)
正數(shù):用0占位
負(fù)數(shù):用1占位。因為負(fù)數(shù)在計算機存的是補碼(因此用1占位)
*/
2.Array擴容
var arr = [1, 2, 3, 4, 5]
arr.append(6)
我們在創(chuàng)建數(shù)組的時候,_storage(ContiguousStorage)
中的count
就已經(jīng)固定為5了。那么我們在執(zhí)行append()
的時候,數(shù)組就需要擴容了。
探究Array擴容
,進入Array.swift
找到append
@inlinable
@_semantics("array.append_element")
public mutating func append(_ newElement: __owned Element) {
// Separating uniqueness check and capacity check allows hoisting the
// uniqueness check out of a loop.
//檢查當(dāng)前_buffer是否存在其它引用
//如果存在其它引用的話,保證Array的值類型特性。創(chuàng)建新的buffer來存儲老的數(shù)據(jù),同時新buffer的count+1
_makeUniqueAndReserveCapacityIfNotUnique()
let oldCount = _buffer.mutableCount
_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
_appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
_endMutation()
}
@inlinable
@_semantics("array.make_mutable")
internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
//是否開辟的這塊buffer是唯一引用,類似于寫時復(fù)制。
//如果不是唯一引用,那就開辟新的內(nèi)存空間來存放新的數(shù)據(jù)
if _slowPath(!_buffer.beginCOWMutation()) {
//此時不是唯一引用,創(chuàng)建新的內(nèi)存空間來存儲oldBuffer
//bufferIsUnique -> 緩存區(qū)是否唯一
//minimumCapacity -> 最小容量count + 1
//growForAppend -> 是否添加時需要擴容
_createNewBuffer(bufferIsUnique: false,
minimumCapacity: count + 1,
growForAppend: true)
}
}
/// Creates a new buffer, replacing the current buffer.
///
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
/// referenced by this array and the elements are moved - instead of copied -
/// to the new buffer.
/// The `minimumCapacity` is the lower bound for the new capacity.
/// If `growForAppend` is true, the new capacity is calculated using
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
@_alwaysEmitIntoClient
internal mutating func _createNewBuffer(
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
) {
_internalInvariant(!bufferIsUnique || _buffer.isUniquelyReferenced())
//擴容
_buffer = _buffer._consumeAndCreateNew(bufferIsUnique: bufferIsUnique,
minimumCapacity: minimumCapacity,
growForAppend: growForAppend)
}
/*
ArrayBuffer.swift中的beginCOWMutation
*/
/// Returns `true` and puts the buffer in a mutable state iff the buffer's
/// storage is uniquely-referenced.
///
/// - Precondition: The buffer must be immutable.
///
/// - Warning: It's a requirement to call `beginCOWMutation` before the buffer
/// is mutated.
@_alwaysEmitIntoClient
internal mutating func beginCOWMutation() -> Bool {
let isUnique: Bool
if !_isClassOrObjCExistential(Element.self) {
isUnique = _storage.beginCOWMutationUnflaggedNative()
} else if !_storage.beginCOWMutationNative() {
return false
} else {
isUnique = _isNative
}
#if INTERNAL_CHECKS_ENABLED
if isUnique {
_native.isImmutable = false
}
#endif
return isUnique
}
上面我們探究了_makeUniqueAndReserveCapacityIfNotUnique()
,因為Array
表現(xiàn)為值類型,為了不影響其它數(shù)據(jù),也會采用寫時復(fù)制
的策略來判斷是否需要創(chuàng)建一個新的buffer
來存數(shù)據(jù)。
接下來繼續(xù)分析append
中的_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
@inlinable
@_semantics("array.mutate_unknown")
internal mutating func _reserveCapacityAssumingUniqueBuffer(oldCount: Int) {
// Due to make_mutable hoisting the situation can arise where we hoist
// _makeMutableAndUnique out of loop and use it to replace
// _makeUniqueAndReserveCapacityIfNotUnique that precedes this call. If the
// array was empty _makeMutableAndUnique does not replace the empty array
// buffer by a unique buffer (it just replaces it by the empty array
// singleton).
// This specific case is okay because we will make the buffer unique in this
// function because we request a capacity > 0 and therefore _copyToNewBuffer
// will be called creating a new buffer.
let capacity = _buffer.mutableCapacity
_internalInvariant(capacity == 0 || _buffer.isMutableAndUniquelyReferenced())
//如果添加元素后的count大于容量,此時就需要創(chuàng)建新的內(nèi)存空間來存數(shù)據(jù),也就是擴容
if _slowPath(oldCount + 1 > capacity) {
_createNewBuffer(bufferIsUnique: capacity > 0,
minimumCapacity: oldCount + 1,
growForAppend: true)
}
}
- 如果oldCount + 1大于容量,此時的數(shù)組就需要擴容,也就是執(zhí)行
_createNewBuffer
通過_createNewBuffer
研究擴容原理,也就是_buffer._consumeAndCreateNew函數(shù)
/// Creates and returns a new uniquely referenced buffer which is a copy of
/// this buffer.
///
/// This buffer is consumed, i.e. it's released.
@_alwaysEmitIntoClient
@inline(never)
@_semantics("optimize.sil.specialize.owned2guarantee.never")
internal __consuming func _consumeAndCreateNew() -> _ArrayBuffer {
return _consumeAndCreateNew(bufferIsUnique: false,
minimumCapacity: count,
growForAppend: false)
}
/// Creates and returns a new uniquely referenced buffer which is a copy of
/// this buffer.
///
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
/// referenced and the elements are moved - instead of copied - to the new
/// buffer.
/// The `minimumCapacity` is the lower bound for the new capacity.
/// If `growForAppend` is true, the new capacity is calculated using
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
///
/// This buffer is consumed, i.e. it's released.
@_alwaysEmitIntoClient
@inline(never)
@_semantics("optimize.sil.specialize.owned2guarantee.never")
internal __consuming func _consumeAndCreateNew(
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
) -> _ArrayBuffer {
//通過_growArrayCapacity,獲取新的容量
let newCapacity = _growArrayCapacity(oldCapacity: capacity,
minimumCapacity: minimumCapacity,
growForAppend: growForAppend)
let c = count
_internalInvariant(newCapacity >= c)
let newBuffer = _ContiguousArrayBuffer<Element>(
_uninitializedCount: c, minimumCapacity: newCapacity)
if bufferIsUnique {
// As an optimization, if the original buffer is unique, we can just move
// the elements instead of copying.
let dest = newBuffer.firstElementAddress
dest.moveInitialize(from: mutableFirstElementAddress,
count: c)
_native.mutableCount = 0
} else {
_copyContents(
subRange: 0..<c,
initializing: newBuffer.mutableFirstElementAddress)
}
return _ArrayBuffer(_buffer: newBuffer, shiftedToStartIndex: 0)
}
關(guān)于_growArrayCapacity
@inlinable
internal func _growArrayCapacity(_ capacity: Int) -> Int {
//擴容方式為2倍
return capacity * 2
}
@_alwaysEmitIntoClient
internal func _growArrayCapacity(
oldCapacity: Int, minimumCapacity: Int, growForAppend: Bool
) -> Int {
if growForAppend {
if oldCapacity < minimumCapacity {
// When appending to an array, grow exponentially.
return Swift.max(minimumCapacity, _growArrayCapacity(oldCapacity))
}
return oldCapacity
}
// If not for append, just use the specified capacity, ignoring oldCapacity.
// This means that we "shrink" the buffer in case minimumCapacity is less
// than oldCapacity.
return minimumCapacity
}
數(shù)組的擴容方式:2倍擴容
比如說var arr = [1, 2, 3, 4, 5]
,這里的初始容量就為count * 2 = 10
。當(dāng)count為11(數(shù)據(jù)量超過10)的時候,此時就需要擴容。新的數(shù)組容量為count * 2 = 22
。然后依次類推
至于,數(shù)組在添加元素的時候采用的寫時復(fù)制
原理,有興趣的也可以通過代碼及LLDB去驗證一下。
三.Moya源碼
直接借用Moya
官網(wǎng)上的一張圖,我們?nèi)粘6紩途W(wǎng)絡(luò)打交道不管是使用AFN
還是Alamofire
,其實都是封裝了URLSession
,不同讓我們使用官方繁瑣的API。
久而久之我們就會發(fā)現(xiàn)項目中散落著和AFN
、Alamofire
相關(guān)代碼,不便于統(tǒng)一管理,而且很多代碼內(nèi)容都是重復(fù)的,于是我們就會新建一個中間層Network layer
來統(tǒng)一管理我們的AFN
、Alamofire
。
我們僅僅希望我們的App只和我們的Network layer
打交道,不同關(guān)心底層使用的是哪個三方網(wǎng)絡(luò)庫,即使進行遷移,也應(yīng)該對我們的上層業(yè)務(wù)邏輯毫無變化,因此我們都是通過Network layer
來耦合業(yè)務(wù)邏輯的。
但是因為抽象的顆粒度不夠,我們往往寫著寫著就會出現(xiàn)越過Network layer
,直接和我們的三方網(wǎng)絡(luò)庫打交道,這樣就違背了我們的設(shè)計原則。
Moya
就是對網(wǎng)絡(luò)業(yè)務(wù)邏輯的抽象,我們只需遵循相關(guān)協(xié)議,就可以發(fā)起網(wǎng)絡(luò)請求,不用關(guān)心底層細(xì)節(jié)
1.Moya的模塊
-
Requst模塊
請求的模塊 -
Provider模塊
發(fā)起請求的模塊 -
Response模塊
請求響應(yīng)的模塊
2.Moya流程圖
TargetType
/// The protocol used to define the specifications necessary for a `MoyaProvider`.
public protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: Moya.Method { get }
/// Provides stub data for use in testing. Default is `Data()`.
var sampleData: Data { get }
/// The type of HTTP task to be performed.
var task: Task { get }
/// The type of validation to perform on the request. Default is `.none`.
var validationType: ValidationType { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
public extension TargetType {
/// The type of validation to perform on the request. Default is `.none`.
var validationType: ValidationType { .none }
/// Provides stub data for use in testing. Default is `Data()`.
var sampleData: Data { Data() }
}
第一步創(chuàng)建一個遵守TargetType
協(xié)議的枚舉,這個過程中我們完成網(wǎng)絡(luò)請求的基本配置
EndPoint
/// Class for reifying a target of the `Target` enum unto a concrete `Endpoint`.
/// - Note: As of Moya 11.0.0 Endpoint is no longer generic.
/// Existing code should work as is after removing the generic.
/// See #1529 and #1524 for the discussion.
open class Endpoint {
public typealias SampleResponseClosure = () -> EndpointSampleResponse
/// A string representation of the URL for the request.
public let url: String
/// A closure responsible for returning an `EndpointSampleResponse`.
public let sampleResponseClosure: SampleResponseClosure
/// The HTTP method for the request.
public let method: Moya.Method
/// The `Task` for the request.
public let task: Task
/// The HTTP header fields for the request.
public let httpHeaderFields: [String: String]?
public init(url: String,
sampleResponseClosure: @escaping SampleResponseClosure,
method: Moya.Method,
task: Task,
httpHeaderFields: [String: String]?) {
self.url = url
self.sampleResponseClosure = sampleResponseClosure
self.method = method
self.task = task
self.httpHeaderFields = httpHeaderFields
}
...
}
public typealias EndpointClosure = (Target) -> Endpoint
@escaping EndpointClosure = MoyaProvider.defaultEndpointMapping
//將Target轉(zhuǎn)化為EndPoint
final class func defaultEndpointMapping(for target: Target) -> Endpoint {
Endpoint(
url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers
)
}
第二步通過endpointClosure
生成一個EndPoint
。實際上EndPoint
就是TargetType
的再一次包裝
RequstClosure
public typealias RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void
//通過Endpoint構(gòu)建RequstClosure
//也就是在判斷URLRequest是否構(gòu)建成功
final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
do {
let urlRequest = try endpoint.urlRequest()
closure(.success(urlRequest))
} catch MoyaError.requestMapping(let url) {
closure(.failure(MoyaError.requestMapping(url)))
} catch MoyaError.parameterEncoding(let error) {
closure(.failure(MoyaError.parameterEncoding(error)))
} catch {
closure(.failure(MoyaError.underlying(error, nil)))
}
}
根據(jù)Endpoint
構(gòu)建URLRequest
,如果發(fā)生錯誤,返回錯誤的數(shù)據(jù)MoyaError
Provider
/// Designated request-making method. Returns a `Cancellable` token to cancel the request later.
@discardableResult
open func request(_ target: Target,
callbackQueue: DispatchQueue? = .none,
progress: ProgressBlock? = .none,
completion: @escaping Completion) -> Cancellable {
let callbackQueue = callbackQueue ?? self.callbackQueue
return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}
func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable {
let endpoint = self.endpoint(target)
let stubBehavior = self.stubClosure(target)
//取消標(biāo)識
let cancellableToken = CancellableWrapper()
// Allow plugins to modify response
let pluginsWithCompletion: Moya.Completion = { result in
let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) }
completion(processedResult)
}
if trackInflights {
var inflightCompletionBlocks = self.inflightRequests[endpoint]
inflightCompletionBlocks?.append(pluginsWithCompletion)
self.internalInflightRequests[endpoint] = inflightCompletionBlocks
if inflightCompletionBlocks != nil {
return cancellableToken
} else {
self.internalInflightRequests[endpoint] = [pluginsWithCompletion]
}
}
let performNetworking = { (requestResult: Result<URLRequest, MoyaError>) in
//如果已經(jīng)取消,直接調(diào)用取消的回調(diào)。return
if cancellableToken.isCancelled {
self.cancelCompletion(pluginsWithCompletion, target: target)
return
}
var request: URLRequest!
switch requestResult {
case .success(let urlRequest):
request = urlRequest
case .failure(let error):
pluginsWithCompletion(.failure(error))
return
}
//網(wǎng)絡(luò)響應(yīng)回調(diào) -> Response
//public typealias Completion = (_ result: Result<Moya.Response, MoyaError>) -> Void
let networkCompletion: Moya.Completion = { result in
if self.trackInflights {
self.inflightRequests[endpoint]?.forEach { $0(result) }
self.internalInflightRequests.removeValue(forKey: endpoint)
} else {
pluginsWithCompletion(result)
}
}
cancellableToken.innerCancellable = self.performRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior)
}
//執(zhí)行閉包
requestClosure(endpoint, performNetworking)
//返回取消標(biāo)識,可以使用該標(biāo)識取消任務(wù)
return cancellableToken
}
通過之前創(chuàng)建的URLRequest
發(fā)起網(wǎng)絡(luò)請求request
。回調(diào)Response
信息,完成網(wǎng)絡(luò)請求
四.高階函數(shù)
高階函數(shù)的本質(zhì)也是函數(shù)
- 接受函數(shù)或者是閉包作為參數(shù)
- 返回值是一個函數(shù)或者是閉包
1.map
map函數(shù)
作用于Collection
中的每一個元素,然后返回一個新的Collection
let arr = ["APPLE", "PEAR", "BANANA"]
//需求將每一個元素的大寫轉(zhuǎn)化為小寫
print(arr.map({$0.lowercased()})) // ["apple", "pear", "banana"]
2.flatMap
與Map
的區(qū)別在于會將Sequence
中的元素壓平
。也就是如果是一個多維數(shù)組,會返回一個壓平
后的Sequence
,也就是一個一維數(shù)據(jù)
let numArr = [[1, 2, 3, 4], [5, 6]]
print(numArr.map({$0})) // [[1, 2, 3, 4], [5, 6]]
print(numArr.flatMap({$0})) // [1, 2, 3, 4, 5, 6]
3.compactMap
當(dāng)轉(zhuǎn)化閉包返回可選值并且你期望得到的結(jié)果為非可選值的序列時,使用compactMap
let numArr = [1, 2, nil, 3, 4, 5, 6, nil]
print(numArr.compactMap({$0})) // [1, 2, 3, 4, 5, 6]
4.filter
filter
就是Sequence
中默認(rèn)提供的方法,允許調(diào)用者傳入一個閉包來過濾集合中的元素
let numArr = [1, 2, 3, 4, 5, 6]
print(numArr.filter({$0 < 3})) // [1, 2]
5.reduce
@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
傳入一個Result
參數(shù)和閉包表達(dá)式
返回Result
閉包表達(dá)式
返回值的類型與Result
一致
let numArr = [1, 2, 3, 4, 5, 6]
//初始值10加上集合元素累加值
let result = numArr.reduce(10, +) // 31
進入源碼SequenceAlgorithms.swift
找到reduce
@inlinable
public func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult:
(_ partialResult: Result, Element) throws -> Result
) rethrows -> Result {
//創(chuàng)建累加器
var accumulator = initialResult
//便利當(dāng)前的序列Sequence
for element in self {
//執(zhí)行傳入的閉包表達(dá)式,返回的Result
//傳入累加器和element
accumulator = try nextPartialResult(accumulator, element)
}
//返回累加器
return accumulator
}
使用reduce實現(xiàn)map
//1.使用reduce實現(xiàn)map
extension Sequence {
func customMap(_ transform: (Element) -> Element) -> [Element] {
// return reduce([Element]()) {
// var arr = $0
// arr.append(transform($1))
// return arr
// }
//這里也可以使用傳地址的方式
//into -> 將地址傳入進去
return reduce(into: [Element]()) {
$0.append(transform($1))
}
}
}
let arr = ["APPLE", "PEAR", "BANANA"]
print(arr.customMap({$0.lowercased()})) // ["apple", "pear", "banana"]
使用reduce找到最大值
//通過reduce找出數(shù)組中的最大值
extension Collection {
func findMaxValue() -> Element where Element: Comparable{
return reduce(into: self[startIndex]) {
$0 = $0 > $1 ? $0 : $1
}
}
}
let numArr = [1, 2, 3, 4, 5, 6]
print(numArr.findMaxValue()) // 6
reduce實現(xiàn)逆序
//通過reduce實現(xiàn)逆序
extension Sequence {
func customReverse() -> [Element] {
return reduce(into: [Element]()) {
$0 = [$1] + $0
//向數(shù)組index = 0的地方插入$1也是可以的
//$0.insert($1, at: 0)
}
}
}
let numArr = [1, 2, 3, 4, 5, 6]
print(numArr.customReverse()) // [6, 5, 4, 3, 2, 1]
使用reduce求出數(shù)組中奇數(shù)的和、以及偶數(shù)的乘積
//使用reduce求出數(shù)組中奇數(shù)的和、以及偶數(shù)的乘積
let result = numArr.reduce(into: (0,1)) {
if $1 % 2 == 0 {
$0.1 *= $1
}else {
$0.0 += $1
}
}
print(result) //(9, 48)
使用reduce求一個數(shù)組中偶數(shù)的平方和
//使用reduce求一個數(shù)組中偶數(shù)的平方和
let result = numArr.reduce(into: 0) { $0 += $1 % 2 == 0 ? $1 * $1 : 0 }
print(result) // 56