8/10/16

iOS macOS tvOS watchOS

// Swift
    
    # swift repl
      :version  // 5.7.2
      :quit
      
      swift package --version  # Apple Swift Package Manager - Swift 5.7.1
      swift package --help
      swift package init
      swift package  generate-xcodeproj
      swift build
      swift test
      swift test --filter "FooTests"
      swift test --filter "BarTests"
      swift test --filter "testFoo"
      
      
      import Darwin
      
      M_PI                 // Double = 3.1415926535897931
      log2(100 as Float)   // 6.64385605
      log2(100 as Double)  // 6.6438561897747244
      
      
      "Foo".lowercased()
      "foo".count           // 3
      "foo".index(of: "f")  // String.Index?
      
      
      let s = "foo bar"
      
      if let idx: String.Index = s.firstIndex(of: "f") {
          s.distance(from: s.startIndex, to: idx) |> p   // Int = 0
      }
      
      
      extension String {  // se
          
          // let c: Character? = s[0]
          // 
          subscript(idx: Int) -> Character? {
              
              guard idx >= 0 && idx < count
              else {
                  return nil
              }
              
              return self[index(startIndex, offsetBy: idx)]
          }
          
          // let idx: Int? = s.index("f")
          // 
          func index(_ c: Character) -> Int? {
              
              guard let idx: String.Index = firstIndex(of: c)
              else {
                  return nil
              }
              
              let id: Int = distance(from: startIndex, to: idx)
              print("\"\(self)\" idx: \(c) = \(id)")
              
              return id
          }
          
      }
      
      let c: Character? = s[0]                       // se
      let cs: Array<Character?> = [c, s[1], s[100]]
      print(cs)                                      // [Optional("f"), Optional("o"), nil]

      if let idx: Int = s.index("f") {               // se
          print("DB idx = \(idx)")
      }
            
      
      // replace
      s.replacingOccurrences(of: "o", with: "*")  // f** bar
      s.replacingOccurrences(of: ",", with: "").replacingOccurrences(of: " ", with: "_").lowercased()
      
      
      let range: Range<String.Index> = Range(uncheckedBounds: (lower: s.index(s.startIndex, offsetBy: 0),
                                                               upper: s.index(s.endIndex,   offsetBy: 0)))
      
      s.replacingOccurrences(of:      "o",
                             with:    "*",
                             options: NSString.CompareOptions.literal,
                             range:   range)
      
      
      s.substring(from: s.index(s.startIndex, offsetBy: 0))  // foo bar
      s.substring(from: s.index(s.startIndex, offsetBy: 1))  // oo bar
      let startIdx =    s.index(s.startIndex, offsetBy: 4)
      s.substring(from: startIdx)                            // bar
      
      s.substring(to: s.index(s.startIndex, offsetBy: 0))    //
      s.substring(to: s.index(s.startIndex, offsetBy: 1))    // f, first char, string
      s.substring(to: s.index(s.startIndex, offsetBy: 2))    // fo
      let endIdx =    s.index(s.startIndex, offsetBy: 3)
      s.substring(to: endIdx)                                // foo
      
      String(s[s.startIndex ..< endIdx])                     // foo

      let range: Range<String.Index> = Range(uncheckedBounds: (lower: s.index(s.startIndex, offsetBy:  1),
                                                               upper: s.index(s.endIndex,   offsetBy: -1)))

      s.substring(with: range)                               // oo ba

      let c: Character = s[s.startIndex]  // f, first char


      let p = CGPoint(x:1, y:2)  // CGPointMake(1, 2)


      Locale.current.identifier    // en_US
      Locale.current.languageCode  // en
      Locale.current.regionCode    // US


      ...  closed range operator
      ..<  half-open range

      for x in 0..<1  {  // 0
      for x in 0...1  {  // 0 1
          x |> p
      }

      for x in 1...8 {
          x % 4 |> p  // 1 2 3 0 1 2 3 0  mod
      }

      for _ in 1...10 { "." |> p }
      String(repeating: ".", count: 10)  // ..........


      for x in 1...3 { "\(x)." |> p }  // 1.
                                       // 2.
                                       // 3.

      for x in stride(from: 10, through: 100, by: 10 ) { "\(x) " |> p }  // 10 20 30 40 50 60 70 80 90 100  closed
      for x in stride(from: 10, to:      100, by: 10 ) { "\(x) " |> p }  // 10 20 30 40 50 60 70 80 90      half-open


      let df = DateFormatter()
      df.dateFormat = "MM-dd-yyyy"
      let d1 = df.date(from: "01-08-1947")!
      let d2 = df.date(from: "01-10-2016")!  // db

      let cal = Calendar.current
      cal.dateComponents([.day],  from: d1, to: d2).day   // 25204
      cal.dateComponents([.year], from: d1, to: d2).year  // 69


      let nf = NumberFormatter()
      nf.numberStyle = .currency
      let d = Double(0.99 + 42.00)
      let n = NSNumber(value: d)
      nf.string(from: n)            // $42.99


      import GLKit

      for x in stride(from:0, through:360, by:90) { String(format:"%3d degrees = %.2f radians", x, GLKMathDegreesToRadians(Float(x))) |> p }
          
            0 degrees = 0.00 radians
           90 degrees = 1.57 radians
          180 degrees = 3.14 radians
          270 degrees = 4.71 radians
          360 degrees = 6.28 radians
  
  
  //--------------------------------
    swiftui
    import SwiftUI
      
      // ContentView.swift
      
      struct ContentView: View {
          
          var body: some View {
              let _ = "DEBUG body" |> p
              
              VStack {
                  Text("Hello SwiftUI")
                  
                  Button("Test") {
                      "DEBUG button tap" |> p
                  }
              }
              .padding()
              .frame(maxWidth: CGFloat.infinity, maxHeight: .infinity)
              .onAppear() {
                  "DEBUG onAppear()" |> p
              }
          }
          
      }
      
      
      // App.swift
      
      @main
      struct xApp: App {
          
          var body: some Scene {
              WindowGroup {
                  let _ = logProcessInfo()
                  ContentView()
              }
          }
          
      }
      
      
      func logProcessInfo() {
          let pi = ProcessInfo.processInfo
          
"""
process info
id:   \(pi.processIdentifier)
name: \(pi.processName)
args: \(pi.arguments)
cmd:  ps aux \(pi.processIdentifier)
"""
          |> log
      }
      
      
      // output
      process info
      id:   4200
      name: x
      args: ["/Users/a/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug/x.app/Contents/MacOS/x", "-NSDocumentRevisionsDebugMode", "YES"]
      cmd:  ps aux 4200
      
      
  //--------------------------------
    binary
      
      String(42, radix: 2)  // 101010  print int to binary
      
      let b1:  UInt = 0b1                                                                 //                    1  0x1
      let b63: UInt = 0b1000000000000000000000000000000000000000000000000000000000000000  //  9223372036854775808  0x8000000000000000
      let ba:  UInt = 0b1111111111111111111111111111111111111111111111111111111111111111  // 18446744073709551615  0xffffffffffffffff
      
           ba.nonzeroBitCount       // 64
                                         
          0b0.leadingZeroBitCount   // 64
          0b0.trailingZeroBitCount  // 64
                                         
           b1.leadingZeroBitCount   // 63
           b1.trailingZeroBitCount  //  0
                                         
       0b1111.leadingZeroBitCount   // 60
       0b1000.leadingZeroBitCount   // 60
                                         
       0b1000.trailingZeroBitCount  //  3
       0b1100.trailingZeroBitCount  //  2
       0b1110.trailingZeroBitCount  //  1
      0b11000.trailingZeroBitCount  //  3
      
      
      // c bit funcs
      // 1-based, max input: Int64
      // 
      flsll(0b1100)  // 4  find last bit set    most sig
      ffsll(0b1100)  // 3  find first bit set  least sig
      
      
      let a  = 0b0100000       // 32     100000  0x20
      let b  = 0b1000000       // 64    1000000  0x40
      let ab = a | b           // 96    1100000  0x60
                                        
      var b  = 0b1                      
          b |= (1 << 3)        //  9       1001  0x9
                                        
      let b: UInt = (1 << 63)  // 9223372036854775808
  
  
  //--------------------------------
    hex
      
      0xa   // 10
      0x2a  // 42
      
      String(42, radix: 16)  // print int to hex
      
      let h: UInt = 0xffffffffffffffff  // 18446744073709551615
      
      dec hex
        0   0
        1   1
        2   2
        3   3
        4   4
        5   5
        6   6
        7   7
        8   8
        9   9
       10   A
       11   B
       12   C
       13   D
       14   E
       15   F
    
    
  //--------------------------------
    int range
                              min                   max
      Int8                   -128                   127
      Int16                -32768                 32767
      Int32           -2147483648            2147483647
      Int64  -9223372036854775808   9223372036854775807  // Int
                                    
      UInt8                     0                   255
      UInt16                    0                 65535
      UInt32                    0            4294967295
      UInt64                    0  18446744073709551615  // UInt
      
      
  //--------------------------------
    scope
      
      do {
          var a = 0
      }
      
      ({
          var a = 0
      })()
      
      
  //--------------------------------
    closures
      
      let c = { }
      let c = { [weak self] in
                 self?.foo()
              }
      
      let c:  () -> Void  = { }  // () -> ()
      let c: (() -> Void) = { }
      
      let c: (String) -> Void = { s in }
      
      
  //--------------------------------
    pipe operator
      
      infix operator |> : LogicalConjunctionPrecedence
      
      public func |> <T, U>(lhs: T, rhs: (T) -> U) -> U {
          rhs(lhs)
      }
      
      
     // generic pipe print wrapper
     // "foo"             |> p
     // #function         |> p
     // [0, 7]            |> p  // [0, 7]
     // Array<Int>(0...3) |> p  // [0, 1, 2, 3]
     // 
     func p<T>(_ data: T) {
         Swift.print(data)
     }
     
     // generic pipe log
     // 
     func log<T>(_ data: T) {
         "\(Date().formatDateTimeMilliseconds()): \(data)" |> p
     }
     
     
  //--------------------------------
    xcode: show errors and warnings on build only
    
    disable "show live issues"
    > prefs > gen > show live issues
    
    dismiss errors and warnings
    > prod > clear all issues
    
    
      // alt no unused warn func
      // param must be initd
      // 
      // a       |> nuwarn
      // (a, b)  |> nuwarn
      // closure |> nuwarn
      // 
      func nuwarn<T>(_ x: T) {
          // 
      }
    
    
  //--------------------------------
    enums
      
      enum WildThing {
          case dragon, rous, troll
      }
      
      let wildThing = WildThing.rous
      
      wildThing |> p  // rous  app.WildThing.rous
      
      
  //--------------------------------
    console input args

      // app "foo, bar" 42
      if CommandLine.argc == 3 {  // arg count

          //                 CommandLine.arguments[0]  // app path
          let arg1: String = CommandLine.arguments[1]
          let arg2: String = CommandLine.arguments[2]
      }


  //--------------------------------
    app delegate

      // AppKit
      // NSApp = NSApplication.shared()
      let appDelegate = NSApp.delegate
      let wc = NSApp.mainWindow?.windowController

      // UIKit
      let appDelegate = UIApplication.shared.delegate
      AppDelegate * appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;  // OC

      // WatchKit
      let appDelegate = WKExtension.shared().delegate


  //--------------------------------
    user defaults, Swift 3

      let keyPeerID = "peerID"

      saveData(peerIDFromUser(), key:keyPeerID)


      func peerIDFromUser() -> MCPeerID {
          let peerID = "foo"  // UIDevice.current.name

          return MCPeerID(displayName: peerID)
      }

      func saveData(_ data: Any, key: String) {
          let archive: Data = NSKeyedArchiver.archivedData(withRootObject: data)
          UserDefaults.standard.set(archive, forKey: key)
      }

      func getPeerID() -> MCPeerID? {
          var peerID: MCPeerID?

          let data: Data? = UserDefaults.standard.data(forKey: keyPeerID)
          if data != nil {
              peerID = NSKeyedUnarchiver.unarchiveObject(with: data!) as? MCPeerID
          }

          return peerID
      }


      let defaults = UserDefaults.standard.dictionaryRepresentation()

      for key in defaults.keys.sorted(by: { $0.caseInsensitiveCompare($1) == .orderedAscending }) {
          print("\(key) = \(defaults[key]!)")
      }


      // OC
      NSDictionary * defaults = [NSUserDefaults.standardUserDefaults dictionaryRepresentation];

      for (NSString * key in [defaults.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) {
          NSLog(@"%@ = %@", key, [defaults valueForKey:key]);
      }


  //--------------------------------
    file and directory comparisons, Swift 3

      let file1 = "~/temp.txt"
      let file2 = "/volumes/vol1/temp.txt"

      let result = fileContentsEqual(file1, file2)
      if result == .equal {
          print("equal\n")
      }
      else if result == .notEqual {
          print("NOT equal\n")
      }


      // alt file path build
      let file1Base = "/volumes/vol1/"
      let file2Base = "/volumes/vol2/"

      let root = "temp/"
      let file = "foo"

      let file1 = "\(file1Base)\(root)\(file)"
      let file2 = "\(file2Base)\(root)\(file)"


      enum FileContentsEqualResult {

          case equal, notEqual, error
      }


      func filesExist(_ filePath1: String, _ filePath2: String) -> Bool {
          let file1Exists = FileManager().fileExists(atPath: filePath1)
          let file2Exists = FileManager().fileExists(atPath: filePath2)

          if !file1Exists { print("error, file 1 does not exist: \(filePath1)") }
          if !file2Exists { print("error, file 2 does not exist: \(filePath2)") }

          return file1Exists && file2Exists
      }

      func fileContentsEqual(_ filePath1: String, _ filePath2: String) -> FileContentsEqualResult {
          print("comparing: \(filePath1)\n           \(filePath2)\n")

          guard filesExist(filePath1, filePath2)                 else { return .error }
          guard filePath1.lowercased() != filePath2.lowercased() else { print("error, file paths are the same"); return .error }

          return FileManager().contentsEqual(atPath: filePath1, andPath: filePath2) ? .equal : .notEqual
      }


  //--------------------------------
    enumerate directories
    
      import Foundation

      let dir: URL = URL(fileURLWithPath: "/users/sn/documents", isDirectory: true)

      let dirEnumerator: FileManager.DirectoryEnumerator = FileManager().enumerator(at: dir,
                                                                                    includingPropertiesForKeys: [],
                                                                                    options: [],
                                                                                    errorHandler: { (file: URL, error: Error) -> Bool in

                                                                                                      print("error: \(error) at file: \(file.path)")

                                                                                                      return true
                                                                                                  })!

      let df = DateFormatter()
      df.dateFormat = "yyyy-MM-dd HH:mm:ss"

      func date(_ date: Date) -> String {
          return df.string(from: date)
      }

      func fileSize(_ file: URL) -> String {
          let attrs: [FileAttributeKey : Any] = try! FileManager().attributesOfItem(atPath: file.path)
          let sizeAttr: Any = attrs[FileAttributeKey.size]
          let size = (sizeAttr as! NSNumber)

          return ByteCountFormatter.string(fromByteCount: size.int64Value,
                                           countStyle: ByteCountFormatter.CountStyle.file)
      }


      let fileResourceKeys: Set<URLResourceKey> = [.contentModificationDateKey,
                                                   .isDirectoryKey,
                                                   .isPackageKey,
                                                   .isRegularFileKey]

      for (index, object) in dirEnumerator.enumerated() {

          let file = object as! URL

          do {

              let resource: URLResourceValues = try file.resourceValues(forKeys: fileResourceKeys)

              if resource.isRegularFile! {
                  print("\(index). \(file.path), size: \(fileSize(file)), mod: \(date(resource.contentModificationDate!))")
              }
              else if resource.isDirectory! {
                  print("\(index). dir: \(file)")
              }
              else if resource.isPackage! {
                  print("\(index). package: \(file)")
              }
              else {
                  print("\(index). unknown file type: \(file)")
              }

          } catch {
              print(error)
          }

      }


  //--------------------------------
    tax rate solver, Swift 3
    - solve tax rate from subtotal and tax
      solve total from subtotal and tax rate
      solve subtotal from total and tax rate

      import Foundation

      protocol Singleton: class {

          static var sharedInstance: Self { get }
      }

      final class Formatter: Singleton {

          static let sharedInstance = Formatter()  // calls init

          private let nfDollar            = NumberFormatter()
          private let nfDollarApproximate = NumberFormatter()
          private let nfDecimal           = NumberFormatter()

          private init() {  // runs once
              nfDollar.numberStyle = .currency
              nfDollar.minimumFractionDigits = 2
              nfDollar.maximumFractionDigits = 2

              nfDollarApproximate.numberStyle = .currency
              nfDollarApproximate.maximumFractionDigits = 4

              nfDecimal.numberStyle = .decimal
              nfDecimal.maximumFractionDigits = 8
          }


          // format dollar
          static func dollar(_ val: Decimal) -> String {
              let sval = String(describing: val) // convert decimal to double via string
              let dval = Double(sval)!
              let nval = NSNumber(value: dval)
              return sharedInstance.nfDollar.string(from: nval)!
          }

          // one-liner version of dollar()
          static func dollarApproximate(_ val: Decimal) -> String {
              return sharedInstance.nfDollarApproximate.string(from: NSNumber(value: Double(String(describing: val))!))!
          }

          static func decimal(_ val: Decimal) -> String {
              return sharedInstance.nfDecimal.string(from: NSNumber(value: Double(String(describing: val))!))!
          }

      }

      extension String {

          func leftPadding(toLength: Int, withPad character: Character) -> String {
              let newLength = self.characters.count

              if newLength < toLength {
                  return String(repeatElement(character, count: toLength - newLength)) + self
              } else {
                  return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength))
              }
          }
      }


      enum TaxCalculation {

          case none,
               solveTaxRateFromSubTotalAndTax,
               solveTotalFromSubTotalAndTaxRate,
               solveSubTotalFromTotalAndTaxRate
      }


      struct TaxRateSolver {

          let subTotalDollar: Decimal
          let taxRatePercent: Decimal
          let taxDollar:      Decimal
          let total:          Decimal
          let calc:           TaxCalculation

          init() {
              self.calc           = .none
              self.subTotalDollar = 0
              self.taxDollar      = 0
              self.taxRatePercent = 0
              self.total          = 0
          }

          init(subTotalDollar: Decimal, taxDollar: Decimal) {

              calc = .solveTaxRateFromSubTotalAndTax

              self.subTotalDollar = subTotalDollar
              self.taxDollar      = taxDollar
              self.taxRatePercent = self.taxDollar / self.subTotalDollar  // solve, approximate
              self.total          = self.subTotalDollar + self.taxDollar
          }

          init(subTotalDollar: Decimal, taxRatePercent: Decimal) {

              calc = .solveTotalFromSubTotalAndTaxRate

              self.subTotalDollar = subTotalDollar
              self.taxRatePercent = taxRatePercent
              self.taxDollar      = self.subTotalDollar * self.taxRatePercent  // solve total via tax, approximate
              self.total          = self.subTotalDollar + self.taxDollar       // solve, approximate
          }

          init(totalDollar: Decimal, taxRatePercent: Decimal) {

              calc = .solveSubTotalFromTotalAndTaxRate

              self.total          = totalDollar
              self.taxRatePercent = taxRatePercent
              self.subTotalDollar = self.total / (1.0 + self.taxRatePercent)  // solve, approximate
              self.taxDollar      = self.total - self.subTotalDollar          // solve, approximate
          }

      }

      extension TaxRateSolver: CustomStringConvertible, CustomDebugStringConvertible {

          public var description: String {

              // calc right-align formatting using total-dollar length
              func formattedLength() -> Int {

                  let nstr = Formatter.dollar(total)
                  return nstr.characters.count + 1
              }

              func formatDollar(_ val: Decimal) -> String {

                  return Formatter.dollar(val).leftPadding(toLength: formattedLength(), withPad: " ")
              }


              let solveSubTotal: String =    calc == .solveSubTotalFromTotalAndTaxRate ? "= \(Formatter.dollarApproximate(subTotalDollar))" : ""
              let solveTaxRate:  String = "\(calc == .solveTaxRateFromSubTotalAndTax   ? "= "                                               : "  ")\(Formatter.decimal(taxRatePercent))%"
              let solveTotal:    String =    calc == .solveTotalFromSubTotalAndTaxRate ? "= \(Formatter.dollarApproximate(total))"          : ""


              return "sub:   \(formatDollar(subTotalDollar))  " + "\(solveSubTotal)\n" +
                     "tax:   \(formatDollar(taxDollar))  "      + "\(solveTaxRate)\n"  +
                     "total: \(formatDollar(total))  "          + "\(solveTotal)\n"
          }


          // print(String(reflecting: taxInfo))
          public var debugDescription: String {

              get {

                  return "calc:  \(calc)\n"                         +
                         "sub:   \(subTotalDollar)\n"               +
                         "tax:   \(taxDollar)  \(taxRatePercent)\n" +
                         "total: \(total)"
              }
          }
      }


      // repl wrapper funcs
      func taxrate( _ subTotal: Decimal, _ tax:     Decimal) { print(TaxRateSolver(subTotalDollar: subTotal,  taxDollar:      tax))     }
      func total(   _ subTotal: Decimal, _ taxRate: Decimal) { print(TaxRateSolver(subTotalDollar: subTotal,  taxRatePercent: taxRate)) }
      func subtotal(_ total:    Decimal, _ taxRate: Decimal) { print(TaxRateSolver(totalDollar:    total,     taxRatePercent: taxRate)) }


      // example
      let t = TaxRateSolver(subTotalDollar: 15.99, taxDollar: 1.40)

      print(t.taxRatePercent)        // 0.0875547217010631644777986241400875547
      print(t)                       // sub:    $15.99
                                     // tax:     $1.40  = 0.08755472%
                                     // total:  $17.39

      taxrate(15.99, 1.40)           // sub:    $15.99
                                     // tax:     $1.40  = 0.08755472%
                                     // total:  $17.39

      let taxRate: Decimal = 0.0875

      total(9.99,  taxRate)          // sub:     $9.99
                                     // tax:     $0.87    0.0875%
                                     // total:  $10.86  = $10.8641

      subtotal(17.39, taxRate)       // sub:    $15.99  = $15.9908
                                     // tax:     $1.40    0.0875%
                                     // total:  $17.39


      // unit test

      import XCTest

      let TR: Decimal = 0.0875  // tax rate
      let P:  Decimal = 0.01    // 1 penny
      let HP: Decimal = 0.005   // 1/2 penny
      let QP: Decimal = 0.0025  // 1/4 penny
      let TP: Decimal = 0.001   // 1/10 penny
      let CP: Decimal = 0.0001  // 1/100 penny


      class TaxRateSolver_1: XCTestCase {

          let ST: Decimal = 1     // subtotal
          let TX: Decimal = 0.09  // tax dollar
          let T:  Decimal = 1.09  // total

          var t = TaxRateSolver()

          func testInput() {
              XCTAssertEqual(ST + TX, T)
          }


          /*
             sub:    $1.00
             tax:    $0.09  = 0.09%
             total:  $1.09

             sub:     1
             tax:     0.09  0.09
             total:   1.09

           */
          func testSolveTaxRate() {
              t = TaxRateSolver(subTotalDollar: ST, taxDollar: TX)
              XCTAssertEqual(t.calc, .solveTaxRateFromSubTotalAndTax)
              XCTAssertEqual(t.subTotalDollar, ST)
              XCTAssertEqual(t.taxDollar, TX)
              XCTAssertEqual(t.taxRatePercent, TX)
              XCTAssertEqual(abs(t.taxRatePercent - TR), QP)
              XCTAssertEqual(t.total, T)
          }


          /*
             sub:    $1.00
             tax:    $0.09    0.0875%
             total:  $1.09  = $1.0875

             sub:     1
             tax:     0.0875  0.0875
             total:   1.0875

           */
          func testSolveTotal() {
              t = TaxRateSolver(subTotalDollar: ST, taxRatePercent: TR)
              XCTAssertEqual(t.calc, .solveTotalFromSubTotalAndTaxRate)
              XCTAssertEqual(t.subTotalDollar, ST)
              XCTAssertEqual(t.taxDollar, TR)
              XCTAssertEqual(t.taxRatePercent, TR)
              XCTAssertEqual(abs(t.total - T), QP)
          }


          /*
             sub:    $1.00  = $1.0023
             tax:    $0.09    0.0875%
             total:  $1.09

             sub:     1.002298850574712643678160919540229885
             tax:     0.087701149425287356321839080459770115  0.0875
             total:   1.09

           */
          func testSolveSubTotal() {
              t = TaxRateSolver(totalDollar: T, taxRatePercent: TR)
              XCTAssertEqual(t.calc, .solveSubTotalFromTotalAndTaxRate)
              XCTAssertLessThan(abs(t.subTotalDollar - ST), HP)
              XCTAssertLessThan(abs(t.taxDollar - TR), TP)
              XCTAssertEqual(t.taxRatePercent, TR)
              XCTAssertEqual(t.total, T)
          }


          override func setUp() {
              super.setUp()
              // Put setup code here. This method is called before the invocation of each test method in the class.
          }

          override func tearDown() {
              // Put teardown code here. This method is called after the invocation of each test method in the class.
              print(t)
              print(String(reflecting: t))

              super.tearDown()
          }

      }


      class TaxRateSolver_70: XCTestCase {

          let ST: Decimal = 70     // subtotal
          let TX: Decimal = 6.12   // tax dollar
          let T:  Decimal = 76.12  // total

          var t = TaxRateSolver()

          func testInput() {
              XCTAssertEqual(ST + TX, T)
          }


          /*
             sub:    $70.00
             tax:     $6.12  = 0.08742857%
             total:  $76.12

             sub:     70
             tax:      6.12  0.087428571428571428571428571428571428571
             total:   76.12

           */
          func testSolveTaxRate() {
              t = TaxRateSolver(subTotalDollar: ST, taxDollar: TX)
              XCTAssertEqual(t.calc, .solveTaxRateFromSubTotalAndTax)
              XCTAssertEqual(t.subTotalDollar, ST)
              XCTAssertEqual(t.taxDollar, TX)
              XCTAssertLessThan(abs(t.taxRatePercent - TR), CP)
              XCTAssertEqual(t.total, T)
          }


          /*
             sub:    $70.00
             tax:     $6.12    0.0875%
             total:  $76.12  = $76.125

             sub:     70
             tax:      6.125  0.0875
             total:   76.125

           */
          func testSolveTotal() {
              t = TaxRateSolver(subTotalDollar: ST, taxRatePercent: TR)
              XCTAssertEqual(t.calc, .solveTotalFromSubTotalAndTaxRate)
              XCTAssertEqual(t.subTotalDollar, ST)
              XCTAssertEqual(abs(t.taxDollar - TX), HP)
              XCTAssertEqual(t.taxRatePercent, TR)
              XCTAssertEqual(abs(t.total - T), HP)
          }


          /*
             sub:    $70.00  = $69.9954
             tax:     $6.12    0.0875%
             total:  $76.12

             sub:     69.995402298850574712643678160919540229
             tax:      6.124597701149425287356321839080459771  0.0875
             total:   76.12

           */
          func testSolveSubTotal() {
              t = TaxRateSolver(totalDollar: T, taxRatePercent: TR)
              XCTAssertEqual(t.calc, .solveSubTotalFromTotalAndTaxRate)
              XCTAssertLessThan(abs(t.subTotalDollar - ST), HP)
              XCTAssertLessThan(abs(t.taxDollar - TX), HP)
              XCTAssertEqual(t.taxRatePercent, TR)
              XCTAssertEqual(t.total, T)
          }


          override func setUp() {
              super.setUp()
              // Put setup code here. This method is called before the invocation of each test method in the class.
          }

          override func tearDown() {
              // Put teardown code here. This method is called after the invocation of each test method in the class.
              print(t)
              print(String(reflecting: t))

              super.tearDown()
          }

      }


  //--------------------------------
    group collection values by properties using this Group By collection extension
    from Alejandro Martinez' blog post "GroupBy in Swift 2"
    http://alejandromp.com/blog/2015/6/28/group-by-swift

      extension CollectionType {

          public func groupBy(grouper: (Self.Generator.Element, Self.Generator.Element) -> Bool) -> [[Self.Generator.Element]] {
              var result : Array<Array<Self.Generator.Element>> = []

              var previousItem : Self.Generator.Element?
              var group : [Self.Generator.Element] = []

              for item in self {
                  defer { previousItem = item }

                  guard let previous = previousItem else {
                      group.append(item)
                      continue
                  }

                  if grouper(previous, item) { // item in the same group
                      group.append(item)
                  }
                  else { // new group
                      result.append(group)
                      group = []
                      group.append(item)
                  }
              }

              result.append(group)

              return result
          }
      }


      import Foundation

      struct Match : Equatable, Hashable, CustomStringConvertible {
          let id: NSUUID
          let row: Int
          let col: Int
          var hashValue: Int { return self.id.hashValue }

          init(row:Int, col: Int) {
              id = NSUUID()
              self.row = row
              self.col = col
          }

          var description: String {
              return "id: \(id.UUIDString), r: \(row), c: \(col)"
          }
      }

      func ==(lhs: Match, rhs: Match) -> Bool {
          return lhs.id == rhs.id
      }


      let m1 = Match(row:  10, col:  20)
      let m2 = Match(row: 100, col: 200)

      var data: Set<Match> = Set([m1, m2])
      data.insert(Match(row: 500, col:   1))
      data.insert(Match(row: 100, col: 201))
      data.insert(Match(row:   1, col:   2))
      data.insert(Match(row:   1, col:  42))
      data.insert(Match(row:   1, col:  66))
      data.insert(Match(row:  10, col:  20))
      data.insert(Match(row:  42, col:   1))
      data.insert(Match(row:   2, col:  42))
      data.insert(Match(row:   1, col:  42))

      let sortedData: [Match] = Array(data).sort { // sort by Column and Row
          if $0.0.col != $0.1.col {
              return $0.0.col < $0.1.col
          }
          else {
              return $0.0.row < $0.1.row
          }
      }

      let groupedData: [[Match]] = sortedData.groupBy { $0.col == $1.col } // group by Column

      print(groupedData) // 7 elements


      // output
      [ [id: 0, r: 42,  c: 1,
         id: 1, r: 500, c: 1],
        [id: 2, r: 1,   c: 2],
        [id: 3, r: 10,  c: 20,
         id: 4, r: 10,  c: 20],
        [id: 5, r: 1,   c: 42,
         id: 6, r: 1,   c: 42,
         id: 7, r: 2,   c: 42],
        [id: 8, r: 1,   c: 66],
        [id: 9, r: 100, c: 200],
        [id: A, r: 100, c: 201] ]
   
  
  //--------------------------------
    search using filter and map functions
    - find records by key
      find records where column count >= search index
      get record column by index, return data rows

      func fooSearch()
      {
              func getData() -> [String]
              {
                  // data has duplicate keys
                  let data = "A:a1,a2,a3\n"  // key : col1, col2, col3
                           + "B:b1,b2,b3\n"
                           + "B:b10,b20\n"
                           + "B:b100\n"
                           + "C:c1\n"
                           + "F:";

                  return split(data) { $0 == "\n" }
              }


          let data:[String] = getData()


              func search(query:(String, Int)) -> [String]
              {
                  let recs:[String] = data.filter { $0.hasPrefix(query.0) }  // filter - where data records match key

               // var cols:[[String]] = recs.map { split( split($0) { $0 == ":" }[1] ) { $0 == "," } }  // [1] = "array index out of range" error if record is empty
                  var cols:[[String]] = recs.map { split( split($0, { $0 == ":" }, maxSplit:Int.max, allowEmptySlices:true)[1] ) { $0 == "," } }

                  cols = cols.filter { $0.count >= query.1 }         // filter - where records column count >= search index
                  var results:[String] = cols.map { $0[query.1-1] }  // get column by index, return data rows

                  return results
              }


              func searchQueries() -> [(String, Int)]  // array of tuples
              {
                  return [("A", 1), // key, column index
                          ("A", 2),
                          ("A", 3),
                          ("A", 4),
                          ("B", 2),
                          ("C", 1),
                          ("C", 2),
                          ("F", 1)];
              }


          for q:(String, Int) in searchQueries()
          {
              var results:[String] = search(q)
              for x in results
              {
                  println(x)
              }
           }
      }


      // output
      a1
      a2
      a3
      b2
      b20
      c1


  //--------------------------------
    get unique objects from array by name, then by max date

      // file: Foo.swift
      import Foundation
      
      class Foo {
          
          var name: String
          var id: Int
          var date: NSDate
          
          init(name:String, id:Int, date:NSDate) {
              self.name = name
              self.id = id
              self.date = date
          }
      }
      
      extension Foo: Comparable, Equatable { }
          
          func <(lhs:Foo, rhs:Foo) -> Bool {
              return lhs.date.compare(rhs.date) == NSComparisonResult.OrderedAscending
          }
          
          func ==(lhs:Foo, rhs:Foo) -> Bool {
              return lhs.name == rhs.name
          }
      
      
      
      // file: FooSet.swift
      // Set Class by Kametrixom http://stackoverflow.com/questions/24044190/how-to-create-array-of-unique-object-list-in-swift
      // 
      class Set<T: Equatable> {
          
          var items:[T] = []
          
          func add(item:T) {
              if !contains(items, { $0 == item }) {
                  items.append(item)
              }
          }
      }
      
      
      // file: FooViewController.swift
      func debugFoo() {
              
              func getFooData() -> [Foo] {
                  var i = 0

                      func getRandomFooName() -> String {
                          return (arc4random_uniform(2) % 2) == 1 ? "Foo" : "Bar"  // mod
                      }
                      
                      func getRandomFooDate() -> NSDate {
                          let secondsPerDay = 86400
                          let dayRange:UInt32 = 30
                          var randomTimeOffset = Double((Int(arc4random_uniform(dayRange)) * secondsPerDay) + (i * 1))
                          
                          return NSDate().dateByAddingTimeInterval(-randomTimeOffset)
                      }
                  
                  
                  var fooData = [Foo]()
                  for (i = 0; i < 10; i++) {
                      let foo = Foo(name: getRandomFooName(), id: i, date: getRandomFooDate())
                      fooData.append(foo)
                  }
                  
                  return fooData
              }
              
              
              func printFoos(a_foos: [Foo]) {
                      func formatDate(a_date:NSDate) -> String {
                          var df = NSDateFormatter()
                          df.dateFormat = "MM-dd-yy HH:mm:ss"
                          df.timeZone = NSTimeZone.localTimeZone()
                          
                          return df.stringFromDate(a_date)
                      }
                  
                  
                  println("\n\n----------------")
                  for foo in a_foos {
                      println("\(foo.name), id: \(foo.id), date: \(formatDate(foo.date))")
                  }
              }
              
              
              func printMaxFoo(a_foos:[Foo]) {
                  let maxFoo = maxElement(a_foos)
                  printFoos([maxFoo])  // one element array
              }
              
              
              func printSortedFoos(a_foos:[Foo]) {
                  let sortedFoos = sorted(a_foos)
                  printFoos(sortedFoos)
              }
              
              
              func getSetFromArray(a_foos:[Foo]) -> Set<Foo> {
                  var fooSet = Set<Foo>()
                  
                  for foo in a_foos {
                      fooSet.add(foo)
                  }
                  
                  return fooSet
              }
          
          
          let foos:[Foo] = getFooData()
          printFoos(foos)
          printSortedFoos(foos)
          printMaxFoo(foos)
          
          let fooSet:Set<Foo> = getSetFromArray(foos)
          printFoos(fooSet.items)
          
          // loop thru the foo set and filter the foos array using the set element to find the matching max foo
          var maxFoos = [Foo]()
          for foo in fooSet.items {
              maxFoos.append(maxElement((foos.filter({ $0 == foo }))))
          }
          
          printFoos(maxFoos)
      }
      
      
      // output

      // raw data
      ----------------
      Bar, id: 0, date: 10-29-14 19:42:17
      Foo, id: 1, date: 10-21-14 19:42:16
      Foo, id: 2, date: 10-16-14 19:42:15
      Foo, id: 3, date: 11-02-14 18:42:14  // standard time diff
      Bar, id: 4, date: 10-22-14 19:42:13
      Foo, id: 5, date: 10-20-14 19:42:12
      Bar, id: 6, date: 10-11-14 19:42:11
      Bar, id: 7, date: 10-26-14 19:42:10
      Foo, id: 8, date: 10-17-14 19:42:09
      Foo, id: 9, date: 10-20-14 19:42:08


      // sorted data
      ----------------
      Bar, id: 6, date: 10-11-14 19:42:11
      Foo, id: 2, date: 10-16-14 19:42:15
      Foo, id: 8, date: 10-17-14 19:42:09
      Foo, id: 9, date: 10-20-14 19:42:08
      Foo, id: 5, date: 10-20-14 19:42:12
      Foo, id: 1, date: 10-21-14 19:42:16
      Bar, id: 4, date: 10-22-14 19:42:13
      Bar, id: 7, date: 10-26-14 19:42:10
      Bar, id: 0, date: 10-29-14 19:42:17
      Foo, id: 3, date: 11-02-14 18:42:14


      // max foo
      ----------------
      Foo, id: 3, date: 11-02-14 18:42:14


      // set data
      ----------------
      Bar, id: 0, date: 10-29-14 19:42:17
      Foo, id: 1, date: 10-21-14 19:42:16


      // unique foo by name, then by max date
      ----------------
      Bar, id: 0, date: 10-29-14 19:42:17
      Foo, id: 3, date: 11-02-14 18:42:14


  //--------------------------------
    sort string array, Swift 3

      let names = ["foo", "bar", "", " ", "abc"]

      let sortedNames = names.sorted()
                     // names.sorted() { $0 < $1 }
                     // names.sorted(by: <)
                     // names.sorted(by: { $0 < $1 })
                     // names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 < s2 })

      for name in sortedNames {
          "'\(name)'" |> p    // '' ' ' 'abc' 'bar' 'foo'
      }


  //--------------------------------
    print unicode chars, Swift 4

      let dog: Character = "\u{1F436}"
      "animal farm: \(dog) \u{1F431} \u{1F638} \u{1F414} \u{1F42E}" |> p  // 🐶 🐱 😸 🐔 🐮


  //--------------------------------
    print number string, Swift 4

      import Foundation

      var s  = String()
      var ms = String()  // mod string

      for x in 1...20 {
          s  += "\(x),"      // 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
          ms += "\(x % 10)"  // 12345678901234567890
      }

      let r: Range<String.Index> = s.startIndex..<s.index(before: s.endIndex)  // range up to last char ","
      let ss1 = String(s[r])                                                   // substring, subscript range to remove last char                1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
      let ss2 = s[..<s.index(s.endIndex, offsetBy: -1)]                        // alt, substring, subscript range to remove last char w/offset  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20


  //--------------------------------
    uuids, Swift 3

      import Foundation

      let uid = UUID()
      uid.uuidString |> p  // 00000000-0000-0000-0000-000000000000

      let buffSize = 16
      var bytes: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer.allocate(capacity: buffSize)
      (uid as NSUUID).getBytes(UnsafeMutablePointer(bytes))

      for i in 0..<buffSize {
          "\(bytes[i]) " |> p  // 4 98 113 71 54 71 70 187 161 54 82 147 74 33 226 33
      }

      bytes.deallocate(capacity: buffSize)


  //--------------------------------
    string to bytes, Swift 3

      import Foundation

      // String -> bytes
      let s = "swift"
      let b: [(UInt8)] = Array(s.utf8)  // [UInt8](s.utf8)

      b |> p  // [115, 119, 105, 102, 116]


      // NSString -> NSData -> bytes
      let d: Data = ("swift" as NSString).data(using: String.Encoding.utf8.rawValue)!

      var b1: [(UInt8)] = Array(repeating: 0, count: d.count)    // 7 values, [0..6] = 0
                       // [UInt8](repeating: 0, count: d.count)

      (d as NSData).getBytes(&b1, length: d.count)  // copy data bytes into buffer

      b1 |> p  // [115, 119, 105, 102, 116]


  //--------------------------------
    decode url, Swift 3

      func decodeURL(_ url: String) -> String {
          return (url as NSString).replacingOccurrences(of: "%20", with: " ")
      }

      decodeURL("foo%20bar")


  //--------------------------------
    download url
    
    - to test non-secure http endpoints disable App Transport Security restrictions for all network connections
      info.plist: NSAppTransportSecurity > NSAllowsArbitraryLoads : YES
      
      func syncDownloadString(_ url: URL) {
          do {
              let html = try NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String
              logHtml(html, url)
          }
          catch {
              error |> log
          }
      }
      
      func asyncDownloadString(_ url: URL) {
          DispatchQueue.global().async {
              
              do {
                  let html = try NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String
                  
                  DispatchQueue.main.async(execute: DispatchWorkItem {
                      self.logHtml(html, url)
                  })
              }
              catch {
                  error |> log
              }
              
          }
      }
      
      func sessionDataTask(_ url: URL) {
          let task = URLSession.shared.dataTask(with: url,
                                                completionHandler: { (data: Data?, urlResponse: URLResponse?, error: Error?) -> Void in
              
              if let error = error {
                  error |> p
              }
              else {
                  let statusCode: Int = (urlResponse as! HTTPURLResponse).statusCode
                  let html = String(data: data!, encoding: String.Encoding.utf8)! as String
                  
                  DispatchQueue.main.async(execute: DispatchWorkItem {
                      
                      self.logHtml(html, url, statusCode)
                      
                  })
              }
          })
          
          task.resume()
      }
      
      
      // async await
      // 
      Button("Download") {
          Task {
              let url = URL(string: "https://localhost:8443")!
              
              try await asyncAwaitDownload(url)
          }
      }
      
      func asyncAwaitDownload(_ url: URL) async throws {
          let result: (data: Data, response: URLResponse) = try await URLSession.shared.data(from: url)
          
          let statusCode: Int = (result.response as! HTTPURLResponse).statusCode
          let html = String(data: result.data, encoding: String.Encoding.utf8)! as String
          
          self.logHtml(html, url, statusCode)
      }
      
      
      func logHtml(_ html: String, _ url: URL, _ statusCode: Int? = nil) {
          
"""

url: \(url.absoluteString)
sc:  \(String(describing: statusCode))
\(html)
"""
          |> log
          
      }
    
    
  //--------------------------------
    average, Swift 3

      let add: (Int, Int) -> Int = { x, y in x + y }

      func average(_ numbers: Int...) -> Float { let sum = numbers.reduce(0, add);                                  return Float(sum) / Float(numbers.count) }
      func average(_ numbers: Int...) -> Float { let sum = numbers.reduce(0) { (x: Int, y: Int) -> Int in x + y };  return Float(sum) / Float(numbers.count) }
      func average(_ numbers: Int...) -> Float { let sum = numbers.reduce(0) { $0 + $1 };                           return Float(sum) / Float(numbers.count) }
      func average(_ numbers: Int...) -> Float { let sum = numbers.reduce(0, +);                                    return Float(sum) / Float(numbers.count) }

      average(1, 2, 3)  // 2.0


      let a: Array<Int> = [1, 2, 3]
      apply(average, a)  // 2.0

      // apply func from Matt Bradley's blog post "The Missing Apply Function in Swift"
      // https://www.drivenbycode.com/the-missing-apply-function-in-swift
      func apply<T, U>(_ fn: (T...) -> U, _ args: [T]) -> U {
          typealias FunctionType = ([T]) -> U
          return unsafeBitCast(fn, to: FunctionType.self)(args)
      }


  //--------------------------------
    unique words, count
    Swift 1.2 has Set data struct

      let input = "FOO bar bar foo 123"
      let data = input.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
      let uniqueWords = Set(data)  // string set elements are case-sensitive
      
      println("count: \(uniqueWords.count)")  // 4
      
      for word in uniqueWords {
          println("\(word)")
      }
      
      
      // output
      foo
      123
      bar
      FOO
    
    
  //--------------------------------
    swift algorithms
    https://github.com/apple/swift-algorithms
      
      - Combinations
      
      // Collection extension
      // 
      // func combinations(ofCount k: Int) -> CombinationsSequence<Self>
      // 
      // Returns a sequence of all the different combinations of a collection’s elements
      // with each combination in the order of the original collection.
      
      import Algorithms
      
      let fenCastling = ["k", "q", "K", "Q"]
      let combos: Array<String> = combos(fenCastling)
      
      combos.flatMap { combo in
                         combo
                     }
            .sorted(by: { c1, c2 in
                            if (c1.count == c2.count) {       // 1. sort by string length
                                return c1 < c2                // 2. sort by string, asc <
                            }
                            else {
                                return c1.count > c2.count    // sort by string length, desc >
                            }
                        })
            .forEach { combo in
                         combo |> p  //
                     }
      }
      
      func combos(_ data: Array<String>) -> Array<String> {
          let range: ClosedRange<Int> = (1...data.count)
          
          let sortAndJoin: (Array<String>) -> String = { cd in  // map closure
              let combo: String = cd.sorted().joined()
              return combo
          }
          
                                        // algorithm
                                        // 
          let combos: Array<String> = data.combinations(ofCount: range).map(sortAndJoin)
          
          return combos
      }
      
      
      // output
      // combos()  sort and print
         k         KQkq
         q         KQk 
         K         KQq 
         Q         Kkq 
         kq        Qkq 
         Kk        KQ  
         Qk        Kk  
         Kq        Kq  
         Qq        Qk  
         KQ        Qq  
         Kkq       kq  
         Qkq       K   
         KQk       Q   
         KQq       k   
         KQkq      q   
    
        
  //--------------------------------
    crypto
      
      import CryptoKit
      
      let s = "foo"
      let data: Data = s.data(using: String.Encoding.utf8)!
      
      let hash: SHA512.Digest = CryptoKit.SHA512.hash(data: data)
      
      
      "input  '\(s)'"              |> p    // 'foo'
      "output \(hash.description)" |> p    // SHA512 digest: f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7
      SHA512.Digest.byteCount      |> p    // 64
      
      let hashdata: Array<String> = hash.map { (byte: UInt8) in
                                                 let hex = String(format: "%02x", byte)  // byte to hex
                                                 return hex
                                             }
      
      hashdata.joined()               |> p
      hashdata.joined(separator: " ") |> p
      
      // f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7
      // f7 fb ba 6e 06 36 f8 90 e5 6f bb f3 28 3e 52 4c 6f a3 20 4a e2 98 38 2d 62 47 41 d0 dc 66 38 32 6e 28 2c 41 be 5e 42 54 d8 82 07 72 c5 51 8a 2c 5a 8c 0c 7f 7e da 19 59 4a 7e b5 39 45 3e 1e d7
    
    
  //--------------------------------
    c apis
    https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html

      // file: foo.h
      #ifndef fooApp__foo__
      #define fooApp__foo__

      const char foo[] = "foo";
      const char * bar = "bar";
      char * getCheezburger();

      #endif


      // file: foo.c
      #include "foo.h"

      char * getCheezburger() {
          return "cheezburger!";
      }


      // file: fooApp-bridging-header.h
      // import target public headers to expose to Swift
      #import "foo.h"


      // file: main.swift
      import Foundation

      // foo char array is tuple (Int8, Int8, Int8, Int8)
      println(foo)  // (102, 111, 111, 0)

      // iterate func by dankogai http://stackoverflow.com/users/1588574/dankogai
      // http://stackoverflow.com/questions/24299045/any-way-to-iterate-a-tuple-in-swift
      func iterate<C,R>(t:C, block:(Any)->R) {
          let mirror = reflect(t)
          for i in 0..<mirror.count {
              block(mirror[i].1.value)
          }
      }

      // iterate tuple (foo char array)
      iterate(foo) {
          print( String(UnicodeScalar((("\($0)") as String).toInt()!)) )  // foo
      }

      println()

      var _bar:UnsafePointer<Int8> = bar  // bar char pointer
      println(_bar)                       // 0x00000000
      println(String.fromCString(_bar)!)  // bar

      var cb:UnsafeMutablePointer<CChar> = getCheezburger()  // typealias CChar = Int8
      println(String.fromCString(cb)!)                       // cheezburger!


- OpenGL

  - OpenGL GPU Compatibility

    iOS   OpenGL ES  https://developer.apple.com/library/content/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/HardwareGPUInformation/HardwareGPUInformation.html
    macOS OpenGL/CL  https://support.apple.com/en-us/HT202823


  - EAGL Version

      // Swift 4
      var major: UInt32 = 0
      var minor: UInt32 = 0
      EAGLGetVersion(&major, &minor)
      print("EAGL version: \(major).\(minor)")  // 1.0


      // OC
      uint major, minor;
      EAGLGetVersion(&major, &minor);
      NSLog(@"EAGL version: %d.%d", major, minor);  // 1.0


  - Upgrade iOS Game to OpenGL ES 3
    1. Update vertex shader program
    2. Update fragment shader program
    3. Init the EAGLContext with OpenGL ES version 3


      // 1. file: shader.vsh
      #version 300 es

      in vec4 position;
      in vec3 normal;

      out lowp vec4 colorVarying;

      uniform mat4 modelViewProjectionMatrix;
      uniform mat3 normalMatrix;

      void main() {
          vec3 eyeNormal = normalize(normalMatrix * normal);
          vec3 lightPosition = vec3(0.0, 0.0, 1.0);
          vec4 diffuseColor = vec4(0.4, 0.4, 1.0, 1.0);

          float nDotVP = max(0.0, dot(eyeNormal, normalize(lightPosition)));

          colorVarying = diffuseColor * nDotVP;

          gl_Position = modelViewProjectionMatrix * position;
      }


      // 2. file: shader.fsh
      #version 300 es

      in lowp vec4 colorVarying;
      out lowp vec4 fragmentColor;

      void main() {
          fragmentColor = colorVarying;
      }


      // 3. file: gameViewController.swift/m
      self.context = EAGLContext(api: .openGLES3)                                   // Swift 4
      self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];  // OC


  - OpenGL Version Info
    Also available in debug capture GPU frame context info

      print("vendor: \(String(cString: unsafeBitCast(glGetString(GLenum(GL_VENDOR)), to: UnsafePointer<CChar>.self)))")  // Swift 4
      printf("vendor: %s", (char *)glGetString(GL_VENDOR));                                                              // OC

        GL_VENDOR      Apple Inc.

        GL_RENDERER    Apple A8   GPU   iphone 6/6+
                       Apple A8X  GPU   ipad air 2
                       Apple A9   GPU   iphone 6s/6s+
                       Apple A9X  GPU   ipad pro
                       Apple A10  GPU   iphone 7/7+
                       Apple A10X GPU   ipad pro 2017
                       Apple A11  GPU   iphone 8/8+/X

        GL_VERSION     OpenGL ES 3.0 Apple A8X GPU - 75.11.5
                       OpenGL ES 3.0 Apple A9  GPU - 75.11.5
                       OpenGL ES 3.0 Apple A9X GPU - 75.11.5
                       OpenGL ES 3.0 Metal         - 33         A9X/A10  iOS 10
                       OpenGL ES 3.0 Metal         - 52.1.2     A10X/A11 iOS 11

        GL_SHADING_LANGUAGE_VERSION    OpenGL ES GLSL ES 3.00


  - OpenGL ES 3 Extensions
    Also available in debug capture GPU frame context info

      #import <OpenGLES/ES3/gl.h>


      int count = 0;
      glGetIntegerv(GL_NUM_EXTENSIONS, &count);
      NSLog(@"count: %d", count);  // 19

      NSMutableArray * extensions = [NSMutableArray array];
      for (int i = 0; i < count; i++)
      {
          char * ext = (char *)glGetStringi(GL_EXTENSIONS, i);
          [extensions addObject: [NSString stringWithUTF8String:ext]];
      }

      for (NSString * ext in [extensions sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)])
      {
          NSLog(@"%@", ext);
      }


      // output
      GL_APPLE_clip_distance
      GL_APPLE_color_buffer_packed_float
      GL_APPLE_copy_texture_levels
      GL_APPLE_rgb_422
      GL_APPLE_texture_format_BGRA8888
      GL_EXT_color_buffer_half_float
      GL_EXT_debug_label
      GL_EXT_debug_marker
      GL_EXT_pvrtc_sRGB
      GL_EXT_read_format_bgra
      GL_EXT_separate_shader_objects
      GL_EXT_shader_framebuffer_fetch
      GL_EXT_shader_texture_lod
      GL_EXT_shadow_samplers
      GL_EXT_texture_filter_anisotropic
      GL_IMG_read_format
      GL_IMG_texture_compression_pvrtc
      GL_KHR_texture_compression_astc_ldr
      GL_OES_standard_derivatives


  - OpenGL Active Uniforms

      GLchar * name;
      GLint    nameBufSize;
      GLsizei  nameLength;
      GLint count, loc, size;
      GLenum type;

      glGetProgramiv(_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &nameBufSize);
      name = (GLchar *)malloc(nameBufSize);

      glGetProgramiv(_program, GL_ACTIVE_UNIFORMS, &count);

      printf("uniform count: %d\n", count);
      for (int i = 0; i < count; i++)
      {
          glGetActiveUniform(_program, i, nameBufSize, &nameLength, &size, &type, name);
          loc = glGetUniformLocation(_program, name);

          printf("%d. loc: %d, name: %s, type: 0x%02x/%d, size: %d, len: %d\n", (i + 1), loc, name, type, type, size, nameLength);
      }

      free(name);
      
      
      // output
      uniform count: 2
      1. loc: 0, name: modelViewProjectionMatrix, type: 0x8b5c/35676, size: 1, len: 25  // type GL_FLOAT_MAT4 (GLKMatrix4)
      2. loc: 4, name: normalMatrix, type: 0x8b5b/35675, size: 1, len: 12               //      GL_FLOAT_MAT3 (GLKMatrix3)


- Sprite Kit

  - Scene Frame Animation Loop Methods
    https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Actions/Actions.html

      - SKScene              - SKSceneDelegate protocol

        update:                update:forScene:               (NSTimeInterval)currentTime (SKScene *)scene
        didEvaluateActions     didEvaluateActionsForScene:
        didSimulatePhysics     didSimulatePhysicsForScene:
        didApplyConstraints    didApplyConstraintsForScene:
        didFinishUpdate        didFinishUpdateForScene:


      - scene frame processing

        update:

        // execute actions and animations
        didEvaluateActions

        // simulate physics
        didSimulatePhysics

        // apply constraints
        didApplyConstraints

        didFinishUpdate
        // render and display updated content


  - SKPhysicsContactDelegate Protocol
    Implement to receive messages when 2 physics bodies begin or end contact

      -(void)didBeginContact:(SKPhysicsContact *)contact
      {
          NSLog(@"a: %@", contact.bodyA);
          NSLog(@"b: %@", contact.bodyB);
      }


  - SKPhysicsBody -allContactedBodies
    Use to check if any nodes of interest are in contact

      -(void)didSimulatePhysics
      {
          NSArray * nodes = [currentNode.physicsBody allContactedBodies];

          if (nodes.count > 1)  // if nodes count > 1 then 3 or more nodes are in contact
          {
              for (SKNode * n in nodes)
              {
                  //
              }
          }
      }


  - Debug Frame Updates Every n Seconds

      -(void)update:(NSTimeInterval)currentTime
      {
              void (^debugFrame)(float) = ^(float interval)
              {
                  static NSTimeInterval lastUpdateTime = 0;
                  if (lastUpdateTime == 0)
                  {
                      lastUpdateTime = currentTime;
                  }

                  NSTimeInterval delta = (currentTime - lastUpdateTime);
                  if (delta == 0 || delta >= interval)
                  {
                      lastUpdateTime = currentTime;
                      // ...
                  }
              };

          debugFrame(0.5f);
      }


      // NSDate * debugFrameTimer;  // ivar
      -(void)didSimulatePhysics
      {
              void (^debugFrame)(float) = ^(float interval)
              {
                  if ((debugFrameTimer == nil) || (debugFrameTimer.timeIntervalSinceNow <= -interval))
                  {
                      debugFrameTimer = [NSDate date];
                      // ...
                  }
              };

          debugFrame(0.5f);
      }


  - Load SKTexture with Size

      NSString * filePath = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"png"
      NSImage * image = [[NSImage alloc] initWithContentsOfFile:filePath];
      NSData * data = [image TIFFRepresentation];

      NSImageRep * rep = [image representations][0];
      CGSize size = CGSizeMake(rep.pixelsWide, rep.pixelsHigh);  // actual image dimensions

      SKTexture * texture = [SKTexture textureWithData:data size:size];

      SKSpriteNode * sprite = [SKSpriteNode spriteNodeWithTexture:texture];


  - TV Noise Shader with SKShader (OpenGL ES 2)

      -(void)touchesBegan: (NSSet *)touches withEvent: (UIEvent *)event {
          CGPoint location = [[touches anyObject] locationInNode: self];
          
          SKShader * shader = [SKShader shaderWithFileNamed: @"noise.fsh"];
          SKUniform * u_red = [SKUniform uniformWithName: @"u_red" float: 0.20f];
          [shader addUniform: u_red];
          
          SKShapeNode * node = [SKShapeNode shapeNodeWithRectOfSize: CGSizeMake(400, 300)];
          node.position = location;
          node.fillShader = shader;
          
          [self addChild:node];
      }


      // fragment shader file: noise.fsh
      // 
      void main() {
          highp float a  = 12.9898;
          highp float b  = 78.233;
          highp float c  = 43758.5453;
          highp float dt = dot(v_tex_coord.xy, vec2(a, b));
          highp float sn = (dt * u_time);
          highp float n  = fract(sin(sn) * c);
          
       // gl_FragColor = vec4(n, n, n, 1.0);
          gl_FragColor = vec4(u_red, n, n, 1.0);
      }


- Scene Kit

  - Node Transformation Properties

      constraints     NSArray *      non-animatable
      eulerAngles     SCNVector3     orientation as roll, yaw, and pitch angles
      orientation     SCNQuaternion
      pivot           SCNMatrix4     pivot point for position, rotation, and scale
      position        SCNVector3
      rotation        SCNVector4     orientation as a rotation angle about an axis
      scale           SCNVector3
      transform       SCNMatrix4
      worldTransform  SCNMatrix4     read-only, non-animatable


  - Scene Frame Animation Loop Methods
    https://developer.apple.com/reference/scenekit/scnscenerendererdelegate

      - SCNSceneRendererDelegate protocol

        renderer:updateAtTime:                (id<SCNSceneRenderer>)aRenderer (NSTimeInterval)time
        renderer:didApplyAnimationsAtTime:
        renderer:didSimulatePhysicsAtTime:
        renderer:willRenderScene:atTime:      (SCNScene *)scene
        renderer:didRenderScene:atTime:


      - scene frame processing

        renderer:updateAtTime:

        // execute actions and animations
        renderer:didApplyAnimationsAtTime:

        // simulate physics
        renderer:didSimulatePhysicsAtTime:

        // apply constraints

        renderer:willRenderScene:atTime:
        // render scene
        renderer:didRenderScene:atTime:


  - Determining Viewable Nodes

      // SCNSceneRenderer protocol
      if ([aRenderer isNodeInsideFrustum:node withPointOfView:cameraNode])
      {
          NSLog(@"node visible");
      }
      else
      {
          NSLog(@"...");
      }


  - Modify Node-Copy Geometry and Material
    Copied nodes share attributes, so copy geometry and material before mutating

      SCNNode * debugNode = [node copy];
      debugNode.geometry = [node.geometry copy];
      debugNode.geometry.firstMaterial = [node.geometry.firstMaterial copy];

      debugNode.geometry.firstMaterial.diffuse.contents = [UIColor redColor];
      ((SCNPlane *)debugNode.geometry).widthSegmentCount = 10;  // add detail
      debugNode.position = SCNVector3Make(2.5, 0, 0);  // offset

      [scene.rootNode addChildNode:debugNode];


  - Bananas Scene Kit Demo: Use a SCNLookAtConstraint to have monkeys look at player

      // add head node property to SkinnedCharacter interface, monkey heads will look at player head
      @property (nonatomic) SCNNode * head;


      // set head nodes in char create, names are defined in dae file
      self.playerCharacter = [[PlayerCharacter alloc] initWithNode:characterRootNode];
      self.playerCharacter.head = [self.playerCharacter childNodeWithName:@"Bip001_Head" recursively:YES];

      MonkeyCharacter * monkey = [[MonkeyCharacter alloc] initWithNode:monkeyRootNode];
      monkey.head = [monkey childNodeWithName:@"Bone_Head" recursively:YES];


      // in monkey update frame, set look-at-constraint based on distance and interact range
      const CGFloat interactRange = 1050;
      CGFloat distanceToCharacter = GLKVector3Distance(SCNVector3ToGLKVector3(playerCharacter.position), position);
      self.head.constraints = (distanceToCharacter <= interactRange) ? @[ [SCNLookAtConstraint lookAtConstraintWithTarget:playerCharacter.head] ] : nil;


  - Bananas Scene Kit Demo: Clone explorer w/o animations

        PlayerCharacter * clone = [self.playerCharacter clone];
        clone.position = SCNVector3Make(clone.position.x + 100, clone.position.y, clone.position.z);
        clone.geometry = [self.playerCharacter.geometry copy];
        clone.mainSkeleton = [self.playerCharacter.mainSkeleton copy];

        [clone enumerateChildNodesUsingBlock:^(SCNNode * child, BOOL * stop)
        {
            if (child.skinner)
            {
                [child.skinner.skeleton removeAllAnimations];
                *stop = YES;
            }
        }];

        /* alt
        SCNNode * n = [clone childNodeWithName:@"explorer" recursively:YES];
        [n.skinner.skeleton removeAllAnimations]; */

        [self.rootNode addChildNode:clone];


  - Hit Test

      // SCNSceneRenderer protocol
      // (NSArray *)hitTest:(CGPoint)thePoint options:(NSDictionary *)options

      -(void)tap:(UIGestureRecognizer *)gestureRecognize
      {
          SCNView * view = (SCNView *)self.view;
          CGPoint tp = [gestureRecognize locationInView:view];
          NSArray * hitTestResults = [view hitTest:tp options:nil];
          [hitTestResults enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop)
          {
              SCNHitTestResult * result = (SCNHitTestResult *)obj;
              SCNNode * node = result.node;
              printNode(node);
         }];
      }


  - JavaScript Bridge, JavaScript Core Framework

      #import <JavaScriptCore/JavaScript.h>
      #import <JavaScriptCore/JSContext.h>
      #import <SceneKit/SCNJavascript.h>


      // setup light node
      SCNNode * light = [SCNNode node];
      light.light = [SCNLight light];
      light.light.type = SCNLightTypeOmni;
      light.light.color = [UIColor redColor];
      light.position = SCNVector3Make(10, 10, 10);
      [scene.rootNode addChildNode:light];


      // control light node via javascript
      JSContext * ctx = [[JSContext alloc] initWithVirtualMachine:[JSVirtualMachine new]];
      SCNExportJavaScriptModule(ctx);
      ctx.globalObject[@"jsLight"] = light;


      // use evaluateScript to print, debug, and modify values, returns the last value generated by script
      // get current light type
      JSValue * jval = [ctx evaluateScript:@"jsLight.light.type"];
      NSLog(@"%@", jval);  // omni

      // set light type to ambient and color white
      NSString * js = @"jsLight.light.type = 'ambient'; \
                        jsLight.light.color = SCNColor.color(1, 1, 1, 1);";

      jval = [ctx evaluateScript:js];
      NSLog(@"%@", jval);  // UIDeviceRGBColorSpace 1 1 1 1


- Double-Precision Literal Defines

    usr/include/math.h

    e           M_E         2.71 828182845904523536028747135266250
    log2(e)     M_LOG2E     1.44 269504088896340735992468100189214
    log10(e)    M_LOG10E    0.43 4294481903251827651128918916605082
    loge(2)     M_LN2       0.69 3147180559945309417232121458176568
    loge(10)    M_LN10      2.30 258509299404568401799145468436421
    pi          M_PI        3.14 159265358979323846264338327950288
    pi/2        M_PI_2      1.57 079632679489661923132169163975144
    pi/4        M_PI_4      0.78 5398163397448309615660845819875721
    1/pi        M_1_PI      0.31 8309886183790671537767526745028724
    2/pi        M_2_PI      0.63 6619772367581343075535053490057448
    2/sqrt(pi)  M_2_SQRTPI  1.12 837916709551257389615890312154517
    sqrt(2)     M_SQRT2     1.41 421356237309504880168872420969808
    1/sqrt(2)   M_SQRT1_2   0.70 7106781186547524400844362104849039


- Core Foundation

    typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;

    typedef const struct CF_BRIDGED_TYPE(NSString)                __CFString * CFStringRef;
    typedef       struct CF_BRIDGED_MUTABLE_TYPE(NSMutableString) __CFString * CFMutableStringRef;


    toll-free bridging                                                 transfer of ownership
    __bridge           CF <-> F   transfer a pointer between F and CF  none
    __bridge_transfer  CF  -> F   moves a CF pointer to an F pointer   transfers ownership to ARC
    __bridge_retained  F   -> CF  casts an F pointer to a CF pointer   transfers ownership to caller, must call CFRelease()

    __bridge_transfer  CFBridgingRelease()  CF -> F
    __bridge_retained  CFBridgingRetain()   F  -> CF

                                                     (__bridge T)                    // CF <-> F
                                                     (__bridge void *)               // CF <-> F
    id        CFBridgingRelease(CFTypeRef X)  return (__bridge_transfer id)X         // CF  -> F
    CFTypeRef CFBridgingRetain(id X)          return (__bridge_retained CFTypeRef)X  // F   -> CF


    CFShow(CFSTR("foo"));

    CFUUIDRef uid = CFUUIDCreate(kCFAllocatorDefault);
    CFShow(uid);  // <CFUUID 0x00000000> 7E67C7A8-7DB3-4B80-AD0B-6C7E6E15A858
    CFRelease(uid);


- CoreGraphics/CGBase.h

    typedef float CGFloat;   // 32-bit
    typedef double CGFloat;  // 64-bit


- CoreGraphics/CGGeometry.h

    struct CGPoint
    {
        CGFloat x, y;
    }

    struct CGRect
    {
        CGPoint origin;
        CGSize size;
    }

    struct CGSize
    {
        CGFloat width, height;
    }

    struct CGVector
    {
        CGFloat dx, dy;
    }


- CoreGraphics/CGAffineTransform.h

    struct CGAffineTransform
    {
        CGFloat a,  b,
                c,  d,
                tx, ty;
    }
        a  b  0
        c  d  0
        tx ty 1

    CGAffineTransformIdentity


- CoreMotion/CMAttitude.h

    struct CMRotationMatrix
    {
        double m11, m12, m13;
               m21, m22, m23;
               m31, m32, m33;
    }

    // q.x*i + q.y*j + q.z*k + q.w
    struct CMQuaternion
    {
        double x, y, z, w;
    }


- CoreMotion/CMDeviceMotion.h

    CMDeviceMotion: CMLogItem


- CoreMotion/CMMotionManager.h

    CMMotionManager


- GameController/GCController.h

    GCController


- GameController/GCMotion.h

    GCMotion

    // 3-axis rotation rate data
    struct GCRotationRate
    {
        double x, y, z;
    }

    // q.x*i + q.y*j + q.z*k + q.w
    struct GCQuaternion
    {
        double x, y, z, w;
    }


- GLKit/GLKMathTypes.h

    union GLKMatrix2
    {
        struct
        {
            float m00, m01;
                  m10, m11;
        }
        float m2[2][2];
        float m[4];
    }

    union GLKMatrix3
    {
        struct
        {
            float m00, m01, m02;
                  m10, m11, m12;
                  m20, m21, m22;
        }
        float m[9];
    }

    union GLKMatrix4
    {
        struct
        {
            float m00, m01, m02, m03;
                  m10, m11, m12, m13;
                  m20, m21, m22, m23;
                  m30, m31, m32, m33;
        }
        float m[16];
    }

    union GLKVector2
    {
        struct { float x, y; }
        float v[2];
    }

    union GLKVector3
    {
        struct { float x, y, z; }
        float v[3];
    }

    union GLKVector4
    {
        struct { float x, y, z, w; }
        float v[4];
    }

    union GLKQuaternion
    {
        struct
        {
            GLKVector3 v;
            float s;  // scalar component
        }
        struct { float x, y, z, w; }
        float q[4];
    }


- GLKit/GLKMatrix3.h

    GLKMatrix3Identity


- GLKit/GLKMatrix4.h

    GLKMatrix4Identity


- GLKit/GLKMatrixStack.h

    struct _GLKMatrixStack * GLKMatrixStackRef  // 4x4 matrices


- QuartzCore/CATransform3D.h

    struct CATransform3D
    {
        CGFloat m11, m12, m13, m14;
                m21, m22, m23, m24;
                m31, m32, m33, m34;
                m41, m42, m43, m44;
    }

    CATransform3DIdentity


- SceneKit/SceneKitTypes.h

    struct SCNMatrix4
    {
        float m11, m12, m13, m14;
              m21, m22, m23, m24;
              m31, m32, m33, m34;
              m41, m42, m43, m44;
    }

    SCNMatrix4Identity

    struct SCNVector3
    {
        float x, y, z;
    }

    struct SCNVector4
    {
        float x, y, z, w;
    }

    // scene kit uses unit quaternions for node orientation, where quaternion components satisfy the equation: x*x + y*y + z*z + w*w == 1
    SCNVector4 SCNQuaternion


- SIMD/matrix_types.h

    matrix_float2x2
    matrix_float3x3
    matrix_float4x4


- SIMD/matrix.h

    matrix_identity_float3x3
    matrix_identity_float4x4  {{ ([0] = 1, [1] = 0, [2] = 0, [3] = 0)
                                 ([0] = 0, [1] = 1, [2] = 0, [3] = 0)
                                 ([0] = 0, [1] = 0, [2] = 1, [3] = 0)
                                 ([0] = 0, [1] = 0, [2] = 0, [3] = 1) }}


- SIMD/vector_types.h

    vector_float2
    vector_float3
    vector_float4

    vector_int2
    vector_int3
    vector_int4


- GLKit, Scene Kit, SIMD Conversions

    - SceneKit/SceneKit_simd.h
      scene kit to simd

        SCNMatrix4ToMat4
        SCNMatrix4FromMat4

        SCNVector3ToFloat3
        SCNVector4ToFloat4

        SCNVector3FromFloat3
        SCNVector4FromFloat4


    - SceneKit/SceneKitTypes.h
      scene kit to glkit

        SCNMatrix4FromGLKMatrix4
        SCNMatrix4ToGLKMatrix4

        SCNVector3FromGLKVector3
        SCNVector3ToGLKVector3

        SCNVector4FromGLKVector4
        SCNVector4ToGLKVector4


    - GLKit/GLKMathUtils.h

        GLKMathDegreesToRadians
        GLKMathRadiansToDegrees

        NSStringFromGLKMatrix2
        NSStringFromGLKMatrix3
        NSStringFromGLKMatrix4

        NSStringFromGLKVector2
        NSStringFromGLKVector3
        NSStringFromGLKVector4

        NSStringFromGLKQuaternion


- OC

  //--------------------------------

      typedef NS_ENUM(NSInteger, FooType) {
          None,
          Foo,
          Bar
      };


  //--------------------------------
    debug

      // file: main.m
      NSLog(@"app start, in %s %d %s %s", __FILE__, __LINE__, __PRETTY_FUNCTION__, __FUNCTION__);               // /users/db/app/app/main.m 15 int main(int, char **) main

      NSString * appDir = (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSAllDomainsMask, YES)[0])  // /var/mobile/containers/data/application/<uid>/documents

                          .stringByDeletingLastPathComponent;                                                   // /var/mobile/containers/data/application/<uid>
                                                                                                                // /users/db/library/developer/coresimulator/devices/<uid>/data/containers/data/application/<uid>

      NSLog(@"app dir: %@", appDir);
      NSLog(@"app bundle id: %@", [NSBundle mainBundle].bundleIdentifier);                                      // com.expressionSoftware.app
    
    
  //--------------------------------
    blocks
    http://goshdarnblocksyntax.com

      //         void (^b)(void) = ^(void) {
      //         void (^b)(void) = ^() {
      //         void (^b)()     = ^() {
      //         void (^b)()     = ^{
      // typedef void (^dispatch_block_t)(void);

                 dispatch_block_t b = ^{
                     NSLog(@"1");
                 };
                 b();


      //         void (^b)(NSString *) = ^(NSString * s) {
         typedef void (^b_t)(NSString *);

                 b_t b = ^(NSString * s) {
                     NSLog(@"%@", s);
                 };
                 b(@"2");


      //         int (^b)() = ^{
         typedef int (^b_t)();

                 b_t b = ^{
                     return 3;
                 };
                 NSLog(@"%d", b());


      //         NSString * (^b)(NSString *) = ^(NSString * s) {
         typedef NSString * (^b_t)(NSString *);

                 b_t b = ^(NSString * s) {
                     return s;
                 };
                 NSLog(@"%@", b(@"4"));
  
  
  //--------------------------------
    download url
    info.plist NSAppTransportSecurity > NSAllowsArbitraryLoads : YES

      -(void)syncDownloadString:(NSURL *)url {
          NSError * error;
          NSString * html = [NSString stringWithContentsOfURL:url
                                                     encoding:NSUTF8StringEncoding
                                                        error:&error];
          if (error) {
              [self logError:error];
          }
          else {
              [self showHtml:html url:url statusCode:0];
          }
      }

      -(void)asyncDownloadString:(NSURL *)url {
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){

              NSError * error;
              NSString * html = [NSString stringWithContentsOfURL:url.absoluteURL
                                                         encoding:NSUTF8StringEncoding
                                                            error:&error];
              if (error) {
                  [self logError:error];
              }
              else {
                  dispatch_async(dispatch_get_main_queue(), ^(void){

                      [self showHtml:html url:url statusCode:0];

                  });
              }
          });
      }

      -(void)sessionDataTask:(NSURL *)url {
          NSURLSessionDataTask * task = [NSURLSession.sharedSession dataTaskWithURL:url
                                                                  completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {

                                            if (error) {
                                                [self logError: error];
                                            }
                                            else {
                                                NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
                                                NSString * html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

                                                dispatch_async(dispatch_get_main_queue(), ^(void){

                                                    [self showHtml:html url:url statusCode:statusCode];

                                                });
                                            }
                                        }];

          [task resume];
      }


      -(void)logError:(NSError *)error {
          NSLog(@"error: %@", error);

          // sort dictionary keys w/NSString -(NSComparisonResult)caseInsensitiveCompare:(NSString *)aString
          for (NSString * key in [error.userInfo.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) {
              NSLog(@"%@ = %@", key, error.userInfo[key]);
          }
      }

      -(void)showHtml:(NSString *)html url:(NSURL *)url statusCode:(NSInteger)statusCode {
          NSString * status = statusCode > 0 ? [NSString stringWithFormat:@"\nstatus: %ld", (long)statusCode] : @"";
          self.textView.string = [NSString stringWithFormat:@"url: %@%@\n\n%@", url, status, html];
      }


  //--------------------------------
    save to webservice

      // file: ESWebService.h
      @import Foundation;


      @interface ESWebService: NSObject<NSURLSessionDelegate>
          
          -(instancetype)initWithURL: (NSString *)aURL;
          -(void)save: (NSData *)aData;
      @end
      
      
      // file: ESWebService.m
      #import "ESWebService.h"


      @implementation ESWebService {
          NSURLSession * session;
          NSURL * url;
      }
          
          -(instancetype)initWithURL: (NSString *)aURL {
              if (self = [super init]) {
                  session = [NSURLSession sessionWithConfiguration: NSURLSessionConfiguration.ephemeralSessionConfiguration
                                                          delegate: self
                                                     delegateQueue: NSOperationQueue.mainQueue];
                  url = [NSURL URLWithString: aURL];
              }
              
              return self;
          }

          -(void)save: (NSData *)aData {
              NSMutableURLRequest * req = [NSMutableURLRequest requestWithURL: url];
              [req setValue: @"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
              [req setValue: @"ios app"                           forHTTPHeaderField:@"User-Agent"];
              req.HTTPMethod = @"POST";
              req.HTTPBody = aData;
              
              NSURLSessionUploadTask * task = [session uploadTaskWithRequest: req
                                                                    fromData: aData
                                                           completionHandler: ^(NSData * data, NSURLResponse * response, NSError * error) {
                                                               if (error) {
                                                                   NSLog(@"error: %@", error);
                                                               }
                                                               else {
                                                                   NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
                                                                   NSLog(@"status: %ld", (long)statusCode);
                                                               }
                                                           }];
              [task resume];
          }
          
          // NSURLSessionTaskDelegate protocol (for self signed ssl cert)
          // 
          -(void)URLSession: (NSURLSession *)session
                       task: (NSURLSessionTask *)task
                       didReceiveChallenge: (NSURLAuthenticationChallenge *)challenge
                         completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential))completionHandler {
              completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]);
          }
      @end
      
      
      // file: ViewController.m
      webservice = [[ESWebService alloc] initWithURL: @"https://localhost:8443/foo/"];
      
      
      -(void)saveData {
          NSString * data = [NSString stringWithFormat: @"userName=%@&password=%@", username, password];
          data = [crypto encrypt: data];  // encrypt data w/public key, decrypt on web server backend process w/private key
          data = [data stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];

          [webservice save: [data dataUsingEncoding: NSUTF8StringEncoding]];
      }


  //--------------------------------
    regex search and replace

      -(NSString *)regexSearchAndReplaceString:(NSString *)string pattern:(NSString *)pattern template:(NSString *)template
      {
          NSError * error;
          NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];

          return error ? [NSString stringWithFormat:@"regex error: %@", error] : [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, string.length) withTemplate:template];
      }

      // block
      NSString * (^regexSearchAndReplace)(NSString *, NSString *, NSString *) = ^(NSString * string, NSString * pattern, NSString * template)


      [self regexSearchAndReplaceString:content pattern:@"\r" template:@""]
      regexSearchAndReplace(content, @"\r", @"")


  //--------------------------------
    sound completion

      @implementation ViewController
      {
          SystemSoundID boomSound;
      }

          // typedef void (*AudioServicesSystemSoundCompletionProc)(SystemSoundID ssID, void * clientData);
          void soundCompletion(SystemSoundID ssid, void * clientData)
          {
              AudioServicesRemoveSystemSoundCompletion(ssid);
              AudioServicesDisposeSystemSoundID(ssid);

              UIButton * b = (__bridge UIButton *)clientData;
              [b setImage:[UIImage imageNamed:@"default.png"] forState:UIControlStateNormal];
          }

          -(void)playSound
          {
              NSString * soundFilePath = [NSBundle.mainBundle pathForResource:@"boom.caf" ofType:nil];
              NSURL * url = [NSURL fileURLWithPath:soundFilePath];
              AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &boomSound);
           // AudioServicesAddSystemSoundCompletion(boomSound, nil, nil, soundCompletion, nil);
              AudioServicesAddSystemSoundCompletion(boomSound, nil, nil, soundCompletion, (__bridge void *)self.button);
              AudioServicesPlaySystemSound(boomSound);

              [self.button setImage:[UIImage imageNamed:@"blowdUp.png"] forState:UIControlStateNormal];
          }
      @end


  //--------------------------------
    macOS toggle bold font w/menu
    add IBAction method, then control-drag Bold menu item to First Responder icon, connect

      -(IBAction)toggleBoldText:(id)sender
      {
          NSLog(@"sender: %@", sender);  // <NSMenuItem: 0x00000000 Bold>

          NSTextView * tv = (NSTextView *)self.textView.documentView;
          NSMutableAttributedString * attrStr = tv.textStorage;

          NSRange rangePointer = NSMakeRange(0, 0);
          NSDictionary * indexAttributes = [attrStr attributesAtIndex:tv.selectedRange.location effectiveRange:&rangePointer];
          NSLog(@"index attributes: %@", indexAttributes);
          NSLog(@"range pointer: %@", NSStringFromRange(rangePointer));

          NSDictionary * selectedFontAttributes = [attrStr fontAttributesInRange:tv.selectedRange];
          NSLog(@"selected font attributes: %@", selectedFontAttributes);

          NSFont * selectedFont = selectedFontAttributes[@"NSFont"];
          NSLog(@"font: %@", selectedFont);

          NSFontTraitMask indexFontTraits = [[NSFontManager sharedFontManager] traitsOfFont:selectedFont];
          if (indexFontTraits & NSBoldFontMask)
          {
           // [[NSFontManager sharedFontManager] removeFontTrait:sender];
              [attrStr applyFontTraits:NSUnboldFontMask range:tv.selectedRange];
          }
          else
          {
           // [[NSFontManager sharedFontManager] addFontTrait:sender];
              [attrStr applyFontTraits:NSBoldFontMask range:tv.selectedRange];
          }
      }


  //--------------------------------
    macOS key view loop custom tab order

        -(void)viewDidLoad
        {
            [super viewDidLoad];

            [self.tf3 setNextKeyView:self.tf2];
            [self.tf2 setNextKeyView:self.tf1];
            [self.tf1 setNextKeyView:self.tf3];
        }

        -(void)viewWillAppear
        {
            [self.view.window setInitialFirstResponder:self.tf3];

            [super viewWillAppear];
        }


  //--------------------------------
    byte array to base64 string

      const char * chars = @"foo bar".UTF8String;
   // char chars[] = { 102, 111, 111, 32, 98, 97, 114 };

      int len = sizeof(chars)/sizeof(char);

      NSData * data = [NSData dataWithBytes:chars length:len];
      NSString * base64String = [data base64EncodedStringWithOptions:0];
      NSLog(@"%@", base64String);  // Zm9vIGJhcgA=


  //--------------------------------
    uuids

      NSUUID * uid = [NSUUID UUID];
      printf("%s\n", uid.UUIDString.UTF8String);  // 00000000-0000-0000-0000-000000000000

      uuid_t bytes;  // typedef unsigned char __darwin_uuid_t[16];
      [uid getUUIDBytes:bytes];
      for (int i = 0; i< sizeof(bytes)/sizeof(unsigned char); i++)
      {
          printf("%d ", bytes[i]);  // 11 36 135 226 133 84 71 138 162 69 163 112 214 72 247 218
      }


  //--------------------------------
    null dictionary values

      NSString * k1 = @"k1";
      NSString * k2 = @"k2";
      NSString * k3 = @"k3";
      NSString * k4 = @"k4";

      NSString * s1 = @"foo";
      NSString * s2 = nil ?: (NSString*)[NSNull null];
      NSString * s3 = @"";
      NSString * s4 = @"bar";

      NSDictionary * d = @{ k1 : s1,
                            k2 : s2,
                            k3 : s3,
                            k4 : s4 };

      for (NSString * k in d)
      {
          if (![[d objectForKey:k] isEqual:[NSNull null]])
          {
              NSLog(@"%@ = %@", k, [d objectForKey:k]);
          }
      }


      // output
      k4 = bar
      k3 =
      k1 = foo
  
  
  //--------------------------------
    collection operators, flatten nested arrays
    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html

      [@[@[@1, @2, @3]]              valueForKeyPath: @"@unionOfArrays.self"]  // 1, 2, 3
      [@[@[@1, @2], @[@3]]           valueForKeyPath: @"@unionOfArrays.self"]
      [@[@[@1, @2, @3, @4]]          valueForKeyPath: @"@unionOfArrays.self"]  // 1, 2, 3, 4
      [@[@[@1, @2, @[@3][0]], @[@4]] valueForKeyPath: @"@unionOfArrays.self"]
      [@[@[@1, @2], @[@3], @[@4]]    valueForKeyPath: @"@unionOfArrays.self"]
      [@[@[@1, @2, @3], @[@4]]       valueForKeyPath: @"@unionOfArrays.self"]


  //--------------------------------
    OC++ GLKit

      #import <GLKit/GLKit.h>
      #include <iostream>
      #include <vector>

          using std::vector;
          using std::cout;
          using std::endl;

          vector<float> data = { 0, 0
                               , 0.0, 0.0

                               , 0, 0
                               , 1, 1

                               , -1, -1
                               , 1, 1

                               , 1.5, 10.5
                               , -2, -20
                               };
          GLKVector2 v1;
          GLKVector2 v2;
          GLKVector2 v3;

          for (int i = 0, n = 1; i < data.size(); i++, n++) {
              v1 = GLKVector2Make(data[i], data[i + 1]);
              v2 = GLKVector2Make(data[i + 2], data[i + 3]);
              v3 = GLKVector2Add(v1, v2);

           // printf("%d. %s + %s = %s\n", n, CStringFromGLKVector2(v1), CStringFromGLKVector2(v2), CStringFromGLKVector2(v3));
              cout << n << ". " << CStringFromGLKVector2(v1) << " + " << CStringFromGLKVector2(v2) << " = " << CStringFromGLKVector2(v3) << endl;

              i += 3;
          }


      // output
      1. {0, 0} + {0, 0} = {0, 0}
      2. {0, 0} + {1, 1} = {1, 1}
      3. {-1, -1} + {1, 1} = {0, 0}
      4. {1.5, 10.5} + {-2, -20} = {-0.5, -9.5}


  //--------------------------------
    glkit utils

      const char * CStringFromGLKVector2(GLKVector2 aVector) {
          return NSStringFromGLKVector2(aVector).UTF8String;
      }

      const char * CStringFromGLKVector3(GLKVector3 aVector) {
          return NSStringFromGLKVector3(aVector).UTF8String;
      }


      // OC++
      #ifdef __cplusplus
      NSString * NSStringFromGLKVector2(GLKVector2 aVector) {
          return [NSString stringWithFormat:@"{%g, %g}", aVector.x, aVector.y];
      }

      NSString * NSStringFromGLKVector3(GLKVector3 aVector) {
          return [NSString stringWithFormat:@"{%g, %g, %g}", aVector.x, aVector.y, aVector.z];
      }
      #endif


  //--------------------------------
    glkit matrix rows columns

      /* GLKMatrix4Identity  {{1, 0, 0, 0},
                              {0, 1, 0, 0},
                              {0, 0, 1, 0},
                              {0, 0, 0, 1}} */

      float matrixData[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

      GLKMatrix4 m = GLKMatrix4MakeWithArray(matrixData);  /* {{0, 1, 2, 3},
                                                               {4, 5, 6, 7},
                                                               {8, 9, 10, 11},
                                                               {12, 13, 14, 15}} */

      NSMutableString * mRows = [NSMutableString string];
      NSMutableString * mCols = [NSMutableString string];
      for (int i = 0; i <= 3; i++)
      {
          [mRows appendFormat:@"row %i: %@\n", i, NSStringFromGLKVector4(GLKMatrix4GetRow(m, i))];
          [mCols appendFormat:@"col %i: %@\n", i, NSStringFromGLKVector4(GLKMatrix4GetColumn(m, i))];
      }

      NSLog(@"%@\n%@", mRows, mCols);


      // output
      row 0: {0, 4, 8, 12}
      row 1: {1, 5, 9, 13}
      row 2: {2, 6, 10, 14}
      row 3: {3, 7, 11, 15}
      
      col 0: {0, 1, 2, 3}
      col 1: {4, 5, 6, 7}
      col 2: {8, 9, 10, 11}
      col 3: {12, 13, 14, 15}
  
  
  //--------------------------------
    glkit multiplication

      GLKMatrix4 m = GLKMatrix4MakeTranslation(2, 4, 6);  /* {{1, 0, 0, 0},
                                                              {0, 1, 0, 0},
                                                              {0, 0, 1, 0},
                                                              {2, 4, 6, 1}} */

      GLKVector4 v = GLKVector4Make(1, 2, 3, 1);  // x, y, z, w

      // matrix * vector = vector
      GLKVector4 mv = GLKMatrix4MultiplyVector4(m, v);  // {3, 6, 9, 1}

      // matrix * matrix = matrix
      GLKMatrix4 mm = GLKMatrix4Multiply(m, m);  /* {{1, 0, 0, 0},
                                                     {0, 1, 0, 0},
                                                     {0, 0, 1, 0},
                                                     {4, 8, 12, 1}} */


      // matrix * vector formula by index
      m               v     +                         v
       0  1  2  3  *  0  =  m0v0 m4v1  m8v2 m12v3  =  x
       4  5  6  7     1     m1v0 m5v1  m9v2 m13v3     y
       8  9 10 11     2     m2v0 m6v1 m10v2 m14v3     z
      12 13 14 15     3     m3v0 m7v1 m11v2 m15v3     w

      // alpha
      m        v     +               v
      abcd  *  x  =  ax ey iz mw  =  x
      efgh     y     bx fy jz nw     y
      ijkl     z     cx gy kz ow     z
      mnop     w     dx hy lz pw     w

      // data
      m        v     +        v
      1000  *  1  =  1002  =  3 x
      0100     2     0204     6 y
      0010     3     0036     9 z
      2461     1     0001     1 w


      //--------------------------------
        simd version
        simd/matrix.h

          matrix_float4x4 m = { .columns[0] = {1, 0, 0, 0},
                                .columns[1] = {0, 1, 0, 0},
                                .columns[2] = {0, 0, 1, 0},
                                .columns[3] = {2, 4, 6, 1} };  /* {{ ([0] = 1, [1] = 0, [2] = 0, [3] = 0)
                                                                     ([0] = 0, [1] = 1, [2] = 0, [3] = 0)
                                                                     ([0] = 0, [1] = 0, [2] = 1, [3] = 0)
                                                                     ([0] = 2, [1] = 4, [2] = 6, [3] = 1) }} */


          vector_float4 v = {1, 2, 3, 1};  // ([0] = 1, [1] = 2, [2] = 3, [3] = 1)

          vector_float4 mv = matrix_multiply(m, v);  // ([0] = 3, [1] = 6, [2] = 9, [3] = 1)

          matrix_float4x4 mm = matrix_multiply(m, m);  /* {{ ([0] = 1, [1] = 0, [2] = 0, [3] = 0)
                                                             ([0] = 0, [1] = 1, [2] = 0, [3] = 0)
                                                             ([0] = 0, [1] = 0, [2] = 1, [3] = 0)
                                                             ([0] = 4, [1] = 8, [2] = 12, [3] = 1) }} */


      //--------------------------------
        blender python version

          import mathutils

          m = Matrix.Translation((2, 4, 6, 1))  # Matrix(((1.0, 0.0, 0.0, 2.0),
                                                #         (0.0, 1.0, 0.0, 4.0),
                                                #         (0.0, 0.0, 1.0, 6.0),
                                                #         (0.0, 0.0, 0.0, 1.0)))

          v = Vector((1, 2, 3, 1))  # Vector((1.0, 2.0, 3.0, 1.0))

          mv = m * v  # Vector((3.0, 6.0, 9.0, 1.0))

          mm = m * m  # Matrix(((1.0, 0.0, 0.0, 4.0),
                      #         (0.0, 1.0, 0.0, 8.0),
                      #         (0.0, 0.0, 1.0, 12.0),
                      #         (0.0, 0.0, 0.0, 1.0)))


      //--------------------------------
        r version

          m4Identity = diag(4)  #      [,1] [,2] [,3] [,4]
                                # [1,]    1    0    0    0
                                # [2,]    0    1    0    0
                                # [3,]    0    0    1    0
                                # [4,]    0    0    0    1

          m = m4Identity
          m[4,] = c(2, 4, 6, 1)  #      [,1] [,2] [,3] [,4]
                                 # [1,]    1    0    0    0
                                 # [2,]    0    1    0    0
                                 # [3,]    0    0    1    0
                                 # [4,]    2    4    6    1

          v = c(1, 2, 3, 1)  # [1] 1 2 3 1

          #vector * matrix = matrix
          mv = v %*% m  #      [,1] [,2] [,3] [,4]
                        # [1,]    3    6    9    1

          #matrix * matrix = matrix
          mm = m %*% m  #      [,1] [,2] [,3] [,4]
                        # [1,]    1    0    0    0
                        # [2,]    0    1    0    0
                        # [3,]    0    0    1    0
                        # [4,]    4    8   12    1

          mm[4,]            # [1] 4 8 12 1
          as.vector(mm)     # [1] 1 0 0 4  0 1 0 8  0 0 1 12  0 0 0 1
          as.vector(t(mm))  # [1] 1 0 0 0  0 1 0 0  0 0 1 0  4 8 12 1


  //--------------------------------
    simd/geometry.h
    geometry functions (float only)

      // simd vs glkit
      vector_float2 v = {skNode.physicsBody.velocity.dx, skNode.physicsBody.velocity.dy};
      vector_length(v)  // float

      GLKVector2 v = GLKVector2Make(skNode.physicsBody.velocity.dx, skNode.physicsBody.velocity.dy);
      GLKVector2Length(v)  // float


      vector_length_squared((vector_float2){skNode.physicsBody.velocity.dx, skNode.physicsBody.velocity.dy}))


  //--------------------------------
    image with color, alt to CIConstantColorGenerator core image filter

      CIImage * getImageWithColor(CIColor * aColor, CGSize aSize)
      {
          CIImage * image = [CIImage imageWithColor:aColor];
          return [image imageByCroppingToRect:CGRectMake(0, 0, aSize.width, aSize.height)];
      }


  //--------------------------------
    assets library, image picker controller
    in iOS 8, the photos framework replaces the assets library, but does not provide an image picker control

      // conform to protocol
      @interface ViewController: UIViewController<UIImagePickerControllerDelegate>


      -(void)pickImage
      {
          UIImagePickerController * ipc = [UIImagePickerController new];
          ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
          ipc.delegate = (id)self;
          [self presentViewController:ipc animated:YES completion:nil];
      }

      // UIImagePickerControllerDelegate
      -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
      {
          NSLog(@"image picked, info: %@", info);  /* UIImagePickerControllerMediaType = "public.image";
                                                      UIImagePickerControllerOriginalImage = "<UIImage:0x00000000> size {2048, 1536} orientation 0 scale 1.000000";
                                                      UIImagePickerControllerReferenceURL = "assets-library://asset/asset.JPG?id=5570A4B1-7E8B-434B-8BDB-F02538FD0B76&ext=JPG"; */

          [self dismissViewControllerAnimated:YES completion:nil];  // dismiss picker

       // NSURL * url = [info objectForKey:UIImagePickerControllerReferenceURL];
          UIImage * image = [info objectForKey:UIImagePickerControllerOriginalImage];
          [self imageView].image = image;
      }

      // UIImagePickerControllerDelegate
      -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
      {
          NSLog(@"image picker cancelled");
          [self dismissViewControllerAnimated:YES completion:nil];  // dismiss picker
      }


  //--------------------------------
    load image using photos framework or assets library framework

      #import <AssetsLibrary/AssetsLibrary.h>  // ALAssetsLibrary
      @import Photos;


      NSString * photoAssetLocalID = @"A3277D72-1FE7-4627-8534-D5B78C3A57D2/L0/001";
      [self loadPhotoAssetFromLocalID:photoAssetLocalID];

      NSURL * alPhotoAssetURL = [NSURL URLWithString:@"assets-library://asset/asset.JPG?id=5570A4B1-7E8B-434B-8BDB-F02538FD0B76&ext=JPG"];
      [self loadPhotoAssetFromURL:alPhotoAssetURL];


      -(void)loadPhotoAssetFromLocalID:(NSString *)aPhotoAssetLocalID
      {
          NSArray * photoAssetLocalID = @[aPhotoAssetLocalID];

          PHFetchResult * fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:photoAssetLocalID options:nil];
          if (fetchResult.count > 0)
          {
              PHAsset * photoAsset = fetchResult[0];
              [self loadPhotoAsset:photoAsset];
          }
      }

      -(void)loadPhotoAssetFromURL:(NSURL *)aALPhotoAssetURL
      {
          NSArray * alPhotoAssetURL = @[aALPhotoAssetURL];

          PHFetchResult * fetchResult = [PHAsset fetchAssetsWithALAssetURLs:alPhotoAssetURL options:nil];
          if (fetchResult.count > 0)
          {
              PHAsset * photoAsset = fetchResult[0];
              [self loadPhotoAsset:photoAsset];
          }
      }

      -(void)loadPhotoAsset:(PHAsset *)aPhotoAsset
      {
          CGSize imageSize = CGSizeMake(aPhotoAsset.pixelWidth, aPhotoAsset.pixelHeight);
          NSLog(@"image size w x h: %@\n", NSStringFromCGSize(imageSize));  // image size w x h: {1536, 2048}

          PHImageRequestOptions * options = [PHImageRequestOptions new];
          options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;

          PHImageManager * im = [PHImageManager defaultManager];
          [im requestImageForAsset:aPhotoAsset
                        targetSize:imageSize
                       contentMode:PHImageContentModeDefault
                           options:options
                     resultHandler:^(UIImage * result, NSDictionary * info)
                                   {
                                       // result handler executes on main thread
                                       NSLog(@"info: %@", info);  /* PHImageFileOrientationKey = 0;
                                                                     PHImageFileSandboxExtensionTokenKey = "...";
                                                                     PHImageFileURLKey = "file:///var/mobile/Media/PhotoStreamsData/1234567890/100APPLE/IMG_0999.JPG";  // photo stream
                                                                     //                   file:///var/mobile/Media/DCIM/100APPLE/IMG_0123.JPG                           // camera roll
                                                                     PHImageFileUTIKey = "public.jpeg";
                                                                     PHImageResultDeliveredImageFormatKey = 9999;
                                                                     PHImageResultIsDegradedKey = 0;
                                                                     PHImageResultIsInCloudKey = 0;
                                                                     PHImageResultIsPlaceholderKey = 0;
                                                                     PHImageResultRequestIDKey = 1;
                                                                     PHImageResultWantedImageFormatKey = 4037; */

                                       [self loadImage:result];
                                   }];
      }

      // on iOS 8, using assets library to load photo with url works only with camera roll, not photo stream
      -(void)loadPhotoFromCameraRollWithAssetsLibrary:(NSURL *)aALPhotoAssetURL
      {
          ALAssetsLibrary * assetLib = [ALAssetsLibrary new];

          // assetForURL is async
          [assetLib assetForURL:aALPhotoAssetURL

                    resultBlock:^(ALAsset * asset)
                    {
                        ALAssetRepresentation * assetRep = [asset defaultRepresentation];
                        CGImageRef cgImage = [assetRep fullResolutionImage];

                        if (cgImage != nil)  // image exists
                        {
                            UIImage * image = [[UIImage alloc] initWithCGImage:cgImage];
                            [self loadImage:image];
                        }
                        else  // image does not exist, deleted, etc
                        {
                            NSLog(@"image not available");
                        }
                    }

                    failureBlock:^(NSError * error)
                    {
                        NSLog(@"image access denied");
                    }];
      }

      -(void)loadImage:(UIImage *)aImage
      {
          [self imageView].image = aImage;
      }


  //--------------------------------
    keychain security

      #import <Security/Security.h>


      NSString * service;  // foo service
      NSString * key;      // foo key


      -(void)setupKeychain
      {
          NSString * secret = @"foo";
          NSData * secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];

          NSDictionary * keychainAttributes = @{ (__bridge id)kSecClass       : (__bridge id)kSecClassGenericPassword,
                                                 (__bridge id)kSecAttrService : service,
                                                 (__bridge id)kSecAttrAccount : key,
                                                 (__bridge id)kSecValueData   : secretData };

          [self addKeychainItem:keychainAttributes];
      }

      -(void)getKeychainItem
      {
          NSDictionary * keychainSearchAttributes = @{(__bridge id)kSecClass       : (__bridge id)kSecClassGenericPassword,
                                                      (__bridge id)kSecMatchLimit  : (__bridge id)kSecMatchLimitOne,
                                                      (__bridge id)kSecAttrService : service,
                                                      (__bridge id)kSecAttrAccount : key,
                                                      (__bridge id)kSecReturnData  : (__bridge id)kCFBooleanTrue };

          NSString * result = [self searchKeychain:keychainSearchAttributes];
          NSLog(@"result: %@", result);
      }

      -(BOOL)addKeychainItem:(NSDictionary *)aKeychainAttributes
      {
          BOOL result;
          CFTypeRef kcResultCode = NULL;

          OSStatus status = SecItemAdd((__bridge CFDictionaryRef)aKeychainAttributes, &kcResultCode);
          if (status == errSecSuccess)
          {
              NSLog(@"keychain item added");
              result = YES;
          }
          else
          {
              NSLog(@"keychain error code: %ld", (long)status);
          }

          return result;
      }

      -(NSString *)searchKeychain:(NSDictionary *)aKeychainSearchAttributes
      {
          NSString * result;
          CFTypeRef kcResultCode = NULL;

          OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)aKeychainSearchAttributes, &kcResultCode);
          if (status == errSecSuccess)
          {
              NSLog(@"keychain item found");
              NSData * resultData = (__bridge NSData *)kcResultCode;
              result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
          }
          else
          {
              NSLog(@"keychain error code: %ld", (long)status);
          }

          return result;
      }


  //--------------------------------
    qr code core image filter
    https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9328Iiz8qja5QkuI0H8pCuM2vablWRRQD1X3L47K9oR-BaObCFZTx5T5N0mTmKRgBushr9728yIJ7WdlU8JV7x8HO87v1ebx4_nP-iBZVUKTkbteFip1ldXhWEdtZMbKCAjQ47cjq67AY/s0/fooQRCode.png

      @property (nonatomic, weak) IBOutlet UIImageView * qrCode;


      NSString * message = @"foo";
      NSData * messageData = [message dataUsingEncoding:NSISOLatin1StringEncoding];
      self.qrCode.image = [self getQRCode:messageData size:self.qrCode.bounds.size];


      -(UIImage *)getQRCode:(NSData *)aMessageData size:(CGSize)aSize
      {
          CIFilter * filter = [CIFilter filterWithName:@"CIQRCodeGenerator"
                                        withInputParameters:@{ @"inputMessage"         : aMessageData,
                                                               @"inputCorrectionLevel" : @"L" }];
          CIImage * filterOutputImage = [filter outputImage];
          UIImage * qrCode = [UIImage imageWithCIImage:filterOutputImage scale:1.0f orientation:UIImageOrientationUp];

          UIGraphicsBeginImageContextWithOptions(aSize, YES, 1.0f);
          CGContextRef context = UIGraphicsGetCurrentContext();
          CGContextSetInterpolationQuality(context, kCGInterpolationNone);  // 1

          [qrCode drawInRect:CGRectMake(0.0f, 0.0f, aSize.width, aSize.height)];  // error: BSXPCMessage received error for message: Connection interrupted
          qrCode = UIGraphicsGetImageFromCurrentImageContext();
          UIGraphicsEndImageContext();

          return qrCode;
      }


  //--------------------------------
    calayer bezier path

      // file: ESLine.h
      @import UIKit;


      @interface ESLine: CALayer

          -(instancetype)initWithRect:(CGRect)aRect;

          @property (nonatomic) UIBezierPath * path;
          @property (nonatomic) UIColor * color;
      @end


      // file: ESLine.m
      @import UIKit;
      #import "ESLine.h"


      @implementation ESLine

          -(instancetype)init
          {
              if (self = [super init])
              {
                  self.contentsScale = UIScreen.mainScreen.scale;
              }

              return self;
          }

          -(instancetype)initWithRect:(CGRect)aRect
          {
              if (self = [self init])
              {
                  self.frame = aRect;
              }

              return self;
          }

          -(void)drawInContext:(CGContextRef)ctx
          {
              UIGraphicsPushContext(ctx);
              [_color setStroke];
              [_path stroke];
              UIGraphicsPopContext();
          }
      @end


      // functions
      UIBezierPath * getLine(CGPoint, CGPoint);
      UIBezierPath * getHorizontalLine(CGSize);
      UIBezierPath * getVerticalLine(CGSize);


      UIBezierPath * getLine(CGPoint aStartPoint, CGPoint aEndPoint)
      {
          UIBezierPath * line = [UIBezierPath bezierPath];
          [line moveToPoint:aStartPoint];
          [line addLineToPoint:aEndPoint];

          return line;
      }

      UIBezierPath * getHorizontalLine(CGSize aSize)
      {
          CGFloat y = aSize.height/2.0f;
          CGPoint startPoint = CGPointMake(0.0f, y);
          CGPoint endPoint = CGPointMake(aSize.width, y);

          UIBezierPath * line = getLine(startPoint, endPoint);

          return line;
      }

      UIBezierPath * getVerticalLine(CGSize aSize)
      {
          CGFloat x = aSize.width/2.0f;
          CGPoint startPoint = CGPointMake(x, 0.0f);
          CGPoint endPoint = CGPointMake(x, aSize.height);

          UIBezierPath * line = getLine(startPoint, endPoint);

          return line;
      }


      // example
      ESLine * line = [[ESLine alloc] initWithRect:self.view.frame];
      line.color = [UIColor grayColor];
      line.path = getHorizontalLine(line.frame.size);
      line.path.lineWidth = 2.0f;

      CGFloat dashes[] = {10, 8};
      [line.path setLineDash:dashes count:2 phase:10];

      [self.view.layer addSublayer:line];
      [line setNeedsDisplay];


  //--------------------------------
    calayer border

      [self showLayerBorder:button.layer color:[UIColor grayColor]];


      -(void)showLayerBorder:(CALayer *)aLayer color:(UIColor *)aColor
      {
          [aLayer setMasksToBounds:YES];
          [aLayer setCornerRadius:0.0f];
          [aLayer setBorderWidth:1.0f];
          [aLayer setBorderColor:aColor.CGColor];
      }


  //--------------------------------
    debug autolayout constraints

      -(void)viewWillAppear:(BOOL)animated
      {
          [self setupConstraints];

          [super viewWillAppear:animated];
      }

      -(void)viewDidAppear:(BOOL)animated
      {
          [super viewDidAppear:animated];

          debugAutolayoutConstraints(nodeView, @"node view");
          debugAutolayoutConstraints(self.view, @"self.view");
      }

      -(void)setupConstraints
      {
          self.view.translatesAutoresizingMaskIntoConstraints = NO;
          nodeView.translatesAutoresizingMaskIntoConstraints = NO;

          // ...
      }

      void debugAutolayoutConstraints(UIView * aView, NSString * aViewDescription)
      {
          printf("\n");
          NSLog(@"Debug Autolayout Constraints");
          printf("view: %s %p\n", aViewDescription.UTF8String, aView);
          printf("frame: %s\n", NSStringFromCGRect(aView.frame).UTF8String);
          printf("ambiguous layout: %d\n", aView.hasAmbiguousLayout);


              void (^printConstraints)(NSArray *, NSString *) = ^(NSArray * constraints, NSString * constraintsDescription)
              {
                  if (constraints.count > 0)
                  {
                      printf("\n%s constraints (%lu)\n", constraintsDescription.UTF8String, (unsigned long)constraints.count);
                      for (int i = 0; i < constraints.count; i++)
                      {
                          NSString * c = [NSString stringWithFormat:@"%@", (NSLayoutConstraint *)constraints[i]];
                          printf("%d. %s\n", (i + 1), c.UTF8String);
                      }
                  }
              };


          printConstraints([aView constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal], @"H");
          printConstraints([aView constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical],   @"V");
          printConstraints([aView constraints], @"All");
      }


  //--------------------------------
    autolayout with dynamic controls

      NSMutableArray * buttons;
      NSArray * buttonTitles = @[ @"Foo",
                                  @"Bar",
                                  @"Button",
                                  @"Button 4",
                                  @"Button 5",
                                  @"Button 6" ];

      NSMutableDictionary * bindings;
      NSArray * buttonBindings = @[ @"b0",
                                    @"b1",
                                    @"b2",
                                    @"b3",
                                    @"b4",
                                    @"b5" ];

      NSMutableArray * bHorizontalConstraints;
      NSMutableArray * bVerticalConstraints;

      UIView * topLayoutGuide = (UIView *)self.topLayoutGuide;
      NSLayoutConstraint * top = [NSLayoutConstraint constraintWithItem:headerLabel
                                                              attribute:NSLayoutAttributeTop
                                                              relatedBy:NSLayoutRelationEqual
                                                                 toItem:topLayoutGuide
                                                              attribute:NSLayoutAttributeBottom
                                                             multiplier:1.0f
                                                               constant:0.0f];


      -(void)setupBindings
      {
          bindings = [NSMutableDictionary dictionary];

          for (int i = 0; i < buttons.count; i++)
          {
              bindings[buttonBindings[i]] = buttons[i];
          }
      }

      -(void)setupConstraints
      {
          self.view.translatesAutoresizingMaskIntoConstraints = NO;

          bHorizontalConstraints = [NSMutableArray array];
          bVerticalConstraints   = [NSMutableArray array];

          [self setupHorizontalConstraints];
          [self setupVerticalConstraints];

          [self updateFont];
      }

      -(void)setupHorizontalConstraints
      {
          BOOL port = UIApplication.sharedApplication.statusBarOrientation == UIDeviceOrientationPortrait;

          NSDictionary * metrics = @{ @"maxWidth" : @(self.view.frame.size.width * (port ? 0.6f : 0.25f)) };

          for (int i = 0; i < buttons.count; i++)
          {
              NSString * vf = [NSString stringWithFormat:@"H:|-[%@(maxWidth)]", buttonBindings[i]];  // H:|-[b0(maxWidth)]
              NSArray * c = [NSLayoutConstraint constraintsWithVisualFormat:vf options:0 metrics:metrics views:bindings];

              [self.view addConstraints:c];
              [bHorizontalConstraints addObjectsFromArray:c];
          }
      }

      -(void)setupVerticalConstraints
      {
          NSString * vf = @"V:|-yPos-[b0]-[b1(b0)]-[b2(b0)]-[b3(b0)]-[b4(b0)]-[b5(b0)]-(8@800)-|";

          NSDictionary * metrics = @{ @"yPos" : @((self.view.frame.size.height * 0.33f)) };
          NSArray * c = [NSLayoutConstraint constraintsWithVisualFormat:vf options:0 metrics:metrics views:bindings];

          [self.view addConstraints:c];
          [bVerticalConstraints addObjectsFromArray:c];
      }

      -(void)updateViewConstraints
      {
          [self.view removeConstraints:bHorizontalConstraints];
          [self.view removeConstraints:bVerticalConstraints];

          [self setupHorizontalConstraints];
          [self setupVerticalConstraints];

          [self updateFont];

          [super updateViewConstraints];
      }


  //--------------------------------
    autolayout pin views opposite horizontally

      -(void)setupConstraints
      {
          self.view.translatesAutoresizingMaskIntoConstraints = NO;
          a.translatesAutoresizingMaskIntoConstraints = NO;
          b.translatesAutoresizingMaskIntoConstraints = NO;

          NSDictionary * bindings = NSDictionaryOfVariableBindings(a, b);
          NSMutableArray * constraints = [NSMutableArray array];

          [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[a(40)]-(>=0)-[b(a)]-|"
                                                                                   options:NSLayoutFormatAlignAllTop
                                                                                   metrics:nil
                                                                                     views:bindings]];

          [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-50-[a(20)]"
                                                                                   options:0
                                                                                   metrics:nil
                                                                                     views:bindings]];

          // set B view vertical constraint == A view
          [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[b(a)]"
                                                                                   options:0
                                                                                   metrics:nil
                                                                                     views:bindings]];

          [self.view addConstraints:constraints];
      }


  //--------------------------------
    set mutable array property with mutable or immutable array
    if array param responds to addObject: then array param is mutable, no copy required
    [array isMemberOfClass:[NSMutableArray class]] always returns false because of param type
    
      -(void)setMutableArray: (NSArray *)array {
          self.mutableArray = [array respondsToSelector: @selector(addObject:)] ? array : [array mutableCopy];
      }
    
    
  //--------------------------------
    tap gesture recognizer

      -(void)viewDidLoad
      {
          [super viewDidLoad];

          UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
          [self.view addGestureRecognizer:tap];
      }

      -(void)tap:(UIGestureRecognizer *)sender
      {
          NSLog(@"gr state: %ld", (long)sender.state);
          NSLog(@"gr number of touches: %lu", (unsigned long)[sender numberOfTouches]);
          NSLog(@"gr location: %@", NSStringFromCGPoint([sender locationInView:sender.view]));
      }


  //--------------------------------
    NSDate - (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate

      instance  param date   result
      self      anotherDate
      before    after        < 0
      after     before       > 0
      =         =              0


  //--------------------------------
    table view data source

      // file: TableViewDataSource.h
      @import UIKit;


      @interface TableViewDataSource : NSObject <UITableViewDataSource>

      @property (nonatomic, strong) NSArray <NSString *> * data;
      @end


      // file: TableViewDataSource.m
      #import "TableViewDataSource.h"


      @implementation TableViewDataSource

      - (NSUInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
          return self.data.count;
      }

      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          static NSString * cellID = @"cellID";  // matches the ID that is set in the storyboard scene table view cell

          UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
          cell.textLabel.text = self.data[indexPath.row];

          return cell;
      }
      @end


  //--------------------------------
    kvc kvo

      @interface LocationInfo : NSObject

          @property (nonatomic, assign) CGPoint point;
          @property (nonatomic, strong) NSDate * date;
          @property (nonatomic, strong, readonly) NSUUID * uid;
      @end


      @interface LocationViewController : NSViewController

          @property (nonatomic, strong) LocationInfo * locationInfo;
      @end


      @implementation ViewController

      - (void)mouseUp:(NSEvent *)event {
          NSStoryboard * storyBoard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
          LocationViewController * locationVC = [storyBoard instantiateControllerWithIdentifier:@"locationVC_sbid"];
          [self presentViewControllerAsModalWindow:locationVC];
      }
      @end


      static void * const KVOContext = (void *)&KVOContext;


      @implementation LocationInfo

      - (instancetype)initWithPoint:(CGPoint)point {
          if (self == [super init]) {
              _point = point;
              _date  = [NSDate date];
              _uid   = [NSUUID UUID];
          }

          return self;
      }

      -(id)valueForUndefinedKey:(NSString *)key {
          return [NSString stringWithFormat:@"KVC getter error, undefined key: %@", key];
      }

      - (NSString *)description {
          return [NSString stringWithFormat:@"x: %f, y: %f, date: %@, uid: %@", self.point.x,
                                                                                self.point.y,
                                                                                self.date,
                                                                                self.uid];
      }
      @end


      @implementation LocationViewController

      - (void)viewDidLoad {
          [super viewDidLoad];

          self.locationInfo = [[LocationInfo alloc] initWithPoint:CGPointZero];
          [self addKeyValueObservers];

          NSLog(@"locationInfo: %@", self.locationInfo);


          // kvc getter
          NSValue * val = [self.locationInfo valueForKey:@"point"];
          CGPoint point = [val pointValue];

          NSDate * date = [self.locationInfo valueForKey:@"date"];
          NSUUID * uid  = [self.locationInfo valueForKey:@"uid"];

          NSLog(@"point: %@", NSStringFromPoint(point));
          NSLog(@"date: %@", date);
          NSLog(@"id: %@",  uid);


          // kvc setter
          point = CGPointMake(100, 200);
          val   = [NSValue valueWithPoint:point];
          [self.locationInfo setValue:val forKey:@"point"];
          [self.locationInfo setValue:[NSDate date] forKey:@"date"];

          [self.locationInfo setValue:[NSUUID UUID] forKey:@"uid"];   // set readonly property
          [self.locationInfo setValue:nil           forKey:@"uid"];

          NSLog(@"error val: %@", [self.locationInfo valueForKey:@"foo"]);
      }

      - (void)addKeyValueObservers {
          [self.locationInfo addObserver:self
                              forKeyPath:@"point"
                                 options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
                                 context:KVOContext];

          [self.locationInfo addObserver:self
                              forKeyPath:@"date"
                                 options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
                                 context:KVOContext];

          [self.locationInfo addObserver:self
                              forKeyPath:@"uid"
                                 options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
                                 context:KVOContext];
      }

      - (void)observeValueForKeyPath:(NSString *)keyPath
                            ofObject:(id)object
                              change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                             context:(void *)context {

          NSLog(@"observeValueForKeyPath: %@, old: %@, new: %@", keyPath,
                                                                 change[NSKeyValueChangeOldKey],
                                                                 change[NSKeyValueChangeNewKey]);
      }

      - (void)mouseUp:(NSEvent *)event {
          self.locationInfo.point = [event locationInWindow];
          self.locationInfo.date  = [NSDate date];

          [self.locationInfo setValue:[NSUUID UUID] forKey:@"uid"];  // set readonly property
      }

      - (void)dealloc {
          [self.locationInfo removeObserver:self
                                 forKeyPath:@"point"
                                    context:KVOContext];

          [self.locationInfo removeObserver:self
                                 forKeyPath:@"date"
                                    context:KVOContext];

          [self.locationInfo removeObserver:self
                                 forKeyPath:@"uid"
                                    context:KVOContext];

          self.locationInfo = nil;
      }
      @end


      IB Storyboard - Bindings Inspector
      Value
      Bind To: Location  // ViewController
      Model Key Path: self.locationInfo.point

      // file: main.storyboard
      <!--Location-->
      <scene sceneID="100-00-000">
          <objects>
              <viewController id="200-00-000">
                  <view>
                      <subviews>
                          <textField>
                              <connections>
                                  <binding destination="200-00-000" name="value" keyPath="self.locationInfo.point" id="300-00-000"/>
                              </connections>
                          </textField>
                      </subviews>
                  </view>
              </viewController>
          </objects>
      </scene>


  //--------------------------------
    init

      -(instancetype)init
      {
          if (self = [super init])
          {
              // ...
          }

          return self;
      }

      -(instancetype)initWithSize:(CGSize)size
      {
          if (self = [super init])
          {
              // ...
          }

          return self;
      }


  //--------------------------------
    unit test

      -(void)testRandomBounds
      {
          const int minLowValue = 3;
          const int randomUpperBound = 10;

          const int minIterations = 100;
          const int maxIterations = 100000;

          int min = randomUpperBound;
          int max = 0;
          int r = 0;
          int i = 0;
          for (; i < maxIterations; i++)
          {
              r = fmax(arc4random_uniform(randomUpperBound), minLowValue);
              min = r < min ? r : min;
              max = r > max ? r : max;

          //*
              if ((i >= minIterations) && (max >= (randomUpperBound - 1)) && (min <= minLowValue))
                  break; //*/
          }

          // expected min 3, max 9
          XCTAssert(min == minLowValue);
          XCTAssert(max == (randomUpperBound - 1));
          printf("\nmin: %d, max: %d, iterations: %d\n\n", min, max, i);
      }


  //--------------------------------
    unit test
    lower <= random < upper bound
    lower increases each iteration

      -(void)testRandomLowerToUpperBound
      {
          const int upperBound = 10;

          int i, r, lower;
          for (i = 0; i < upperBound; i++)
          {
              lower = i;
              r = arc4random_uniform(upperBound - lower) + lower;
              printf("%d. r: %d%s\n", i, r, (r == i) ? " ==" : "");
              XCTAssert(r >= lower);

              if (i == (upperBound - 1))
              {
                  XCTAssert(r == i);  // last r will always match index/lower
              }
          }
      }


      // output
      0. r: 0 ==
      1. r: 5
      2. r: 7
      3. r: 3 ==
      4. r: 8
      5. r: 6
      6. r: 6 ==
      7. r: 8
      8. r: 9
      9. r: 9 ==  // last r will always match index/lower


  //--------------------------------
    unit test

      typedef void (^printDate_t)(NSDate *);
      typedef void (^printDict_t)(id, id, BOOL *);
      typedef void (^printJsonData_t)(NSData *);

      -(void)testJsonSerialization
      {
              printDate_t printDate = ^(NSDate * aDate)
              {
                  NSDateFormatter * df = [NSDateFormatter new];
                  df.dateFormat = @"MM-dd-yyyy HH:mm:ss.SSS";
                  NSLog(@"date: %@", [df stringFromDate:aDate]);
              };

              printDict_t printDict = ^(id key, id obj, BOOL *stop)
              {
                  printf("%s", [NSString stringWithFormat:@"%@ = %@\n", key, obj].UTF8String);
              };

              printJsonData_t printJsonData = ^(NSData * aJsonData)
              {
                  NSLog(@"json data:\n%@", [[NSString alloc] initWithData:aJsonData encoding:NSUTF8StringEncoding]);

                  /* NSJSONSerialization dataWithJSONObject:options
                     0, 1 NSJSONWritingPrettyPrinted

                     0: {"panic":false,"foo":{"date":1428231364.498701,"code":"HG2G"},"answer":42,"trilogyParts":[1,2,3,4,5],"question":null}
                     1: {
                          "panic" : false,
                          "foo" : {
                            "date" : 1428231364.498701,  // 04-05-2015 04:56:04.499
                            "code" : "HG2G"
                          },
                          "answer" : 42,
                          "trilogyParts" : [
                            1,
                            2,
                            3,
                            4,
                            5
                          ],
                          "question" : null
                        } */
              };


          NSDate * date = [NSDate date];
          id dateVal = [NSNumber numberWithDouble:date.timeIntervalSince1970];
          printDate(date);

          // json data source
          NSDictionary * inData = @{ @"answer"       : @42,
                                     @"question"     : [NSNull null],
                                     @"panic"        : @NO,
                                     @"trilogyParts" : @[ @1, @2, @3, @4, @5 ],
                                     @"foo"          : @{ @"code" : @"HG2G",
                                                          @"date" : dateVal } };

          NSLog(@"in data");
          [inData enumerateKeysAndObjectsUsingBlock:printDict];

          if ([NSJSONSerialization isValidJSONObject:inData])
          {
              NSError * error;

              // serialize
              NSData * jsonData = [NSJSONSerialization dataWithJSONObject:inData options:0 error:&error];
              if (error)
              {
                  NSLog(@"json serialization error: %@", error);
              }
              else
              {
                  printJsonData(jsonData);
                  printJsonData([NSJSONSerialization dataWithJSONObject:inData options:NSJSONWritingPrettyPrinted error:NULL]);

                  // deserialize
                  NSDictionary * outData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
                  if (error)
                  {
                      NSLog(@"json deserialization error: %@", error);
                  }
                  else
                  {
                      XCTAssert([outData isEqualToDictionary:inData]);

                      NSLog(@"out data");
                      [outData enumerateKeysAndObjectsUsingBlock:printDict];

                   /* NSDictionary * foo = [outData objectForKey:@"foo"];
                      date = [NSDate dateWithTimeIntervalSince1970:[[foo objectForKey:@"date"] doubleValue]]; */
                      date = [NSDate dateWithTimeIntervalSince1970:[[outData valueForKeyPath:@"foo.date"] doubleValue]];
                      printDate(date);
                  }
              }
          }
      }


   //--------------------------------
     unit test

      -(void)testRegexMatchDates
      {
          NSArray * dates = @[ @"Jan 1 1201",
                               @"Jan 01 1800",
                               @"Jan 1 1899",
                               @"Dec 31 1900",
                               @"Jan 1 1901",
                               @"Jan 24 2084",
                               @"Jan 1 3000" ];

          NSString * text = [dates componentsJoinedByString:@" ; "];

          NSError * regexError;
          NSString * regexPattern = @"\\w{3}\\s+\\d{1,2}\\s+\\d{4}";
          id regex = [NSRegularExpression regularExpressionWithPattern:regexPattern options:0 error:&regexError];

          if (regexError)
          {
              NSLog(@"regex error: %@", regexError);
          }
          else
          {
              NSDateFormatter * df = [NSDateFormatter new];
              df.dateFormat = @"MM-dd-yyyy";

              NSRange textRange = NSMakeRange(0, text.length);
              [regex enumerateMatchesInString:text options:0 range:textRange usingBlock:^(NSTextCheckingResult * result, NSMatchingFlags flags, BOOL * stop)
              {
                  NSString * match = [text substringWithRange:result.range];
                  NSDate * date = [df dateFromString:match];
                  NSLog(@"%11s (%@)", match.UTF8String, [df stringFromDate:date]);
              }];


              const NSUInteger matchCount = [regex numberOfMatchesInString:text options:0 range:textRange];
              const NSUInteger expectedMatchCount = dates.count;
              XCTAssert(matchCount == expectedMatchCount, @"expected regex match count failed");
          }
      }


      // output
       Jan 1 1201 (01-01-1201)
      Jan 01 1800 (01-01-1800)
       Jan 1 1899 (01-01-1899)
      Dec 31 1900 (12-31-1900)
       Jan 1 1901 (01-01-1901)
      Jan 24 2084 (01-24-2084)
       Jan 1 3000 (01-01-3000)


- Action Methods

    #define IBAction void  // UINibDeclarations.h

    // Swift 3
    @IBAction func buttonTap(sender: UIButton) {
        print("button was tapped, sender: \(sender)")  // button was tapped, sender: <UIButton: 0x00000000>
    }

    // OC
    - (IBAction)buttonTap:(id)sender {
        NSLog(@"button was tapped, sender: %@", sender);
    }


- C Dialects

  //--------------------------------
    Swift 3

      import Foundation

      let now = Date()

      let df = DateFormatter()
      df.dateFormat = "MM-dd-yyyy HH:mm:ss"
      print("\(df.string(from:now))")  // 09-01-2016 00:00:00

      let a = "HAL"
      let x = 9000
      print("Swift hello \(a) \(x).")  // Swift hello HAL 9000.


  //--------------------------------
    OC

      #import <Foundation/Foundation.h>

      int main() {
          @autoreleasepool {
              NSDate * now = [NSDate date];

              NSDateFormatter * df = [NSDateFormatter new];
              df.dateFormat = @"MM-dd-yyyy HH:mm:ss";
              NSLog(@"%@", [df stringFromDate:now]);  // 10-02-2013 00:00:00

              NSString * a = @"HAL";
              int x = 9000;
              NSLog(@"OC hello %@ %d%c", a, x, '.');  // OC hello HAL 9000.
          }

          return 0;
      }


  //--------------------------------
    C++

      #include <iostream>
      #include <ctime>

      int main() {
          time_t now = time(0);
          struct tm * t;
          t = localtime(&now);

          char buff[20];
          strftime(buff, sizeof(buff), "%m-%d-%Y %H:%M:%S", t);  // 10-02-2013 00:00:00
          std::cout << buff << std::endl;

          const std::string a = {"HAL"};
          int x = 9000;
          std::cout << "C++ hello " << a << " " << x << '.' << std::endl;  // C++ hello HAL 9000.

          return 0;
      }


  //--------------------------------
    C

      #include <stdio.h>
      #include <time.h>

      int main() {
          time_t now = time(0);
          struct tm * t;
       // t = gmtime(&now);     // 10-02-2013 06:00:00
          t = localtime(&now);  // 10-02-2013 00:00:00

          char buff[20];
          strftime(buff, sizeof(buff), "%m-%d-%Y %H:%M:%S", t);
          printf("%s\n", buff);

          const char a[] = "HAL";
          int x = 9000;
          printf("C hello %s %d%c\n", a, x, '.');  // C hello HAL 9000.

          return 0;
      }


- C

  //--------------------------------
    linked list

      // file: main.c
      #include "list.h"


      int getData();
      void test();

      int main()
      {
          test();
          return 0;
      }

      void test()
      {
          const int nodeCount = 4;
          for (int i = 0; i < nodeCount; i++)
          {
              int data = getData();
              struct node * n = createNodeWithData(data);
              appendNodeToList(n);
          }

          printList();

          reverseList();
          printList();

          prependNodeToList(createNodeWithData(getData()));
          printList();

          freeList();
      }

      int getData()
      {
          static int dataValue;
          dataValue += 100;
          return dataValue;
      }


      // file: list.h
      struct node
      {
          struct node * next;
          int data;
      };

      struct node * createNodeWithData(int);

      void appendNodeToList(struct node *);
      void prependNodeToList(struct node *);
      void reverseList();
      void printList();
      void freeList();


      // file: list.c
      #include <stdio.h>
      #include <stdlib.h>
      #include "list.h"


      struct node * head;
      struct node * tail;

      struct node * createNodeWithData(int aData)
      {
          struct node * n = malloc(sizeof(struct node));
          n->data = aData;
          n->next = NULL;

          return n;
      }

      void appendNodeToList(struct node * aNode)
      {
          if (head == NULL)
          {
              head = aNode;
              tail = head;
          }
          else
          {
              tail->next = aNode;
              tail = aNode;
          }
      }

      void prependNodeToList(struct node * aNode)
      {
          if (head == NULL)
          {
              head = aNode;
              tail = head;
          }
          else
          {
              aNode->next = head;
              head = aNode;
          }
      }

      void reverseList()
      {
          struct node * prev = NULL;
          struct node * current = head;
          struct node * next = NULL;

          while (current != NULL)
          {
              next = current->next;

              current->next = prev;
              prev = current;
              current = next;
          }

          tail = head;
          head = prev;
      }

      void printList()
      {
          const struct node * current = head;
          while (current != NULL)
          {
              printf("%p, %d, %p\n", current, current->data, current->next);
              current = current->next;
          }
      }

      void freeList()
      {
          struct node * current = head;
          struct node * next;
          while (current != NULL)
          {
              next = current->next;

              current->data = 0;
              current = NULL;
              free(current);

              current = next;
          }
      }


- C++

  //--------------------------------
    C++ 11 range-based for-statements

      #include <iostream>
      #include <vector>
      using namespace std;


      int main()
      {
          int a[] = { 0, 1, 2 };
          for (const auto &i: a)
          {
              //
          }

          vector<int> v;  // = { 0, 1, 2 };
          for (int i = 0; i < 3; ++i)
          {
              v.push_back(i);
          }

          for (const auto &i: v)
          {
              cout << i << " ";  // 0 1 2
          }
          cout << "\n";

          for (auto &i: v)
          {
              i += 1;
              cout << i << " ";  // 1 2 3
          }

          return 0;
      }


- String Conversions

    - C string char * to C++
      const char c[] = "foo";
      std::string cc(c);
      std::cout << cc << std::endl;

    - C string char * to OC
      const char c[]="foo";
      NSString * oc = [NSString stringWithUTF8String:c];
      NSLog(@"%@", oc);

    - C++ string to C char *
      std::string cc = {"foo"};
      const char * c = cc.c_str();
      printf("%s\n", c);

    - OC string to C char *
      NSString * oc = @"foo";
      const char * c = oc.UTF8String;
      printf("%s\n", c);


- Cocoa Framework Header Files

    iOS      /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObject.h
             /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIView.h

    macOS    /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSObject.h
             /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSView.h

    watchOS  /Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObject.h
             /Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/WatchKit.framework/Headers/WKInterfaceObject.h
  
  
- App Icon

  Adobe Photoshop CC v 20.0.5
  Preferences - Plug-Ins - Generator - Enable
  File - Generate - Image Assets

  https://helpx.adobe.com/photoshop/using/generate-assets-layers.html
  https://github.com/adobe-photoshop/generator-core
  https://github.com/adobe-photoshop/generator-assets
  https://github.com/adobe-photoshop/generator-assets/wiki/Generate-Image-Assets-Functional-Spec


  Layer Name
  iPhone: 40x40 notification_20@2x.png, 60x60 notification_20@3x.png, 58x58 settings_29@2x.png, 87x87 settings_29@3x.png, 80x80 spotlight_40@2x.png, 120x120 spotlight_40@3x.png, 120x120 appicon_60@2x.png, 180x180 appicon_60@3x.png, 1024x1024 appicon_1024.png
  iPad:   40x40 notification_20@2x.png, 58x58 settings_29@2x.png, 80x80 spotlight_40@2x.png, 152x152 appicon_76@2x.png, 167x167 appicon_83.5@2x.png, 1024x1024 appicon_1024.png

  - iPhone
    40x40 notification_20@2x.png
    60x60 notification_20@3x.png

    58x58 settings_29@2x.png
    87x87 settings_29@3x.png

    80x80 spotlight_40@2x.png
    120x120 spotlight_40@3x.png

    120x120 appicon_60@2x.png
    180x180 appicon_60@3x.png
    1024x1024 appicon_1024.png

  - iPad
    40x40 notification_20@2x.png
    58x58 settings_29@2x.png
    80x80 spotlight_40@2x.png

    152x152 appicon_76@2x.png
    167x167 appicon_83.5@2x.png
    1024x1024 appicon_1024.png
  
  
- Xcode Regex Search and Replace

    indent line
    s:^(.)
    r:    $1

    replace first n chars
    s:^.{3}

    deleting first n chars, 2 part search & replace
    s:^.{3}
    r:  x
    s:^  x

    remove trailing chars
    d:^(.{39}-)    # debug show length
    s:^(.{39}-).*  # keep first 40 chars, delete everything after char 40
    r:$1

    add trailing chars
    s:^(.{39}-)  # keep first 40 chars
    r:$1----

    s:spinner|((ui)?activityIndicator(View)?)
  
  
- Xcode Scheme Scripts
    
  - Build Pre-Actions Script: Redirect Output to File
    
    vLine="-------------------------------"
    vDate=$(date '+%m-%d-%y.%H%M%S')
    echo "$vLine\n$vDate\nbuild pre-actions...\npwd: $(pwd)\n$vLine" >> ~/xcodeActions.txt
    
    # build post-actions script
    vDate=$(date '+%m-%d-%y.%H%M%S')
    echo "$vDate\nbuild post-actions..." >> ~/xcodeActions.txt
    
    
    # text file output
    -------------------------------
    11-21-14.120100
    build pre-actions...
    pwd: /private/var/folders/p4/abc7123x999xyz_ab1qwe0xx9999xs/T
    -------------------------------
    11-21-14.120100
    build post-actions...
    
    
  - Test Post-Actions Script: Apple Script to set Focus on Xcode after Running Unit Tests
    osascript -e 'tell application "Xcode" to activate'
    
    
  - Disable debugging Document Versions for Run and Profile actions
    Removes NSDocumentRevisionsDebugMode from app launch args
    Nav to scheme window and uncheck Document Versions debugging from Run and Profile options
    
    # script
    open project
    nav to scheme window (CS + <)    // > prod > scheme > edit scheme, will gen xcscheme file
    esc to dismiss scheme window       
    close project                      
                                       
    # run script from root           // scheme name = project name
  # sed -i '' -e  s/'debugDocumentVersioning = "YES"'/'debugDocumentVersioning = "NO"'/g  x.xcodeproj/xcshareddata/xcschemes/x.xcscheme
    
    open project
    
    
- Xcode Symbolic Breakpoints
  symbol: Swift.print
          NSLog
          -[Foo name]      // read property
          -[Foo setName:]  // write property
          -[Foo update]    // method