Skip to content

Latest commit

ย 

History

History
298 lines (178 loc) ยท 5.26 KB

UsingCollectionsEffectively.md

File metadata and controls

298 lines (178 loc) ยท 5.26 KB

@ WWDC 18

Collections Store Elements

// Declaraton of Collection
protocol Collection: Sequence {
  associatedtype Element
  associatedtype Index: Comparable
  
  subscrippt(position: Index) -> Element { get }
  
  var startIndex: Index { get }
  var endIndex: Index { get }
  
  func index(after i: Index) -> Index
}

Every Other Element

extension Collection {
  func everyOther(_ body: (Element) -> Void) {
    let start = self.startIndex
    let end = self.endIndex
    
    var iter = start
    while iter != end {
      body(self[iter])
      let next = index(after: iter)
      if next == end { break }
      iter = index(after: next)
    }
  }
}

(1...10).everyOther { print($0) }

Collections Protocol Hierarchy

image

Indices

  • Each collection defines its own index
  • Must be Comparable
  • Think of these as opaque

Find the Second Element of a Collection

extension Collection {
  var secnod: Element? {
    guard self.startIndex != self.endIndex else { return nil }
    let index = self.index(after: self.startIndex)
    guard index != self.endIndex else { return nil }
    return self[index]
  }
}

Forming a Slice

let array = [1, 2, 3, 4, 5]

let subarray = array.dropFirst()
let secondIndex = array.index(after: array.startIndex)

print(secondIndex == subarray.startIndex)

Find the Second Element of a Collection ver.2

var second: Element? {
  return self.dropFirst().first
}

Slices

  • Produce Collection- like peers of original collection

  • Array -> ArraySlice

  • String -> Substring

  • Set -> Slice

  • Data -> Data

  • Range -> Range

Slices Keep Underlying Storage Alivee

extension Array {
  var firstHalf: ArraySliice<Element> {
    return self.droppLast(self.count / 2)
  }
}

var array = [1, 2, 3, 4, 5, 6, 7, 8]
var firstHalf = array.firstHalf
array = []

print(firstHalf.first!) // 1

let copy = Array(firstHalf)
firstHalf = []
print(copy.first!) // 1

Eager Functions

์‹น ๋‹ค ๊ณ„์‚ฐํ•จ

let items = (1...4000).map { $0 * 2 }.filter { $0 < 10 }

Lazy Functions

ํ•„์š”ํ•œ ๊ฑฐ์„œ ์œ„์ฃผ๋กœ

let items = (1...4000).lazy.map { $0 * 2 }.filter { $0 < 10 }
items.first

Advice: When to Be Lazy?

  • Chained computation
  • Only need part of a result (์ „์ฒด ๊ฒฐ๊ณผ ํ•„์š” ์—†์„ ๋•Œ!)
  • No side effects
  • Avoid API boundaries

์™œ collection ๋‹ค๋ฃจ๋ฉด์„œ crash ๋‚˜๋ƒ!

  • collection์„ mutating ์‹œํ‚ค๊ณ  ์žˆ์ง€ ์•Š๋Š” ์ง€ ์ฒดํฌํ•ด๋ด๋ผ
  • ์—ฌ๋Ÿฌ thread์—์„œ collections์„ ์ ‘๊ทผํ•˜๊ณ  ์žˆ์ง€๋Š” ์•Š์€๊ฐ€?

Advice: Indices and Slices

  • Use caution when keeping indices/slices
  • Mutation invalidates
  • Calculate only as needed ๋”ฑ ํ•„์š”ํ•  ๋•Œ๋งŒ ๊ณ„์‚ฐํ•ด์„œ ์ ‘๊ทผํ•ด๋ผ

Multithreaded Mutable Collections

  • Our collections optimized for single-threaded access
  • This is a Good Thing
  • Undefined behavior without mutual exclusion
// Thread-Unsafe Ppractices
var sleepingBears = [String]()
let queue = DispatchQueue.global()

queue.async { sleepingBears.append("Grandpa") }
queue.async { sleepingBears.append("Cub") }

// Avoid concurrent mutation
let queue = DispatchQueue(label: "Bear-Cave")

Advice: Multithreading

  • Prefer state accessible from a single thread
  • When this is not possible:
    • Ensure mutual exclusion
    • Use TSAN

Advice: Prefer Immutable Collection

  • Easier to reason about data that can't change
  • Less surface area for bugs
  • Emulate mutation with slices and lazy
  • The compiler will help you

Advice: Forming New Collections

  • Use capacity hints if possible
Array.reserveCapacity(_:)
Set(minimumCapacity:)
Dictionary(minimumCapacity:)

Bridging between Objective-C and Swift

  • Converts between runtime types
  • Bidirectional
  • Bridging of collections
    • Is necessary
    • Can be cheap, but is never free

Two Kinds of Bridging

  • Eager when element types are bridged
  • Otherwise lazy
    • Bridged on first use

Identifying Bridging Problems

  • Measure your performance with Instruments
  • Especially inside loops at language boundaries
  • Look for hotspots like:
    • _unconditionallyBridgeFromObjectiveC
    • bridgeEverything
let story = NSString(string: "๋ญ๋ผ๋ญ๋ผ ๋ญ๋ผ๋ญ๋ผ๋ญ๋ผ๋Ÿฌ๋ผใ…๋ผ๋ผ๋ญ๋ผ๋ผ๋ผใ…")

let text = NSMutableAttributedString(string: story)

// ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „ (Bridging overhead๊ฐ€ ํผ)
let range = text.string.range(of: "Brown")!
let nsrange = NSRange(range, in: text.string)

// ๋‘ ๋ฒˆ์งธ ๋ฒ„์ „
let string = text.string // ์—ฌ๊ธฐ์„œ ์˜ค๋ฒ„ํ—ค๋“œ ํ•œ ๋ฒˆ ๋ฐœ์ƒ
let range = text.string.range(of: "Brown")!
let nsrange = NSRange(range, in: text.string)

// ์„ธ ๋ฒˆ์งธ ๋ฒ„์ „
let string = text.string as NSString
let nsrange = string.range(of: "Brown")

text.addAttribute(.forgroundColor, value: NSColor.brown, range: nsrange)

Advice: When to Use Foundation Collections

  • You need reference semantics
  • You are working with known proxies
    • NSAttributedString.string
    • Core Data Managed Objects
  • You've measured and identified bridging costs

Now It's Your Turn

  • Explore your existing collections
  • Measure your code
  • Audit your mutable state
  • Gain mastery in Playgrounds