Expression Software Blog

8/10/16

iOS macOS

- Swift

  //--------------------------------
    repl command line

      :version  #lldb-360.1.65  // Swift 3.0.1, swiftlang-800.0.58.6 clang-800.0.42.1
      :quit


      import Darwin

      M_PI
      log2(100 as Float)
      log2(100 as Double)  // 6.6438561897747244


      "Foo".lowercased()
      "foo".characters.count           // 3 String.CharacterView.IndexDistance
      "foo".endIndex                   // 3 String.CharacterView.Index
      "foo".startIndex                 // 0
      "foo".characters.index(of: "f")  // 0 String.CharacterView.Index?


      import Foundation

      let s = "foo bar"

      // replace
      s.replacingOccurrences(of: "o", with: "*")  // f** bar


      let range: Range<String.CharacterView.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
      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.characters[s.startIndex ..< endIdx])          // foo
      String(s[s.startIndex ..< endIdx])                     // foo (String?)
      String(s[s.startIndex ..< endIdx])!                    // foo

      let range: Range<String.CharacterView.Index> = Range(uncheckedBounds: (lower: s.index(s.startIndex, offsetBy:  1),
                                                                             upper: s.index(s.endIndex,   offsetBy: -1)))
      s.substring(with: range)                               // oo ba


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


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


      import GLKit

      for x in stride(from:0, through:360, by:90) { print( String(format:"%3d degrees = %.2f radians", x, GLKMathDegreesToRadians(Float(x))) ) }

            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


  //--------------------------------
    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]);
      }


  //--------------------------------
    enums

      enum WildThing {

          case dragon, rous, troll
      }

      let wildThing = WildThing.rous
      print(wildThing)                // rous  app.WildThing.rous


  //--------------------------------
    console input args, Swift 3

      if CommandLine.argc == 3 {  // arg count

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


  //--------------------------------
    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
      }


  //--------------------------------
    write string array to file, Swift 3

      var data = [String]()
      // ...

      let fileData: String = data.joined(separator: "\n")

      do {

          try fileData.write(toFile: outFileName, atomically: true, encoding: String.Encoding.utf8)

      } catch {
          print(error)
      }


  //--------------------------------
    read and print file, Swift 3
    - with line numbers
    - lines matching regex

      let filename = "~/temp.txt"
      if let file: String = readFile(filename) {
          let lines: [String] = getLinesFromFile(file)

          printFile(filename, lines, printLine)
          printFile(filename, lines, printLineRegex)


          // printLineRegex closure
          var matchCount = 0
          printFile(filename, lines) { (lineNum: String, line: String) -> Void in

              let pattern = ".*foo.*"
              let regex = try! NSRegularExpression(pattern: pattern, options: [])

              let lineRange = NSMakeRange(0, line.characters.count)
              guard let match: NSTextCheckingResult = regex.firstMatch(in: line, options: [], range: lineRange) else { return }

              if match.range.length > 0 {
                  matchCount += 1
                  let matchStartIndex: String.CharacterView.Index        = line.index(line.startIndex, offsetBy: match.range.location)
                  let matchEndIndex:   String.CharacterView.Index        = line.index(matchStartIndex, offsetBy: match.range.length)
                  let range:           Range<String.CharacterView.Index> = Range(uncheckedBounds: (lower: matchStartIndex, upper: matchEndIndex))

                  printLine(lineNum, line.substring(with: range))
              }
          }

          print("\n\(matchCount) matches found")
      }


      import Foundation

      func readFile(_ filename: String) -> String? {
          guard let fileContent: Data = FileManager().contents(atPath: filename) else { return nil }

          let rawFile: String? = String(data: fileContent, encoding:String.Encoding.utf8)! as String

          return rawFile
      }

      func getLinesFromFile(_ file: String) -> [String] {
          return file.characters.split { $0 == "\n" }.map(String.init)  // .map{String($0)}
      }

      func printFile(_ filename: String, _ lines: [String], _ printLineClosure: (String, String) -> Void) {
          let paddingLength: Int = String(lines.count).characters.count + 1

          print("file: \(filename)")
          for (index, line) in lines.enumerated() {
              var lineNum = String(format: "%d.", index + 1)
              lineNum = lineNum.padding(toLength: paddingLength, withPad: " ", startingAt: 0)

              printLineClosure(lineNum, line)
          }
      }

      func printLine(_ lineNum: String, _ line: String) {
          print("\(lineNum) \(line)")
      }

      func printLineRegex(lineNum: String, line: String) {
          let pattern = "^[a-f0-9]{40}" // sha1 hash
          let regex = try! NSRegularExpression(pattern: pattern, options: [])

          let lineRange = NSMakeRange(0, line.characters.count)
          guard let match: NSTextCheckingResult = regex.firstMatch(in: line, options: [], range: lineRange) else { return }

          if match.range.length > 0 {
              let matchStartIndex: String.CharacterView.Index        = line.index(line.startIndex, offsetBy: match.range.location)
              let matchEndIndex:   String.CharacterView.Index        = line.index(matchStartIndex, offsetBy: match.range.length)
              let range:           Range<String.CharacterView.Index> = Range(uncheckedBounds: (lower: matchStartIndex, upper: matchEndIndex))

              printLine(lineNum, line.substring(with: range))
          }
      }


  //--------------------------------
    enumerate directories, Swift 3

      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)
          }

      }


  //--------------------------------
    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"
                      }

                      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 {
          print("'\(name)'")  // '' ' ' 'abc' 'bar' 'foo'
      }


  //--------------------------------
    print unicode chars

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


  //--------------------------------
    print number string, Swift 3

      import Foundation

      var s1 = String()
      var s2 = String()

      for x in 1...20 {
          s1 += "\(x),"
          s2 += "\(x % 10)"  // mod
      }

      print("s1 length = \(s1.characters.count)")                 // s1 length = 51
      s1 = s1.substring(to: s1.index(s1.endIndex, offsetBy: -1))  // remove last char

      print(s1)  // 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
      print(s2)  // 12345678901234567890


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

      import Foundation

      let uid = UUID()
      print(uid.uuidString)  // 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 {
           print("\(bytes[i]) ")  // 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)

      print(b)  // [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

      print(b1)  // [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")


  //--------------------------------
    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


  //--------------------------------
    misc

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

      for _ in 1...10 { print(".") };      // ..........   String(count:10, repeatedValue: Character("."))
      for x in 1...10 { print(x) };        // 12345678910
      for x in 1...10 { print("\(x) ") };  // 1 2 3 4 5 6 7 8 9 10

      for x in 1...3 { println("\(x).") }  // 1.
                                           // 2.
                                           // 3.

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

      let df = NSDateFormatter()
      df.dateFormat = "MM-dd-yyyy"
      let d1 = df.dateFromString("01-08-1947")!
      let d2 = df.dateFromString("01-10-2016")!  // db

      let cal = NSCalendar.currentCalendar()
      cal.components([.Day], fromDate: d1, toDate: d2, options: []).day    // 25204
      cal.components([.Year], fromDate: d1, toDate: d2, options: []).year  // 69

      let appDelegate: AppDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
      AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;     // OC


  //--------------------------------
    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 3
      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 3
      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 3
      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+

        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

        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) = ^
      // 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

      -(void)downloadUrl:(NSString *)a_url
      {
          NSURL * url = [NSURL URLWithString:a_url];
          NSURLSession * session = [NSURLSession sharedSession];
          NSURLSessionDataTask * task = [session dataTaskWithURL:url completionHandler:^(NSData * data, NSURLResponse * response, NSError * error)
                                        {
                                            if (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]);
                                                }
                                            }
                                            else
                                            {
                                                NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
                                                NSString * htmlContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

                                                dispatch_async(dispatch_get_main_queue(), ^
                                                {
                                                    self.textView.text = htmlContent;
                                                    NSLog(@"url: %@\nstatus: %ld\n%@", url, (long)statusCode, htmlContent);
                                                });
                                            }
                                        }];
          [task resume];
      }


  //--------------------------------
    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://127.0.0.1/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
    http://4.bp.blogspot.com/-sQNVySG1oRc/VR9wx8pLbuI/AAAAAAAACQY/OqzbiPdQlh4/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


- xcrun --show-sdk-path --sdk iphoneos  #/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk
  xcrun --show-sdk-path --sdk macosx    #/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk
  xcrun --show-sdk-path --sdk watchos   #/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk

  xcrun simctl launch booted com.apple.Preferences  #launch iOS settings

  xcrun swift-demangle __TFC4Demo6HomeVC5debugfT_T_  #Demo.HomeVC.debug ()()


- 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----


- xcodebuild -help

    /usr/bin/xcodebuild -showBuildSettings
    xcodebuild -showsdks
    xcodebuild -dry-run -project app.xcodeproj
    xcodebuild -list -project app.xcodeproj
    xcodebuild -showBuildSettings -project app.xcodeproj


- 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'


- xcode-select
  xcode-select -h

  sudo xcode-select --switch /Applications/Xcode-Beta.app
  xcode-select -p  #/Applications/Xcode-Beta.app/Contents/Developer


- LLDB
  http://lldb.llvm.org/lldb-gdb.html

    help
    #comment
    p @import Foundation
    p sizeof(int)
    p width
    po foo
    po @"foo".length

    po FooType $x = (FooType)1  // cast int to enum Foo

    po [0x00000000 description]

    po node.position;  // (x = 0, y = 0, z = -10)
    po node.rotation;  // (x = 1, y = 0, z = 0, w = 6.28318548)

    po [self.view class]
    po [self.view superclass]

    po cell.contentView.subviews

    po _stdlib_getDemangledTypeName(x)  // Swift.Array<Swift.Int>

    p @import UIKit
    po view.bounds.size.width
    po indexPath.row
    po indexPath.section
    po [refreshControl actionsForTarget:self forControlEvent:UIControlEventValueChanged]

    po $arg1  #use w/exception breakpoint

    po [NSUserDefaults.standardUserDefaults dictionaryRepresentation]

    expr width = 25
    expr debugFlag = true;  // reset

    fr v
    frame variable s  // (__NSCFString *) s = 0x00000000 @"foo"
    frame variable k  // (__NSCFConstantString *) k = 0x00000000 @"bar"
    frame variable d  // (__NSDictionaryI *) d = 0x00000000 4 key/value pairs


- TargetConditionals.h

    #if TARGET_CPU_ARM     // iphone 4-5
        TARGET_CPU_ARM64   // iphone 5s
        TARGET_CPU_X86     // 32-bit simulator
        TARGET_CPU_X86_64  // 64-bit simulator
        TARGET_IPHONE_SIMULATOR
        TARGET_OS_IPHONE
        TARGET_OS_MAC


- LP64 Macro

    #if __LP64__