├── .gitignore ├── LICENSE.txt ├── README-DateTimeUtils.md ├── README-RSAUtils.md ├── README-SnowflakeIdGenerator.md ├── README.md ├── RELEASE-NOTES.md ├── SwiftUtils.playground ├── Contents.swift └── contents.xcplayground ├── SwiftUtils.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── btnguyen.xcuserdatad │ └── xcschemes │ ├── SwiftUtils.xcscheme │ └── xcschememanagement.plist ├── SwiftUtils.xcworkspace └── contents.xcworkspacedata ├── SwiftUtils ├── DateTimeUtils.swift ├── Info.plist ├── RSAUtils.swift ├── SnowflakeIdGenerator.swift └── SyncUtils.swift ├── SwiftUtilsPlayground ├── SwiftUtilsPlayground.xcodeproj │ └── project.pbxproj └── SwiftUtilsPlayground │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SwiftUtilsPlayground.entitlements │ └── ViewController.swift └── SwiftUtilsTests ├── Info.plist └── SwiftUtilsTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Thanh Ba Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README-DateTimeUtils.md: -------------------------------------------------------------------------------- 1 | DateTimeUtils 2 | ============= 3 | 4 | Datetime utility class. 5 | 6 | 7 | Functions 8 | --------- 9 | 10 | - `static currentUnixTimestampMillisec() -> UInt64`: get current UNIX timestamp in milliseconds. 11 | - `static currentUnixTimestampSecond() -> UInt64`: get current UNIX timestamp in seconds. 12 | - `static waitTillNextMillisec(currentMillisec: UInt64) -> UInt64`: wait until clock moves to the next millisecond (return the "next" millisecond). 13 | - `static waitTillNextSecond(currentSecond: UInt64) -> UInt64`: wait until clock moves to the next second (return the "next" second). 14 | - `static startOfSecond(date: Date) -> Date`: reset to start of the second of a Date (nanosecond is set to 0). 15 | - `static startOfMinute(date: Date) -> Date`: reset to start of the minute of a Date (second and nanosecond are set to 0s) 16 | - `static startOfHour(date: Date) -> Date` : reset to start of the hour of a Date (minute, second and nanosecond are set to 0s). 17 | - `static startOfDay(date: Date) -> Date` : reset to start of the day of a Date (hour, minute, second and nanosecond are set to 0s). 18 | - `static startOfDate(date: Date) -> Date` : alias of `startOfDay` 19 | - `static startOfMonth(date: Date) -> Date` : reset to start of the month of a Date (day is set to 1; hour, minute, second and nanosecond are set to 0s). 20 | - `static startOfYear(date: Date) -> Date` : reset to start of the year of a Date (month and day are set to 1s; hour, minute, second and nanosecond are set to 0s). 21 | - `static startOfWeek(date: Date) -> Date` : reset to start of the week of a Date (day is set to `first week day`; hour, minute, second and nanosecond are set to 0s). 22 | - `static addSeconds(date: Date, numSeconds: Int) -> Date`: add n seconds to a Date. 23 | - `static addMinutes(date: Date, numMinutes: Int) -> Date`: add n minutes to a Date. 24 | - `static addHours(date: Date, numHours: Int) -> Date` : add n hours to a Date. 25 | - `static addDays(date: Date, numDays: Int) -> Date` : add n days to a Date. 26 | - `static addMonths(date: Date, numMonths: Int) -> Date` : add n months to a Date. 27 | - `static addYears(date: Date, numYears: Int) -> Date` : add n years to a Date. 28 | - `static addWeeks(date: Date, numWeeks: Int) -> Date` : add n weeks to a Date (1 week = 7 days). 29 | 30 | 31 | Changes 32 | ------- 33 | 34 | - Since v1.3 - 2016-11-22: add functions `addXxx(...)`. 35 | - Since v1.1.1 - 2016-09-16: input/output type changed from `NSDate` to `Date`. 36 | - Since v1.1 - 2016-08-27: added functions `startOfXxx(...)`. 37 | - Since v1.0 - 2016-08-15: first release. 38 | -------------------------------------------------------------------------------- /README-RSAUtils.md: -------------------------------------------------------------------------------- 1 | RSAUtils 2 | ======== 3 | 4 | Utility class to encrypt/decrypt data using RSA private/public key. 5 | 6 | Objective-C version can be found at [https://github.com/ideawu/Objective-C-RSA](https://github.com/ideawu/Objective-C-RSA) 7 | 8 | **Note: if you receive error status -34018 on Xcode8 / iOS 10, enable Keychain Sharing. See: http://stackoverflow.com/questions/38456471/secitemadd-always-returns-error-34018-in-xcode-8-in-ios-10-simulator/38543243#38543243** 9 | 10 | 11 | Introduction 12 | ------------ 13 | 14 | This library is to solve the following problem (actually mine): 15 | 16 | - We already had a server that provides APIs for clients to call. Data has to be encrypted using RSA, and the server had already generated a pair of RSA public/private key for each client. 17 | - We had to implement an iOS client for the server's APIs. It seemed to be a simple task at first, but it soon turned out not so: 18 | 1. In Apple's Security framework all key management happens at the keychain level. It means existing key(s) must be imported to keychain in order to be usable. Using the key-pair generated by `SecKeyGeneratePair` was easy. Importing an existing key (in file, or as a string), however, turned out to be tricky. 19 | 2. There was a solution in [Objective-C](https://github.com/ideawu/Objective-C-RSA) but we needed a pure-Swift one and there was no good pure-Swift implementation at the time. 20 | 3. The amount of data that can be encrypted in one go is limitted by the key's length. If a large amount of data needs to be encrypted, it has to be processed chunk by chunk. 21 | 22 | This small library addresses all 3 problems above. 23 | 24 | 25 | Usage 26 | ----- 27 | 28 | *Notes:* 29 | 30 | >>>Public key: only `X509` public key is supported. On disk, `X509` public key file begins with `-----BEGIN PUBLIC KEY-----` and ends with `-----END PUBLIC KEY-----`. 31 | >>> 32 | >>>Private key: `PKCS#1` and `PKCS#8` private keys are supported. On disk, `PKCS#8 private key` file begins with `-----BEGIN PRIVATE KEY-----` and ends with `-----END PRIVATE KEY-----`, `PKCS#1 private key` file begins with `-----BEGIN RSA PRIVATE KEY-----` and ends with `-----END RSA PRIVATE KEY-----`. 33 | 34 | *Obtain `PKCS#8 private key` and `X509 public key`:* 35 | 36 | Usually you have the `PKCS#1 private key` in a `.pem` file (this file starts with `-----BEGIN RSA PRIVATE KEY-----` and ends with `-----END RSA PRIVATE KEY-----`). To convert to `PKCS#8` format, use the following command: 37 | 38 | >>> `openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in privkey.pem -out privkey.pkcs8.pem` 39 | 40 | You can extract `X509 public key` from a PEM private key by using the following command: 41 | 42 | >>> `openssl rsa -pubout -in privkey.pem -out pubkey.x509.pem` 43 | 44 | or 45 | 46 | >>> `openssl rsa -pubout -in privkey.pkcs8.pem -out pubkey.x509.pem` 47 | 48 | Example of a `PKCS#1 private key`: 49 | 50 | ``` 51 | -----BEGIN RSA PRIVATE KEY----- 52 | MIICXQIBAAKBgQDAF4nCtUoppj4taUW7T4+aGhf45lGR8mAvVnKHufTzPaNinYzl 53 | mkLF7I7dk0Tgc3KoACJ6I2NC//mhEpNErX0MAqVlCMbAgWvUNSFvHByaqC/5o5hu 54 | k3CwvERFho2IHmSMMTasMG6PoOhto3KPF9AxszkRCsY3xnS5JURE/4h4DQIDAQAB 55 | AoGAcwOk8Hgr15Q6VmZZ4jVY/iPxho4g+QnunWldWfb1u06ErV84JaGfqXJieDjd 56 | XKbDV+P8wLS/kjZB+TduBgj6fVTqTdL26sBsAhTiYTBK4UoXwRMg14dbXJrsnH5n 57 | KUU9GFslFQefRaWn4mjvaMFkUqRsrLkM2nmgBXDnmJLdmwECQQDd40S4EuwoLFYW 58 | xDiWpdCn2nA6n7SBp2xa26Az7bZog6KcDxGgNPY1Z1+PGnnOw6Lcgmx2LOZS4g4S 59 | WwzjnuQ1AkEA3Z+cFFaGll9di5J2BWlQNKzv5UeSMZqvspxsd2VDJXmWs2PvqEHd 60 | BzU4WW8WsHWyoycOY0R710t8JcWCrMGPeQJBAIl6rTNYFhZ2EgkdHurIZlX6FBte 61 | pJrIv2w0NDi4ipKLLQ+Ajq0y43IHUL/76Yjg0mHKNaWrADJOeeWJoJzP8BkCQQCo 62 | zHSYmP6RcblSIQ97N6c7N6zQOR8EYQkJRVLn7VyjImTB5ZAX23J5lvOASrhBVqPk 63 | 2E6BFRwt8vRv4GuTISohAkAImxaeC1+jiRGN+EtsNDSmgNjILg5Tyteor6t3guTR 64 | uMv2Y8tJiCh7UTguUnWiDo/RLrDjuvoE4lLpbyAvariY 65 | -----END RSA PRIVATE KEY----- 66 | ``` 67 | 68 | It's `PKCS#8 private key` format: 69 | 70 | ``` 71 | -----BEGIN PRIVATE KEY----- 72 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMAXicK1SimmPi1p 73 | RbtPj5oaF/jmUZHyYC9Wcoe59PM9o2KdjOWaQsXsjt2TROBzcqgAInojY0L/+aES 74 | k0StfQwCpWUIxsCBa9Q1IW8cHJqoL/mjmG6TcLC8REWGjYgeZIwxNqwwbo+g6G2j 75 | co8X0DGzOREKxjfGdLklRET/iHgNAgMBAAECgYBzA6TweCvXlDpWZlniNVj+I/GG 76 | jiD5Ce6daV1Z9vW7ToStXzgloZ+pcmJ4ON1cpsNX4/zAtL+SNkH5N24GCPp9VOpN 77 | 0vbqwGwCFOJhMErhShfBEyDXh1tcmuycfmcpRT0YWyUVB59FpafiaO9owWRSpGys 78 | uQzaeaAFcOeYkt2bAQJBAN3jRLgS7CgsVhbEOJal0KfacDqftIGnbFrboDPttmiD 79 | opwPEaA09jVnX48aec7DotyCbHYs5lLiDhJbDOOe5DUCQQDdn5wUVoaWX12LknYF 80 | aVA0rO/lR5Ixmq+ynGx3ZUMleZazY++oQd0HNThZbxawdbKjJw5jRHvXS3wlxYKs 81 | wY95AkEAiXqtM1gWFnYSCR0e6shmVfoUG16kmsi/bDQ0OLiKkostD4COrTLjcgdQ 82 | v/vpiODSYco1pasAMk555YmgnM/wGQJBAKjMdJiY/pFxuVIhD3s3pzs3rNA5HwRh 83 | CQlFUuftXKMiZMHlkBfbcnmW84BKuEFWo+TYToEVHC3y9G/ga5MhKiECQAibFp4L 84 | X6OJEY34S2w0NKaA2MguDlPK16ivq3eC5NG4y/Zjy0mIKHtROC5SdaIOj9EusOO6 85 | +gTiUulvIC9quJg= 86 | -----END PRIVATE KEY----- 87 | ``` 88 | 89 | And it's `X509 public key`: 90 | 91 | ``` 92 | -----BEGIN PUBLIC KEY----- 93 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAF4nCtUoppj4taUW7T4+aGhf4 94 | 5lGR8mAvVnKHufTzPaNinYzlmkLF7I7dk0Tgc3KoACJ6I2NC//mhEpNErX0MAqVl 95 | CMbAgWvUNSFvHByaqC/5o5huk3CwvERFho2IHmSMMTasMG6PoOhto3KPF9AxszkR 96 | CsY3xnS5JURE/4h4DQIDAQAB 97 | -----END PUBLIC KEY----- 98 | ``` 99 | 100 | *Functions:* 101 | 102 | Key management functions: 103 | 104 | - `static func getRSAKeyFromKeychain(_ tagName: String) -> SecKey?`: Gets an existing RSA key specified by a tag from keychain. 105 | - `static func deleteRSAKeyFromKeychain(_ tagName: String)`: Deletes an existing RSA key specified by a tag from keychain. 106 | - `static func addRSAPrivateKey(_ privkeyBase64: String, tagName: String) throws -> SecKey?`: Adds a RSA private key to keychain and returns its SecKey reference. 107 | - The private key is in base-64 PKCS#1 or PKCS#8 format. 108 | - Throws `RSAUtilsError` if the input key is not a valid PKCS#8 private key. 109 | - `-----BEGIN PRIVATE KEY-----`, `-----BEGIN RSA PRIVATE KEY-----`, `-----END PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` can be omitted. 110 | - `static func addRSAPublicKey(_ pubkeyBase64: String, tagName: String) throws -> SecKey?`: Adds a RSA public key to keychain and returns its SecKey reference. 111 | - The public key is in base-64 X509 format. 112 | - Throws `RSAUtilsError` if the input key is not a valid X509 public key. 113 | - `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` can be omitted. 114 | 115 | Encryption/Decryption functions: 116 | 117 | - `static func encryptWithRSAKey(data: Data, tagName: String) -> Data?`: Encrypts data using a RSA key from keychain specified by `tagName`. 118 | - `static func encryptWithRSAKey(str: String, tagName: String) -> Data?`: Encrypts a string using a RSA key from keychain specified by `tagName`. 119 | - `static func decryptWithRSAKey(encryptedData: Data, tagName: String) -> Data?`: Decrypts an encrypted data using a RSA key from keychain specified by `tagName`. 120 | - `static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String) throws -> Data?`: Encrypts data using RSA public key. 121 | - The public key is in base-64 X509 format. 122 | - Throws `RSAUtilsError` if the input key is not a valid X509 public key. 123 | - `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` can be omitted. 124 | - `static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String) throws -> Data?`: Encrypts a string using RSA public key. 125 | - `static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String, tagName: String) throws -> Data?`: Encrypts data using RSA public key. Also, the public key will be stored in keychain specified by `tagName`. 126 | - `static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String, tagName: String) throws -> Data?`: Encrypts a string using RSA public key. Also, the public key will be stored in keychain specified by `tagName`. 127 | - `static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String) throws -> Data?`: Decrypts an encrypted data using a RSA private key. 128 | - `static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String, tagName: String) throws -> Data?`: Decrypts an encrypted data using a RSA private key. Also, the private key will be stored in keychain specified by `tagName`. 129 | 130 | 131 | Encrypt/Decrypt Large Amount of Data 132 | ------------------------------------ 133 | 134 | The amount of data that can be encrypted in one go is `key's size - 11`. For example, if the key's size is 256 bytes (2048 bits), maximum amount of data that can be encrypted is 245 bytes. 135 | 136 | In order to encrypt a large amount of data, `encryptXXX()` function splits the large data into chunks of size `key's size - 11`, encrypts each chunk and concatenates encrypted chunks into the final result. 137 | 138 | Decryption performs similarly: encrypted data is splitted into chunks of size `key's size`, each chunk is then decrypted and concatenated into the final result. 139 | 140 | 141 | Changes 142 | ------- 143 | 144 | - Since v1.2.1 - 2016-09-18: add support for RSA PKCS#1 private key. 145 | - Since v1.2.0 - 2016-09-17: migrated from [https://github.com/btnguyen2k/swift-rsautils](https://github.com/btnguyen2k/swift-rsautils). 146 | -------------------------------------------------------------------------------- /README-SnowflakeIdGenerator.md: -------------------------------------------------------------------------------- 1 | SnowflakeIdGenerator 2 | ==================== 3 | 4 | Swift implementation of Twitter's Snowflake algorithm. 5 | 6 | 7 | Usage 8 | ----- 9 | 10 | ```swift 11 | let idGen = SwiftIdGenerator() 12 | 13 | // or 14 | // let nodeId: UInt64 = calcDeviceIdAsInt() 15 | // let idGen = SwiftIdGenerator(nodeId) 16 | 17 | let id64 = idGen.generateId64() // generate a 64-bit id 18 | let id64Hex = idGen.generateid64Hex() // generate a 64-bit id as a hex string 19 | let id128Hex = idGen.generateid128Hex() // generate a 128-bit id as a hex string 20 | 21 | // extract the timestamp as a UNIX timestamp in millisecond from a generated id 22 | let timestamp = SwiftIdGenerator.extractTimestamp64(id64) 23 | 24 | // extract the timestamp as a Date from a generated id 25 | let date = SwiftIdGenerator.extractTimestamp128HexAsDate(id128Hex) 26 | ``` 27 | 28 | 29 | Functions 30 | --------- 31 | 32 | - 64-bit ID (format Format `<41-bits: timestamp><21-bits: node id><2-bits: sequence number>`, where timestamp is in millisec, minus the epoch): 33 | - `generateId64() -> UInt64`: generate a 64-bit id. 34 | - `generateId64Hex() -> String`: generate a 64-bit id as a hex string. 35 | - `generateId64Bin() -> String`: generate a 64-bit id as a binary string. 36 | - `static extractTimestamp64(id: UInt64) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64()`. 37 | - `static extractTimestamp64Hex(idHex: String) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64Hex()`. 38 | - `static extractTimestamp64Bin(idBin: String) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64Bin()`. 39 | - `static extractTimestamp64AsDate(id: UInt64) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64()` as a `Date`. 40 | - `static extractTimestamp64HexAsDate(idHex: String) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64Hex()` as a `Date`. 41 | - `static extractTimestamp64BinAsDate(idBin: String) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64Bin()` as a `Date`. 42 | - 64-bit-nil ID (format Format `<41-bits: timestamp><23-bits: node id>` (no sequence part), where timestamp is in millisec, minus the epoch): 43 | - `generateId64Nil() -> UInt64`: generate a 64-bit id. 44 | - `generateId64NilHex() -> String`: generate a 64-bit id as a hex string. 45 | - `generateId64NilBin() -> String`: generate a 64-bit id as a binary string. 46 | - `static extractTimestamp64Nil(id: UInt64) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64Nil()`. 47 | - `static extractTimestamp64NilHex(idHex: String) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64NilHex()`. 48 | - `static extractTimestamp64NilBin(idBin: String) -> UInt64`: extract the UNIX timestamp from a 64-bit id generated by `generateId64NilBin()`. 49 | - `static extractTimestamp64NilAsDate(id: UInt64) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64Nil()` as a `Date`. 50 | - `static extractTimestamp64NilHexAsDate(idHex: String) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64NilHex()` as a `Date`. 51 | - `static extractTimestamp64NilBinAsDate(idBin: String) -> Date`: extract the timestamp part from a 64-bit id generated by `generateId64NilBin()` as a `Date`. 52 | - 128-bit ID (format Format `<64-bits: timestamp><48-bits: node id><16-bits: sequence number>`, where timestamp is in millisec): 53 | - `generateId128() -> [UInt64]`: generate a 128-bit id as an array of two UInt64s. 54 | - `generateId128Hex() -> String`: generate a 128-bit id as a hex string. 55 | - `generateId128Bin() -> String`: generate a 128-bit id as a binary string. 56 | - `static extractTimestamp128Hex(idHex: String) -> UInt64`: extract the UNIX timestamp from a 128-bit id generated by `generateId128Hex()`. 57 | - `static extractTimestamp128Bin(idBin: String) -> UInt64`: extract the UNIX timestamp from a 128-bit id generated by `generateId128Bin()`. 58 | - `static extractTimestamp128HexAsDate(idHex: String) -> Date`: extract the timestamp part from a 128-bit id generated by `generateId128Hex()` as a `Date`. 59 | - `static extractTimestamp128BinAsDate(idBin: String) -> Date`: extract the timestamp part from a 128-bit id generated by `generateId128Bin()` as a `Date`. 60 | 61 | 62 | Changes 63 | ------- 64 | 65 | - Since v1.1.1 - 2016-09-16: output type changed from `NSDate` to `Date`. 66 | - Since v1.0 - 2016-08-15: first release 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwiftUtils 2 | ========== 3 | 4 | Utility Library in Swift. 5 | 6 | By Thanh Ba Nguyen (btnguyen2k (at) gmail.com). 7 | 8 | Project home: 9 | [https://github.com/btnguyen2k/swiftutils](https://github.com/btnguyen2k/swiftutils) 10 | 11 | 12 | ## License ## 13 | 14 | See LICENSE.txt for details. Copyright (c) 2016 Thanh Ba Nguyen. 15 | 16 | Third party libraries are distributed under their own license(s). 17 | 18 | 19 | ## Release Notes ## 20 | 21 | Latest release version: `1.3`. See [RELEASE-NOTES.md](RELEASE-NOTES.md) for details. 22 | 23 | 24 | DateTimeUtils 25 | ------------- 26 | 27 | Date & time utility class. See [README-DateTimeUtils.md](README-DateTimeUtils.md). 28 | 29 | 30 | SnowflakeIdGenerator 31 | -------------------- 32 | 33 | Swift implementation of Twitter's Snowflake algorithm. See [README-SnowflakeIdGenerator.md](README-SnowflakeIdGenerator.md). 34 | 35 | 36 | RSAUtils 37 | -------- 38 | 39 | Utility class to encrypt/decrypt data using RSA public/private key. See [README-RSAUtils.md](README-RSAUtils.md). 40 | 41 | 42 | SyncUtils 43 | --------- 44 | 45 | * Synchronizes a code block: * 46 | 47 | ```swift 48 | let result = synchronizd(lockObj) { 49 | // .... code here .... 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | SwiftUtils Release Notes 2 | ======================== 3 | 4 | 1.3 - 2016-11-22 5 | ------------------ 6 | 7 | - `DateTimeUtils`: add new utility functions 8 | - `addSeconds(_ date: Date, numSeconds: Int) -> Date` 9 | - `addMinutes(_ date: Date, numMinutes: Int) -> Date` 10 | - `addHours (_ date: Date, numHours : Int) -> Date` 11 | - `addDays (_ date: Date, numDays : Int) -> Date` 12 | - `addMonths (_ date: Date, numMonths : Int) -> Date` 13 | - `addYears (_ date: Date, numYears : Int) -> Date` 14 | - `addWeeks (_ date: Date, numWeeks : Int) -> Date` 15 | 16 | 17 | 1.2.1 - 2016-09-18 18 | ------------------ 19 | 20 | - `RSAUtils`: add support for RSA PKCS#1 private key. 21 | 22 | 23 | 1.2.0 - 2016-09-17 24 | ------------------ 25 | 26 | - `RSAUtils`: utility class to encrypt/decrypt data with RSA public/private key. 27 | 28 | 29 | 1.1.1 - 2016-09-16 30 | ------------------ 31 | 32 | - Migrated to Swift3/XCode8. 33 | - `DateTimeUtils`: input/output type changed from `NSDate` to `Date` 34 | - Documents updated. 35 | 36 | 37 | 1.1 - 2016-08-27 38 | ---------------- 39 | 40 | - `DateTimeUtils`: utility functions 41 | - `startOfSecond(_ date: NSDate) -> NSDate` 42 | - `startOfMinute(_ date: NSDate) -> NSDate` 43 | - `startOfHour (_ date: NSDate) -> NSDate` 44 | - `startOfDay (_ date: NSDate) -> NSDate` 45 | - `startOfMonth (_ date: NSDate) -> NSDate` 46 | - `startOfYear (_ date: NSDate) -> NSDate` 47 | - `startOfWeek (_ date: NSDate) -> NSDate` 48 | 49 | 50 | 1.0 - 2016-08-15 51 | ---------------- 52 | 53 | First release: 54 | - `SnowflakeIdGenerator`: utility class to generate ID using Twitter's Snowflake algorithm. 55 | - `DateTimeUtils`: some useful datetime utility functions. 56 | -------------------------------------------------------------------------------- /SwiftUtils.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | import SwiftUtils 5 | @testable import SwiftUtils 6 | 7 | /*--- START DateTimeUtils ----*/ 8 | //let df = DateFormatter() 9 | //df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" 10 | // 11 | //let d1 = Date() 12 | //print("Original date :", d1) 13 | //print("Date format :", df.string(from: d1)) 14 | //print("+2 weeks format:", df.string(from: DateTimeUtils.addWeeks(d1, numWeeks: 2))) 15 | /*--- END DateTimeUtils ----*/ 16 | 17 | /*--- START RSAUtils ----*/ 18 | let pubKey64 = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkw7pYdYdpFJhpb0D4hWesYA7hvC7ykRGO8VlsiJYuf7BgLZeUE5OYEP+mynePuHEh6SPhzkxgHp/LFSQdFrNIJRslOrE35yAEE2+nYQzZSgDPIFC8tN9458/bFiKExFfgUn8Eu6rhXGSPJOD93dRPspVCq0ZyOKzANPJfi8kixb/kDUU2ZKr9Gc8Rgt1JWgED4eribrtU14AvUzXibF5n1TFmbQ33Ik22U3Rrm6i914cP4de5fn02WiIBA1ohXwQRemUxlb1Sj7YFKqMo8nNBioGoRMsSpbzBiYyQfN93VE/ehMUQIjAx5AIhALo5qMWuGjYYQZo5Tz5EhnqyzUvD6F0R3Ii2GcMTPIqHmmciLQNIyLdJoG9NJxbGpEzogNIoFQ4BNGYQLlZ6Z7wHTKZs5nvWCA0Pbb1t24jQ2rgw+gEQ1krcM/Q6zuL5+3pIARiSmt3wrNYrzCZ9gM/xmxE8OFizLA9KjOnEUqB6ULj0KT6MkFB8EqN4VHSnLsbALfefTw57UldYQweui0GRZwQ+dnjs7rd7DvAIifdDjY8AVX4iJ2qYMwUiHhjlKBjPW1CQpeU+Emdl1oY1/gTHOeJOoBZd8wRpLFFY7V0JR2aQhi9eVwpn5GT0w4DML+tNDbil2m8StySom760MbnHtIy6HIK7E4HF8Iq9lZZpsKUL+MCAwEAAQ==" 19 | let privKey64 = "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCTDulh1h2kUmGlvQPiFZ6xgDuG8LvKREY7xWWyIli5/sGAtl5QTk5gQ/6bKd4+4cSHpI+HOTGAen8sVJB0Ws0glGyU6sTfnIAQTb6dhDNlKAM8gULy033jnz9sWIoTEV+BSfwS7quFcZI8k4P3d1E+ylUKrRnI4rMA08l+LySLFv+QNRTZkqv0ZzxGC3UlaAQPh6uJuu1TXgC9TNeJsXmfVMWZtDfciTbZTdGubqL3Xhw/h17l+fTZaIgEDWiFfBBF6ZTGVvVKPtgUqoyjyc0GKgahEyxKlvMGJjJB833dUT96ExRAiMDHkAiEAujmoxa4aNhhBmjlPPkSGerLNS8PoXRHciLYZwxM8ioeaZyItA0jIt0mgb00nFsakTOiA0igVDgE0ZhAuVnpnvAdMpmzme9YIDQ9tvW3biNDauDD6ARDWStwz9DrO4vn7ekgBGJKa3fCs1ivMJn2Az/GbETw4WLMsD0qM6cRSoHpQuPQpPoyQUHwSo3hUdKcuxsAt959PDntSV1hDB66LQZFnBD52eOzut3sO8AiJ90ONjwBVfiInapgzBSIeGOUoGM9bUJCl5T4SZ2XWhjX+BMc54k6gFl3zBGksUVjtXQlHZpCGL15XCmfkZPTDgMwv600NuKXabxK3JKibvrQxuce0jLocgrsTgcXwir2VlmmwpQv4wIDAQABAoICADtPiFEtSkc78qYl1asZjSeJ0HhcH2E0qB7zPlDaCW76kJCY/PDmpDWvNsDd4gT6iYkrlbe0GYL35NK8SIalGJZLRn/JMB3/wKbStE5TQ2dI/AUrHRVStzNWxv2ruAR4vUwnysLX/9WSOKi11kO4m8v3TtU/e2bKB+gNgvahjNuMKwa+YepefXvVJzoY5OZxTTH2RfkGqzE0eJfgpg2ixqq8RfX3+Y+4x6Zc/HSdqpyaGEqHDn8ykM4emr0Hq2Aq6WVJD/fumn+XWQ78ujxU55vU11efo/1xZBLW1O5Ehj78xd02VLSycw4JyWV0xvo+TKuTnbGEJEpQjSP/z/KlQJUtaY+vaicRwGV5uLVA7jxJQlFHrENQd47sRsZEnwmsBwUWfu7ZSf6YLhnV5XRM5oumnSM9wfiJR3XlOAz6wBYRjY/XbnYW/4uHFmxEXkjuWQBWyI9/OTYHYTrZDgIWnmdW4bP+9tuXzbaSkAisW850w59z3lLD5967WMuYRNgJYXrxc415dWlKU4N3n8eyhAr86dJ+yzIg/mfoAN+uSns+cOm+wnjjOiA8Ea6cusVpm9+UoXgjr6Ir4f4oDzYJYuo4snJL0Ilb/GYPXs1sjOPHizPT9GzL+6Aei1C00tY1Vn2qaoup/4mXYJ9t+ZAsWv+QGnTLrkJxQ+A1p2jnNmYBAoIBAQDuoEfVFFNbQQSAPUjLgXlI7CREIRbsKtjqOHbOZHBw5sbf0O1tFk6+dI/SNRQQE1g8PBakIaE02hQwkS0TJ1W6V4hPIXO7o1zavkk05WqlfPY2IdlyDETB5hANsTycCA4WIYROZ9Z+aQI2xKlA8VoQoTOpdnmENOwzhhIU09hA4/hNMtKucMCnOnqB8Ab7TLBhM5Mlis79TCnqWmLI7f5svyM/I1ZFIs2FYqn1IoWA4Ldg1LIzxeBw55Gza37J0/gSmAOARCOfCgxESklOC2EVje6ZlOVvYBkBLLzi6I9bgf1lGa1fOia6qFBr7Y3kFDo84ghVN9w6iy15KAS46Oh3AoIBAQCdw+kOjMCXf77k7P/UTyes2NXc5hrF+FSHxmliXtyLZvPSlOirPYenZwkuDf9ZbpCLy1Wt66eOgU1EkFyDm4Oz1o6r1mjhc0Pua84xKgK67O9UPfxlKm2GptwYzYApLqYm5+sWanRrFGOvQkFIQdrV4IwAkl+CNyZdmzInqxtPXuTT7HXoYNKa2ZPcVXn09Ao8q6OZYEb3UcqebsKGT3f6Jwft59HBVhz7MrYm0qxp45/RtF9zDjR5lAtI3UDmTJ/CZetTNqymUkGiFb6RUVrtCSfmwzV7EdPUoU6rdJNr/Yz+DBYpjofhKa4VTx+/1Ry+g/YyvIAHQMcRMBjog3r1AoIBAQChXaX1w77ysK74gXjelXmNBJMpty6nGfqBuRkuTOF0l7rY05Ia+MDbLjurrNUaYdqDMScmas92zBpnFWVj1G+2iUbWLAr1Um2SRqd+q71Il45v4MgIxZ8heBBXEgmoghH0iUnNM47rANvMaYUDM7LyF/C1ojZeXuNznDBSAUf16bOZkMrDx9+ftn3trIEsIxoLQTM6afm7dGvL3L98nOQ2abLOGXlXW0eb8bJ44JVjJ5MaWfr/48n4z7/JkuG0XjEdccq8TFfCTqso6wtTSYKnbslw9i2I+XUvRC+fSiDXgrn2SjzhbpOOGg/4TDZti/gt/7Qe3J2CwWdIzPUUjfvVAoIBAE3GKrpVgQwumbjZbTUI4JJBQwznEwPNbD6S/ZjVEvxWAJUre5gPeTR5gvkFRYCdFP0VfGOE7NE/xQur7y+iFsctWqAzzvh1jmOXGcr63uWvsDxjYm2tANmEoLRJeojSym4bnrUqPcIcBxh7HRu1+1+8lsghYJwc+/jhbVRI8emq6jvfMjEgqjJUWKQiL0EmnXVTWA9gexupYq3ABH2Z55eWbj5GLg2Vmivr0AhLi1uYL01+Eh/yPMRCy11cVYYy9/8pp10acvp7SofRGGUjKiP4g4crbM4C9962tsWnbpWqJTuIUdSiwzGpDnzKyOgU81qoS1Kvwp3QvVIn19+oEZECggEBAMrTCHmNl9vfSz5o9vE9LVen5Kx/0PMAccDBHIHjkYg+6gUfoTW0dEPQuhtTLys3RntpjQrnuH/+RpJVwNzKKWHLmx4u8NZXR2un+0uJ18Ya++o5a04CjycO0PMVkKIcN/ujpPIo/FEMXrnN4gPMDnTMDjwCeAjjuvQUImOf6CjPIogK7S68gCplezz1OQGvlsA28y0fPUBQITQThVh+SZP4byRuS21tNk0SgJNwcf/KTvT/xfI9TmmCG9+jpDKhEymLQgEYGPRU/EhBgsSqyB4J5q8vOVhGcvRolwdWuqufl1tLTKLKHwSD7nlN8bRJiwX+JPFM0GY+as6CMlaRh98=" 20 | 21 | let tagPublic = "com.github.btnguyen2k.ios.SwiftUtilsPlayground.public" 22 | let tagPrivate = "com.github.btnguyen2k.ios.SwiftUtilsPlayground.private" 23 | let keyRef = try RSAUtils.addRSAPublicKey(pubKey64, tagName: tagPublic) 24 | 25 | //let orgString = "This a string" 26 | //let encData = try! RSAUtils.encryptWithRSAPublicKey(str: orgString, pubkeyBase64: pubKey64, tagName: "com.github.btnguyen2k.ios.SwiftUtilsPlayground.public") 27 | 28 | // let eData = Data(base64Encoded: eData64, options: []) 29 | // 30 | //// let data = "I'm a string" 31 | //// 32 | //// let eData = try! RSAUtils.encryptWithRSAPublicKey(str: data, pubkeyBase64: pubKey64, tagName: "com.github.btnguyen2k.ios.SwiftUtilsPlayground.public") 33 | // let dData = try! RSAUtils.decryptWithRSAPrivateKey(encryptedData: eData!, privkeyBase64: privKey64, tagName: "com.github.btnguyen2k.ios.SwiftUtilsPlayground.private") 34 | // let dDataStr = String(data: dData!, encoding: .utf8) 35 | // 36 | // //NSLog("Original data : \(data)") 37 | // NSLog("Encrypted data: \(eData!)") 38 | // NSLog("Decrypted data: \(dData!)") 39 | // NSLog("Decrypted data: \(dDataStr!)") 40 | 41 | 42 | /*--- END RSAUtils ----*/ 43 | 44 | //var privKeyStr = "-----BEGIN RSA PRIVATE KEY-----\n" + 45 | //"MIIEpAIBAAKCAQEAtVDMVHzXEweGJH600LQsa8cpd9lrtwEbU6mOJ6/paKB4GaDpVQLbe1B6vRtE\n" + 46 | //"QHYVjvJRUZvMpP/C5iNWrI8MCsxp5QfySPU1O+lVOK9/sCZZNZvbjLnNF1MRbgW0cNZ6B69hAPk7\n" + 47 | //"61+yKYvhLOqQvFbiKj4zsXNdALO9yt5ogP1is0KclBEZjHgv3hWeTQqe2W9qk7W4AyNXML9So5Za\n" + 48 | //"Ftm4xj/y6N8uRDpJ9WSRJkbZY1C/GaGLOSyCAEdrpqJiv5fHN5JgjCTGA1TZC+XrIVsLh4SRSgaZ\n" + 49 | //"owgDADvDL7bke0NcZo7YDNn8xEarKURSBqdEkvt6TNipYd3vpSXCLwIDAQABAoIBAQCo/YeT708d\n" + 50 | //"w1N5TEcGhgco5NV3iMNmK7fmgA+oWcJ366hEar9Opn0eL+JVwsYYNnbdZmf4YfeYiXCDwsgTyLd2\n" + 51 | //"kRLZjlB5Q2G8VztAvAGMkhFvb7ZvMPVAGgISw2psCNpvszv8Bk+cjvKk2mSi0nTXtTrSGGthpp41\n" + 52 | //"+1CXdjd8kBqnZGy1PjQDclT5RQOQPppIV1QE5f4HJYsEjipjvEAf9KGcukL5+U2X1EYJue2cfTGH\n" + 53 | //"VLVj9D5COsEsVX3yoJd7v6+Vv2QFJOkbQ0AR49vxezhoIVyB4Wona8jhMu5nbToB+0xwjtBPnOmr\n" + 54 | //"KIa9sX8+lamBh8Ig8HCP0GZrpEPhAoGBAOnzQaK/LCCC9RmKS6W16EkTc5lb5AlQqiT2HUsUDFzn\n" + 55 | //"A3ylTvUtlorsWMQTm1sidvgpFYRzNwGChK/mby55GmD7Ki4E35saXM9O8Sc5K+xHOm/Qm7ug0h/V\n" + 56 | //"Z3vtPaygvct3Me9QTr3EzV+//LzkP0fydRuCb1yVlgdsh9ixJ393AoGBAMZnk2MN8I9hnZtNxUYH\n" + 57 | //"K2kmJUYj2kl6RdI4HQJDefrJ6Ax+1sop1+y9wipy/7kaGvEtMjLPWQ1FDlqd63mSKDK8CoHUtppX\n" + 58 | //"UI79w/d5aAkKBtEYS2opqYHwVoDeBJpMtII9mzGbi//mpK1peB3wKFUw8422eb5oWR2gvUXzp7EJ\n" + 59 | //"AoGAe683jCs10RprKk2LINjDLZ8zBmkmbiZPTaswYXj/8D3SAhwDWmv3owrBAyi6gbv+2cQetB+V\n" + 60 | //"fR7bt4NEVHWCFLO04Q+CiCWv24oIxIvdRNWDd4n6S4AClwHj7AjJCnMQ5Hjn1XXHq1xYl76Qh+zD\n" + 61 | //"5+2vLBQRfAk5gENIrq86CpECgYAM1UA8RPeNXqQCGpqq9+W/7F621bTV0dnYShbeQZ0PMqYWwNGF\n" + 62 | //"zAUSZAe+FSgjQeQCO4kmrjC1YgUWqWZEu8Zr2avYT/jrrMAiL5nQVQL8qrBcL/UYzc8P/4qo1TN5\n" + 63 | //"A4MddXynTcdVawa1zL+fOBxzvjHStb3UKg2xk+cKMkNjeQKBgQDJ8bo0s/eMDw5F9oCIEOopCi2N\n" + 64 | //"qxs6mkglF78bwO39uN92+6yK+Ue92y+DkgeYhr43hLbL7vOZmijPire4hqHsl7wTf/vzwQ0DrXkd\n" + 65 | //"75r6xcez9oCLa61clSGP2DWSa9b+Bee0ybwWovPDeZAhnyG9rilN93DPFD374l/WREv5gA==\n" + 66 | //"-----END RSA PRIVATE KEY-----" 67 | //privKeyStr.characters.count 68 | // 69 | //let pattern = "(-----BEGIN.*?-----)|(-----END.*?-----)|\\s+" 70 | // 71 | //let regExp = try! NSRegularExpression(pattern: pattern, options: []) 72 | // 73 | //privKeyStr = regExp.stringByReplacingMatches(in: privKeyStr, options: [], range: NSRange(location: 0, length: privKeyStr.characters.count), withTemplate: "") 74 | //privKeyStr.characters.count 75 | 76 | //df.string(from: DateTimeUtils.startOfSecond(d1)) 77 | 78 | //df.string(from: DateTimeUtils.addSeconds(d1, numSeconds: 5)) 79 | //df.string(from: DateTimeUtils.addMinutes(d1, numMinutes: 1)) 80 | //df.string(from: DateTimeUtils.addHours(d1, numHours: 1)) 81 | 82 | // 83 | //let d1 = Date() 84 | //df.string(from: d1) 85 | //df.string(from: DateTimeUtils.startOfSecond(d1)) 86 | //df.string(from: DateTimeUtils.startOfMinute(d1)) 87 | //df.string(from: DateTimeUtils.startOfHour(d1)) 88 | //df.string(from: DateTimeUtils.startOfDay(d1)) 89 | //df.string(from: DateTimeUtils.startOfMonth(d1)) 90 | //df.string(from: DateTimeUtils.startOfYear(d1)) 91 | //df.string(from: DateTimeUtils.startOfWeek(d1)) 92 | // 93 | //let d2 = NSDate() 94 | //df.string(from: d2 as Date) 95 | // 96 | //let idGen = SnowflakeIdGenerator(103) 97 | ////let idGen2 = SnowflakeIdGenerator(1981) 98 | //let id64 = idGen.generateId64() 99 | //SnowflakeIdGenerator.extractTimestamp64(id64) 100 | 101 | // 102 | //idGen.generateId128() 103 | //idGen.generateId128Hex() 104 | //idGen.generateId128Bin() 105 | // 106 | //idGen2.generateId64() 107 | //idGen2.generateId64Hex() 108 | //idGen2.generateId64Bin() 109 | // 110 | //let cal = NSCalendar.current 111 | //let date = NSDate() 112 | ////DateFormatter("yyyy-MM-dd HH:mm:ss").string(from: date as Date) 113 | //let df = DateFormatter() 114 | //df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" 115 | // 116 | //cal.component(Calendar.Component.weekday, from: date as Date) 117 | //cal.firstWeekday 118 | // 119 | ////let currentDateComponents = cal.dateComponents([.year, .month, .day, .weekOfYear], from: date as Date) 120 | ////df.string(from: cal.date(from: currentDateComponents)!) 121 | // 122 | ////df.string(from: DateTimeUtils.startOfWeek(date) as Date) 123 | // 124 | //df.string(from: date as Date) 125 | ////df.string(from: cal.startOfDay(for: date as Date)) 126 | ////df.string(from: DateTimeUtils.startOfSecond(date) as Date) 127 | ////df.string(from: DateTimeUtils.startOfMinute(date) as Date) 128 | ////df.string(from: DateTimeUtils.startOfHour(date) as Date) 129 | ////df.string(from: DateTimeUtils.startOfDate(date) as Date) 130 | ////df.string(from: DateTimeUtils.startOfMonth(date) as Date) 131 | ////df.string(from: DateTimeUtils.startOfYear(date) as Date) 132 | //df.string(from: DateTimeUtils.startOfWeek(date) as Date) 133 | //cal.component(Calendar.Component.weekday, from: date as Date) 134 | //var d = DateTimeUtils.startOfDate(date) as Date 135 | //cal.date(byAdding: Calendar.Component.weekday, value: -1, to: d)! 136 | // 137 | ////cal.startOfDay(for: date as Date) 138 | ////DateTimeUtils.startOfMinute(date) 139 | ////var comps = cal.dateComponents([Calendar.Component.day, Calendar.Component.month, Calendar.Component.year, Calendar.Component.weekOfYear, Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second, Calendar.Component.nanosecond], from: date as Date) 140 | ////comps.setValue(1, for: Calendar.Component.hour) 141 | ////cal.date(from: comps) 142 | // 143 | ////let components: NSDateComponents = cal?.components(NSCalendar.Unit.CalendarUnit, fromDate: date) 144 | // 145 | //date 146 | // 147 | // 148 | ///* 149 | //idGen.generateId64() 150 | //idGen.generateId64Hex() 151 | //idGen.generateId64Bin() 152 | // 153 | //idGen2.generateId64() 154 | //idGen2.generateId64Hex() 155 | //idGen2.generateId64Bin() 156 | // 157 | //idGen.getLastTimestampMillisec() 158 | //idGen2.getLastTimestampMillisec() 159 | // 160 | //idGen.generateId128() 161 | //idGen.generateId128Hex() 162 | //idGen.generateId128Bin() 163 | // 164 | //idGen2.generateId128() 165 | //idGen2.generateId128Hex() 166 | //idGen2.generateId128Bin() 167 | //*/ 168 | // 169 | ////String(12345, radix: 2) 170 | ////String(83630470, radix: 2) 171 | // 172 | ////idGen.generateId64() 173 | ////idGen.generateId128() 174 | ////var id = idGen.generateId128Hex() 175 | ////let index = id.index(id.endIndex, offsetBy: -16) 176 | ////id.substring(to: index) 177 | ////SnowflakeIdGenerator.extractTimestamp128Hex(idHex: id) 178 | ////SnowflakeIdGenerator.extractTimestamp128HexAsDate(idHex: id) 179 | // 180 | ////idGen2.generateId64() 181 | ////idGen2.generateId128() 182 | ////var id2 = idGen2.generateId128Hex() 183 | ////SnowflakeIdGenerator.extractTimestamp128Hex(idHex: id2) 184 | ////SnowflakeIdGenerator.extractTimestamp128HexAsDate(idHex: id2) 185 | -------------------------------------------------------------------------------- /SwiftUtils.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SwiftUtils.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4C1BAB781D6210A1003B699E /* RELEASE-NOTES.md in Sources */ = {isa = PBXBuildFile; fileRef = 4C1BAB771D6210A1003B699E /* RELEASE-NOTES.md */; }; 11 | 4C2AD78F1D605B2D003AF2DD /* SwiftUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C2AD7851D605B2D003AF2DD /* SwiftUtils.framework */; }; 12 | 4C2AD7941D605B2D003AF2DD /* SwiftUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2AD7931D605B2D003AF2DD /* SwiftUtilsTests.swift */; }; 13 | 4C2AD7A21D605C08003AF2DD /* SnowflakeIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2AD7A11D605C08003AF2DD /* SnowflakeIdGenerator.swift */; }; 14 | 4C4AC9911D8E635F00F72BE3 /* README-RSAUtils.md in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AC9901D8E635F00F72BE3 /* README-RSAUtils.md */; }; 15 | 4CC4BC261D6158D0000EF79D /* DateTimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC4BC251D6158D0000EF79D /* DateTimeUtils.swift */; }; 16 | 4CC4BC281D615D5F000EF79D /* SyncUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC4BC271D615D5F000EF79D /* SyncUtils.swift */; }; 17 | 4CC4BC2C1D620F32000EF79D /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4CC4BC291D620F32000EF79D /* LICENSE.txt */; }; 18 | 4CC4BC2D1D620F32000EF79D /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 4CC4BC2A1D620F32000EF79D /* README.md */; }; 19 | 4CC503291D8BB30300967A15 /* README-DateTimeUtils.md in Sources */ = {isa = PBXBuildFile; fileRef = 4CC503281D8BB30300967A15 /* README-DateTimeUtils.md */; }; 20 | 4CC5032B1D8BB55300967A15 /* README-SnowflakeIdGenerator.md in Sources */ = {isa = PBXBuildFile; fileRef = 4CC5032A1D8BB55300967A15 /* README-SnowflakeIdGenerator.md */; }; 21 | 4CC5032D1D8BBBDD00967A15 /* RSAUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC5032C1D8BBBDD00967A15 /* RSAUtils.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 4C2AD7901D605B2D003AF2DD /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 4C2AD77C1D605B2D003AF2DD /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 4C2AD7841D605B2D003AF2DD; 30 | remoteInfo = SwiftUtils; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 4C1BAB771D6210A1003B699E /* RELEASE-NOTES.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "RELEASE-NOTES.md"; sourceTree = ""; }; 36 | 4C2AD7851D605B2D003AF2DD /* SwiftUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 4C2AD7891D605B2D003AF2DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 4C2AD78E1D605B2D003AF2DD /* SwiftUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 4C2AD7931D605B2D003AF2DD /* SwiftUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUtilsTests.swift; sourceTree = ""; }; 40 | 4C2AD7951D605B2D003AF2DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 4C2AD7A11D605C08003AF2DD /* SnowflakeIdGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnowflakeIdGenerator.swift; sourceTree = ""; }; 42 | 4C4AC9901D8E635F00F72BE3 /* README-RSAUtils.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "README-RSAUtils.md"; sourceTree = ""; }; 43 | 4CC4BC251D6158D0000EF79D /* DateTimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateTimeUtils.swift; sourceTree = ""; }; 44 | 4CC4BC271D615D5F000EF79D /* SyncUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncUtils.swift; sourceTree = ""; }; 45 | 4CC4BC291D620F32000EF79D /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 46 | 4CC4BC2A1D620F32000EF79D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 47 | 4CC503281D8BB30300967A15 /* README-DateTimeUtils.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "README-DateTimeUtils.md"; sourceTree = ""; }; 48 | 4CC5032A1D8BB55300967A15 /* README-SnowflakeIdGenerator.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "README-SnowflakeIdGenerator.md"; sourceTree = ""; }; 49 | 4CC5032C1D8BBBDD00967A15 /* RSAUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSAUtils.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 4C2AD7811D605B2D003AF2DD /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 4C2AD78B1D605B2D003AF2DD /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 4C2AD78F1D605B2D003AF2DD /* SwiftUtils.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 4C2AD77B1D605B2D003AF2DD = { 72 | isa = PBXGroup; 73 | children = ( 74 | 4C4AC9901D8E635F00F72BE3 /* README-RSAUtils.md */, 75 | 4CC5032A1D8BB55300967A15 /* README-SnowflakeIdGenerator.md */, 76 | 4CC503281D8BB30300967A15 /* README-DateTimeUtils.md */, 77 | 4C1BAB771D6210A1003B699E /* RELEASE-NOTES.md */, 78 | 4CC4BC291D620F32000EF79D /* LICENSE.txt */, 79 | 4CC4BC2A1D620F32000EF79D /* README.md */, 80 | 4C2AD7871D605B2D003AF2DD /* SwiftUtils */, 81 | 4C2AD7921D605B2D003AF2DD /* SwiftUtilsTests */, 82 | 4C2AD7861D605B2D003AF2DD /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 4C2AD7861D605B2D003AF2DD /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 4C2AD7851D605B2D003AF2DD /* SwiftUtils.framework */, 90 | 4C2AD78E1D605B2D003AF2DD /* SwiftUtilsTests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | 4C2AD7871D605B2D003AF2DD /* SwiftUtils */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 4C2AD7891D605B2D003AF2DD /* Info.plist */, 99 | 4C2AD7A11D605C08003AF2DD /* SnowflakeIdGenerator.swift */, 100 | 4CC4BC251D6158D0000EF79D /* DateTimeUtils.swift */, 101 | 4CC4BC271D615D5F000EF79D /* SyncUtils.swift */, 102 | 4CC5032C1D8BBBDD00967A15 /* RSAUtils.swift */, 103 | ); 104 | path = SwiftUtils; 105 | sourceTree = ""; 106 | }; 107 | 4C2AD7921D605B2D003AF2DD /* SwiftUtilsTests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 4C2AD7931D605B2D003AF2DD /* SwiftUtilsTests.swift */, 111 | 4C2AD7951D605B2D003AF2DD /* Info.plist */, 112 | ); 113 | path = SwiftUtilsTests; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXHeadersBuildPhase section */ 119 | 4C2AD7821D605B2D003AF2DD /* Headers */ = { 120 | isa = PBXHeadersBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXHeadersBuildPhase section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 4C2AD7841D605B2D003AF2DD /* SwiftUtils */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 4C2AD7991D605B2D003AF2DD /* Build configuration list for PBXNativeTarget "SwiftUtils" */; 132 | buildPhases = ( 133 | 4C2AD7801D605B2D003AF2DD /* Sources */, 134 | 4C2AD7811D605B2D003AF2DD /* Frameworks */, 135 | 4C2AD7821D605B2D003AF2DD /* Headers */, 136 | 4C2AD7831D605B2D003AF2DD /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = SwiftUtils; 143 | productName = SwiftUtils; 144 | productReference = 4C2AD7851D605B2D003AF2DD /* SwiftUtils.framework */; 145 | productType = "com.apple.product-type.framework"; 146 | }; 147 | 4C2AD78D1D605B2D003AF2DD /* SwiftUtilsTests */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 4C2AD79C1D605B2D003AF2DD /* Build configuration list for PBXNativeTarget "SwiftUtilsTests" */; 150 | buildPhases = ( 151 | 4C2AD78A1D605B2D003AF2DD /* Sources */, 152 | 4C2AD78B1D605B2D003AF2DD /* Frameworks */, 153 | 4C2AD78C1D605B2D003AF2DD /* Resources */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | 4C2AD7911D605B2D003AF2DD /* PBXTargetDependency */, 159 | ); 160 | name = SwiftUtilsTests; 161 | productName = SwiftUtilsTests; 162 | productReference = 4C2AD78E1D605B2D003AF2DD /* SwiftUtilsTests.xctest */; 163 | productType = "com.apple.product-type.bundle.unit-test"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | 4C2AD77C1D605B2D003AF2DD /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastSwiftUpdateCheck = 0800; 172 | LastUpgradeCheck = 1150; 173 | ORGANIZATIONNAME = "Thanh Nguyen"; 174 | TargetAttributes = { 175 | 4C2AD7841D605B2D003AF2DD = { 176 | CreatedOnToolsVersion = 8.0; 177 | LastSwiftMigration = 1140; 178 | }; 179 | 4C2AD78D1D605B2D003AF2DD = { 180 | CreatedOnToolsVersion = 8.0; 181 | ProvisioningStyle = Automatic; 182 | }; 183 | }; 184 | }; 185 | buildConfigurationList = 4C2AD77F1D605B2D003AF2DD /* Build configuration list for PBXProject "SwiftUtils" */; 186 | compatibilityVersion = "Xcode 3.1"; 187 | developmentRegion = en; 188 | hasScannedForEncodings = 0; 189 | knownRegions = ( 190 | en, 191 | Base, 192 | ); 193 | mainGroup = 4C2AD77B1D605B2D003AF2DD; 194 | productRefGroup = 4C2AD7861D605B2D003AF2DD /* Products */; 195 | projectDirPath = ""; 196 | projectRoot = ""; 197 | targets = ( 198 | 4C2AD7841D605B2D003AF2DD /* SwiftUtils */, 199 | 4C2AD78D1D605B2D003AF2DD /* SwiftUtilsTests */, 200 | ); 201 | }; 202 | /* End PBXProject section */ 203 | 204 | /* Begin PBXResourcesBuildPhase section */ 205 | 4C2AD7831D605B2D003AF2DD /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 4CC4BC2C1D620F32000EF79D /* LICENSE.txt in Resources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | 4C2AD78C1D605B2D003AF2DD /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXResourcesBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 4C2AD7801D605B2D003AF2DD /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 4C4AC9911D8E635F00F72BE3 /* README-RSAUtils.md in Sources */, 228 | 4CC5032D1D8BBBDD00967A15 /* RSAUtils.swift in Sources */, 229 | 4C1BAB781D6210A1003B699E /* RELEASE-NOTES.md in Sources */, 230 | 4CC4BC261D6158D0000EF79D /* DateTimeUtils.swift in Sources */, 231 | 4C2AD7A21D605C08003AF2DD /* SnowflakeIdGenerator.swift in Sources */, 232 | 4CC5032B1D8BB55300967A15 /* README-SnowflakeIdGenerator.md in Sources */, 233 | 4CC503291D8BB30300967A15 /* README-DateTimeUtils.md in Sources */, 234 | 4CC4BC2D1D620F32000EF79D /* README.md in Sources */, 235 | 4CC4BC281D615D5F000EF79D /* SyncUtils.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 4C2AD78A1D605B2D003AF2DD /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 4C2AD7941D605B2D003AF2DD /* SwiftUtilsTests.swift in Sources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXSourcesBuildPhase section */ 248 | 249 | /* Begin PBXTargetDependency section */ 250 | 4C2AD7911D605B2D003AF2DD /* PBXTargetDependency */ = { 251 | isa = PBXTargetDependency; 252 | target = 4C2AD7841D605B2D003AF2DD /* SwiftUtils */; 253 | targetProxy = 4C2AD7901D605B2D003AF2DD /* PBXContainerItemProxy */; 254 | }; 255 | /* End PBXTargetDependency section */ 256 | 257 | /* Begin XCBuildConfiguration section */ 258 | 4C2AD7971D605B2D003AF2DD /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 263 | CLANG_ANALYZER_NONNULL = YES; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_COMMA = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 290 | COPY_PHASE_STRIP = NO; 291 | CURRENT_PROJECT_VERSION = 1; 292 | DEBUG_INFORMATION_FORMAT = dwarf; 293 | ENABLE_STRICT_OBJC_MSGSEND = YES; 294 | ENABLE_TESTABILITY = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu99; 296 | GCC_DYNAMIC_NO_PIC = NO; 297 | GCC_NO_COMMON_BLOCKS = YES; 298 | GCC_OPTIMIZATION_LEVEL = 0; 299 | GCC_PREPROCESSOR_DEFINITIONS = ( 300 | "DEBUG=1", 301 | "$(inherited)", 302 | ); 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 310 | MTL_ENABLE_DEBUG_INFO = YES; 311 | ONLY_ACTIVE_ARCH = YES; 312 | SDKROOT = iphoneos; 313 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 314 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 315 | SWIFT_VERSION = 4.0; 316 | TARGETED_DEVICE_FAMILY = "1,2"; 317 | VERSIONING_SYSTEM = "apple-generic"; 318 | VERSION_INFO_PREFIX = ""; 319 | }; 320 | name = Debug; 321 | }; 322 | 4C2AD7981D605B2D003AF2DD /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ALWAYS_SEARCH_USER_PATHS = NO; 326 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 327 | CLANG_ANALYZER_NONNULL = YES; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 348 | CLANG_WARN_STRICT_PROTOTYPES = YES; 349 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 350 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 351 | CLANG_WARN_UNREACHABLE_CODE = YES; 352 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 354 | COPY_PHASE_STRIP = NO; 355 | CURRENT_PROJECT_VERSION = 1; 356 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 357 | ENABLE_NS_ASSERTIONS = NO; 358 | ENABLE_STRICT_OBJC_MSGSEND = YES; 359 | GCC_C_LANGUAGE_STANDARD = gnu99; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 368 | MTL_ENABLE_DEBUG_INFO = NO; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 372 | SWIFT_VERSION = 4.0; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | VALIDATE_PRODUCT = YES; 375 | VERSIONING_SYSTEM = "apple-generic"; 376 | VERSION_INFO_PREFIX = ""; 377 | }; 378 | name = Release; 379 | }; 380 | 4C2AD79A1D605B2D003AF2DD /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | CLANG_ENABLE_MODULES = YES; 384 | CODE_SIGN_IDENTITY = ""; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | CODE_SIGN_STYLE = Automatic; 387 | DEFINES_MODULE = YES; 388 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 389 | DYLIB_COMPATIBILITY_VERSION = 1; 390 | DYLIB_CURRENT_VERSION = 1; 391 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 392 | INFOPLIST_FILE = SwiftUtils/Info.plist; 393 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 394 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtils; 397 | PRODUCT_NAME = "$(TARGET_NAME)"; 398 | PROVISIONING_PROFILE_SPECIFIER = ""; 399 | SKIP_INSTALL = YES; 400 | SUPPORTS_MACCATALYST = NO; 401 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 402 | SWIFT_VERSION = 4.0; 403 | }; 404 | name = Debug; 405 | }; 406 | 4C2AD79B1D605B2D003AF2DD /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | CLANG_ENABLE_MODULES = YES; 410 | CODE_SIGN_IDENTITY = ""; 411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 412 | CODE_SIGN_STYLE = Automatic; 413 | DEFINES_MODULE = YES; 414 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 415 | DYLIB_COMPATIBILITY_VERSION = 1; 416 | DYLIB_CURRENT_VERSION = 1; 417 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 418 | INFOPLIST_FILE = SwiftUtils/Info.plist; 419 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 420 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtils; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | PROVISIONING_PROFILE_SPECIFIER = ""; 425 | SKIP_INSTALL = YES; 426 | SUPPORTS_MACCATALYST = NO; 427 | SWIFT_VERSION = 4.0; 428 | }; 429 | name = Release; 430 | }; 431 | 4C2AD79D1D605B2D003AF2DD /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 435 | INFOPLIST_FILE = SwiftUtilsTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtilsTests; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 4.0; 440 | }; 441 | name = Debug; 442 | }; 443 | 4C2AD79E1D605B2D003AF2DD /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 447 | INFOPLIST_FILE = SwiftUtilsTests/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtilsTests; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | SWIFT_VERSION = 4.0; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 4C2AD77F1D605B2D003AF2DD /* Build configuration list for PBXProject "SwiftUtils" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 4C2AD7971D605B2D003AF2DD /* Debug */, 462 | 4C2AD7981D605B2D003AF2DD /* Release */, 463 | ); 464 | defaultConfigurationIsVisible = 0; 465 | defaultConfigurationName = Debug; 466 | }; 467 | 4C2AD7991D605B2D003AF2DD /* Build configuration list for PBXNativeTarget "SwiftUtils" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 4C2AD79A1D605B2D003AF2DD /* Debug */, 471 | 4C2AD79B1D605B2D003AF2DD /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Debug; 475 | }; 476 | 4C2AD79C1D605B2D003AF2DD /* Build configuration list for PBXNativeTarget "SwiftUtilsTests" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 4C2AD79D1D605B2D003AF2DD /* Debug */, 480 | 4C2AD79E1D605B2D003AF2DD /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Debug; 484 | }; 485 | /* End XCConfigurationList section */ 486 | }; 487 | rootObject = 4C2AD77C1D605B2D003AF2DD /* Project object */; 488 | } 489 | -------------------------------------------------------------------------------- /SwiftUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUtils.xcodeproj/xcuserdata/btnguyen.xcuserdatad/xcschemes/SwiftUtils.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /SwiftUtils.xcodeproj/xcuserdata/btnguyen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftUtils.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4C2AD7841D605B2D003AF2DD 16 | 17 | primary 18 | 19 | 20 | 4C2AD78D1D605B2D003AF2DD 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftUtils.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SwiftUtils/DateTimeUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTimeUtils.swift 3 | // SwiftUtils 4 | // 5 | // Created by Thanh Nguyen on 8/15/16. 6 | // Copyright © 2016 Thanh Nguyen. All rights reserved. 7 | //---------------------------------------------------------------------- 8 | // Useful DateTime utilities. 9 | //---------------------------------------------------------------------- 10 | 11 | import Foundation 12 | 13 | public class DateTimeUtils { 14 | /** 15 | * Gets the current UNIX timestamp in milliseconds as a UInt64. 16 | * 17 | * - Returns: the current UNIX timestamp in milliseconds 18 | */ 19 | @available(iOS, introduced: 1.0) 20 | public static func currentUnixTimestampMillisec() -> UInt64 { 21 | return UInt64(NSDate().timeIntervalSince1970 * 1000) 22 | } 23 | 24 | /** 25 | * Gets the current UNIX timestamp in seconds as a UInt64. 26 | * 27 | * - Returns: the current UNIX timestamp in seconds 28 | */ 29 | @available(iOS, introduced: 1.0) 30 | public static func currentUnixTimestampSecond() -> UInt64 { 31 | return UInt64(NSDate().timeIntervalSince1970) 32 | } 33 | 34 | /** 35 | * Waits until clock moves to the next millisecond. 36 | * 37 | * - Parameter currentMillisec: the current timestamp in milliseconds 38 | * 39 | * - Returns: the "next" millisecond 40 | */ 41 | @available(iOS, introduced: 1.0) 42 | @discardableResult public static func waitTillNextMillisec(_ currentMillisec: UInt64) -> UInt64 { 43 | var nextMillisec = currentUnixTimestampMillisec() 44 | while ( nextMillisec <= currentMillisec ) { 45 | nextMillisec = currentUnixTimestampMillisec() 46 | } 47 | return nextMillisec 48 | } 49 | 50 | /** 51 | * Waits until clock moves to the next second. 52 | * 53 | * - Parameter currentMillisec: the current timestamp in seconds 54 | * 55 | * - Returns: the "next" second 56 | */ 57 | @available(iOS, introduced: 1.0) 58 | @discardableResult public static func waitTillNextSecond(_ currentSecond: UInt64) -> UInt64 { 59 | var nextSecond = currentUnixTimestampSecond() 60 | while ( nextSecond <= currentSecond ) { 61 | nextSecond = currentUnixTimestampSecond() 62 | } 63 | return nextSecond 64 | } 65 | 66 | @available(iOS, introduced: 1.1) 67 | static private let FULL_DATE_COMPONENTS: Set = [Calendar.Component.era, 68 | Calendar.Component.year, 69 | Calendar.Component.month, 70 | Calendar.Component.day, 71 | Calendar.Component.hour, 72 | Calendar.Component.minute, 73 | Calendar.Component.second, 74 | Calendar.Component.nanosecond, 75 | Calendar.Component.weekday, 76 | Calendar.Component.weekdayOrdinal, 77 | Calendar.Component.weekOfMonth, 78 | Calendar.Component.weekOfYear, 79 | Calendar.Component.quarter, 80 | Calendar.Component.yearForWeekOfYear, 81 | Calendar.Component.calendar, 82 | Calendar.Component.timeZone] 83 | 84 | /** 85 | * Resets to start of the second of (nanosecond is set to 0) a Date object. 86 | * 87 | * - Parameter date: the input Date object 88 | * 89 | * - Returns: start of the second of the input Date object 90 | */ 91 | @available(iOS, introduced: 1.1) 92 | public static func startOfSecond(_ date: Date) -> Date { 93 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 94 | comps.setValue(0, for: Calendar.Component.nanosecond) 95 | return Calendar.current.date(from: comps)! 96 | } 97 | 98 | /** 99 | * Adds n seconds to a Date object. 100 | * 101 | * - Parameter date: the input Date object 102 | * - Parameter numSeconds: the number of seconds to add 103 | * 104 | * - Returns: the result Date object 105 | */ 106 | @available(iOS, introduced: 1.3) 107 | public static func addSeconds(_ date: Date, numSeconds: Int) -> Date { 108 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 109 | comps.second! += numSeconds 110 | return Calendar.current.date(from: comps)! 111 | } 112 | 113 | /** 114 | * Resets to start of the minute (second and nanosecond are set to 0s) of a Date object. 115 | * 116 | * - Parameter date: the input Date object 117 | * 118 | * - Returns: start of the minute of the input Date object 119 | */ 120 | @available(iOS, introduced: 1.1) 121 | public static func startOfMinute(_ date: Date) -> Date { 122 | let d = startOfSecond(date) 123 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: d) 124 | comps.setValue(0, for: Calendar.Component.second) 125 | return Calendar.current.date(from: comps)! 126 | } 127 | 128 | /** 129 | * Adds n minutes to a Date object. 130 | * 131 | * - Parameter date: the input Date object 132 | * - Parameter numMinutes: the number of minutes to add 133 | * 134 | * - Returns: the result Date object 135 | */ 136 | @available(iOS, introduced: 1.3) 137 | public static func addMinutes(_ date: Date, numMinutes: Int) -> Date { 138 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 139 | comps.minute! += numMinutes 140 | return Calendar.current.date(from: comps)! 141 | } 142 | 143 | /** 144 | * Resets to start of the hour (minute, second and nanosecond are set to 0s) of a Date object. 145 | * 146 | * - Parameter date: the input Date object 147 | * 148 | * - Returns: start of the hour of the input Date object 149 | */ 150 | @available(iOS, introduced: 1.1) 151 | public static func startOfHour(_ date: Date) -> Date { 152 | let d = startOfMinute(date) 153 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: d) 154 | comps.setValue(0, for: Calendar.Component.minute) 155 | return Calendar.current.date(from: comps)! 156 | } 157 | 158 | /** 159 | * Adds n hours to a Date object. 160 | * 161 | * - Parameter date: the input Date object 162 | * - Parameter numHours: the number of hours to add 163 | * 164 | * - Returns: the result Date object 165 | */ 166 | @available(iOS, introduced: 1.3) 167 | public static func addHours(_ date: Date, numHours: Int) -> Date { 168 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 169 | comps.hour! += numHours 170 | return Calendar.current.date(from: comps)! 171 | } 172 | 173 | /** 174 | * Resets to start of the day (hour, minute, second and nanosecond are set to 0s) of a Date object. 175 | * 176 | * - Parameter date: the input Date object 177 | * 178 | * - Returns: start of the day of the input Date object 179 | */ 180 | @available(iOS, introduced: 1.1) 181 | public static func startOfDay(_ date: Date) -> Date { 182 | let d = startOfHour(date) 183 | var comps = NSCalendar.current.dateComponents(FULL_DATE_COMPONENTS, from: d) 184 | comps.setValue(0, for: Calendar.Component.hour) 185 | return NSCalendar.current.date(from: comps)! 186 | } 187 | 188 | /** 189 | * Alias of startOfDay() 190 | */ 191 | @available(iOS, introduced: 1.1) 192 | public static func startOfDate(_ date: Date) -> Date { 193 | return startOfDay(date) 194 | } 195 | 196 | /** 197 | * Adds n days to a Date object. 198 | * 199 | * - Parameter date: the input Date object 200 | * - Parameter numDays: the number of days to add 201 | * 202 | * - Returns: the result Date object 203 | */ 204 | @available(iOS, introduced: 1.3) 205 | public static func addDays(_ date: Date, numDays: Int) -> Date { 206 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 207 | comps.day! += numDays 208 | return Calendar.current.date(from: comps)! 209 | } 210 | 211 | /** 212 | * Resets to start of the month (day is set to 1; hour, minute, second and nanosecond are set to 0s) of a Date object. 213 | * 214 | * - Parameter date: the input Date object 215 | * 216 | * - Returns: start of the month of the input Date object 217 | */ 218 | @available(iOS, introduced: 1.1) 219 | public static func startOfMonth(_ date: Date) -> Date { 220 | let d = startOfDay(date) 221 | var comps = NSCalendar.current.dateComponents(FULL_DATE_COMPONENTS, from: d) 222 | comps.setValue(1, for: Calendar.Component.day) 223 | return NSCalendar.current.date(from: comps)! 224 | } 225 | 226 | /** 227 | * Adds n months to a Date object. 228 | * 229 | * - Parameter date: the input Date object 230 | * - Parameter numMonths: the number of months to add 231 | * 232 | * - Returns: the result Date object 233 | */ 234 | @available(iOS, introduced: 1.3) 235 | public static func addMonths(_ date: Date, numMonths: Int) -> Date { 236 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 237 | comps.month! += numMonths 238 | return Calendar.current.date(from: comps)! 239 | } 240 | 241 | /** 242 | * Resets to start of the year (month and day is set to 1s; hour, minute, second and nanosecond are set to 0s) of a Date object. 243 | * 244 | * - Parameter date: the input Date object 245 | * 246 | * - Returns: start of the year of the input Date object 247 | */ 248 | @available(iOS, introduced: 1.1) 249 | public static func startOfYear(_ date: Date) -> Date { 250 | let d = startOfMonth(date) 251 | var comps = NSCalendar.current.dateComponents(FULL_DATE_COMPONENTS, from: d) 252 | comps.setValue(1, for: Calendar.Component.month) 253 | return NSCalendar.current.date(from: comps)! 254 | } 255 | 256 | /** 257 | * Adds n years to a Date object. 258 | * 259 | * - Parameter date: the input Date object 260 | * - Parameter numYears: the number of years to add 261 | * 262 | * - Returns: the result Date object 263 | */ 264 | @available(iOS, introduced: 1.3) 265 | public static func addYears(_ date: Date, numYears: Int) -> Date { 266 | var comps = Calendar.current.dateComponents(FULL_DATE_COMPONENTS, from: date) 267 | //comps.year is wrong! 268 | comps.yearForWeekOfYear! += numYears 269 | return Calendar.current.date(from: comps)! 270 | } 271 | 272 | /** 273 | * Resets to start of the week (day is set to 'first week day'; hour, minute, second and nanosecond are set to 0s) of a Date object. 274 | * 275 | * - Parameter date: the input Date object 276 | * 277 | * - Returns: start of the week of the input Date object 278 | */ 279 | @available(iOS, introduced: 1.1) 280 | public static func startOfWeek(_ date: Date) -> Date { 281 | let CAL = NSCalendar.current 282 | var result = startOfDate(date) 283 | var weekday = CAL.component(Calendar.Component.weekday, from: result) 284 | while ( weekday != CAL.firstWeekday ) { 285 | result = CAL.date(byAdding: Calendar.Component.weekday, value: -1, to: result)! 286 | weekday = CAL.component(Calendar.Component.weekday, from: result) 287 | } 288 | return result 289 | } 290 | 291 | /** 292 | * Adds n weeks to a Date object. 293 | * 294 | * - Parameter date: the input Date object 295 | * - Parameter numWeeks: the number of weeks to add (1 week = 7 days) 296 | * 297 | * - Returns: the result Date object 298 | */ 299 | @available(iOS, introduced: 1.3) 300 | public static func addWeeks(_ date: Date, numWeeks: Int) -> Date { 301 | return addDays(date, numDays: numWeeks*7) 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /SwiftUtils/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.2.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftUtils/RSAUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RsaUtils.swift 3 | // SwiftUtils 4 | // 5 | // Created by Thanh Nguyen on 9/16/16. 6 | // Copyright © 2016 Thanh Nguyen. All rights reserved. 7 | //---------------------------------------------------------------------- 8 | // RSA utilities. 9 | // Credits: 10 | // - https://github.com/ideawu/Objective-C-RSA 11 | // - http://netsplit.com/swift-storing-key-pairs-in-the-keyring 12 | // - http://netsplit.com/swift-generating-keys-and-encrypting-and-decrypting-text 13 | // - http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l1036 14 | //---------------------------------------------------------------------- 15 | 16 | import Foundation 17 | import Security 18 | 19 | public class RSAUtils { 20 | 21 | private static let PADDING_FOR_DECRYPT = SecPadding() 22 | 23 | @available(iOS, introduced: 1.2.0) 24 | public class RSAUtilsError: NSError { 25 | init(_ message: String) { 26 | super.init(domain: "com.github.btnguyen2k.SwiftUtils.RSAUtils", code: 500, userInfo: [ 27 | NSLocalizedDescriptionKey: message 28 | ]) 29 | } 30 | 31 | @available(*, unavailable) 32 | required public init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | } 36 | 37 | // Base64 encode a block of data 38 | @available(iOS, introduced: 1.2.0) 39 | private static func base64Encode(_ data: Data) -> String { 40 | return data.base64EncodedString(options: []) 41 | } 42 | 43 | // Base64 decode a base64-ed string 44 | @available(iOS, introduced: 1.2.0) 45 | private static func base64Decode(_ strBase64: String) -> Data { 46 | let data = Data(base64Encoded: strBase64, options: []) 47 | return data! 48 | } 49 | 50 | /** 51 | * Deletes an existing RSA key specified by a tag from keychain. 52 | * 53 | * - Parameter tagName: tag name to query for RSA key from keychain 54 | */ 55 | @available(iOS, introduced: 1.2.0) 56 | public static func deleteRSAKeyFromKeychain(_ tagName: String) { 57 | let queryFilter: [String: AnyObject] = [ 58 | String(kSecClass) : kSecClassKey, 59 | String(kSecAttrKeyType) : kSecAttrKeyTypeRSA, 60 | String(kSecAttrApplicationTag): tagName as AnyObject 61 | ] 62 | SecItemDelete(queryFilter as CFDictionary) 63 | } 64 | 65 | /** 66 | * Gets an existing RSA key specified by a tag from keychain. 67 | * 68 | * - Parameter tagName: tag name to query for RSA key from keychain 69 | * 70 | * - Returns: SecKey reference to the RSA key 71 | */ 72 | @available(iOS, introduced: 1.2.0) 73 | public static func getRSAKeyFromKeychain(_ tagName: String) -> SecKey? { 74 | let queryFilter: [String: AnyObject] = [ 75 | String(kSecClass) : kSecClassKey, 76 | String(kSecAttrKeyType) : kSecAttrKeyTypeRSA, 77 | String(kSecAttrApplicationTag): tagName as AnyObject, 78 | //String(kSecAttrAccessible) : kSecAttrAccessibleWhenUnlocked, 79 | String(kSecReturnRef) : true as AnyObject 80 | ] 81 | 82 | var keyPtr: AnyObject? 83 | let result = SecItemCopyMatching(queryFilter as CFDictionary, &keyPtr) 84 | if ( result != noErr || keyPtr == nil ) { 85 | return nil 86 | } 87 | return keyPtr as! SecKey? 88 | } 89 | 90 | /** 91 | * Adds a RSA private key (PKCS#1 or PKCS#8) to keychain and returns its SecKey reference. 92 | * 93 | * On disk, a PEM RSA PKCS#8 private key file starts with string "-----BEGIN PRIVATE KEY-----", and ends with string "-----END PRIVATE KEY-----"; PKCS#1 private key file starts with string "-----BEGIN RSA PRIVATE KEY-----", and ends with string "-----END RSA PRIVATE KEY-----". 94 | * 95 | * - Parameter privkeyBase64: RSA private key (PKCS#1 or PKCS#8) in base64 96 | * - Parameter tagName: tag name to store RSA key to keychain 97 | * 98 | * - Throws: `RSAUtilsError` if the input key is not a valid PKCS#8 private key 99 | * 100 | * - Returns: SecKey reference to the RSA private key. 101 | */ 102 | @available(iOS, introduced: 1.2.0) 103 | @discardableResult public static func addRSAPrivateKey(_ privkeyBase64: String, tagName: String) throws -> SecKey? { 104 | let fullRange = NSRange(location: 0, length: privkeyBase64.lengthOfBytes(using: .utf8)) 105 | let regExp = try! NSRegularExpression(pattern: "(-----BEGIN.*?-----)|(-----END.*?-----)|\\s+", options: []) 106 | let myPrivkeyBase64 = regExp.stringByReplacingMatches(in: privkeyBase64, options: [], range: fullRange, withTemplate: "") 107 | return try addRSAPrivateKey(base64Decode(myPrivkeyBase64), tagName: tagName) 108 | } 109 | 110 | /** 111 | * Adds a RSA private key to keychain and returns its SecKey reference. 112 | * 113 | * - Parameter privkey: RSA private key (PKCS#1 or PKCS#8) 114 | * - Parameter tagName: tag name to store RSA key to keychain 115 | * 116 | * - Throws: `RSAUtilsError` if the input key is not a valid PKCS#8 private key 117 | * 118 | * - Returns: SecKey reference to the RSA private key. 119 | */ 120 | @available(iOS, introduced: 1.2.0) 121 | @discardableResult private static func addRSAPrivateKey(_ privkey: Data, tagName: String) throws -> SecKey? { 122 | // Delete any old lingering key with the same tag 123 | deleteRSAKeyFromKeychain(tagName) 124 | 125 | let privkeyData = try stripPrivateKeyHeader(privkey) 126 | if ( privkeyData == nil ) { 127 | return nil 128 | } 129 | 130 | // Add persistent version of the key to system keychain 131 | let queryFilter: [String : Any] = [ 132 | (kSecClass as String) : kSecClassKey, 133 | (kSecAttrKeyType as String) : kSecAttrKeyTypeRSA, 134 | (kSecAttrApplicationTag as String) : tagName, 135 | //(kSecAttrAccessible as String) : kSecAttrAccessibleWhenUnlocked, 136 | (kSecValueData as String) : privkeyData!, 137 | (kSecAttrKeyClass as String) : kSecAttrKeyClassPrivate, 138 | (kSecReturnPersistentRef as String): true 139 | ] as [String : Any] 140 | let result = SecItemAdd(queryFilter as CFDictionary, nil) 141 | if ((result != noErr) && (result != errSecDuplicateItem)) { 142 | NSLog("Cannot add key to keychain, status \(result).") 143 | return nil 144 | } 145 | 146 | return getRSAKeyFromKeychain(tagName) 147 | } 148 | 149 | /** 150 | * Verifies that the supplied key is in fact a PEM RSA private key, and strips its header. 151 | * 152 | * If the supplied key is PKCS#8, its ASN.1 header should be stripped. Otherwise (PKCS#1), the whole key data is left intact. 153 | * 154 | * On disk, a PEM RSA PKCS#8 private key file starts with string "-----BEGIN PRIVATE KEY-----", and ends with string "-----END PRIVATE KEY-----"; PKCS#1 private key file starts with string "-----BEGIN RSA PRIVATE KEY-----", and ends with string "-----END RSA PRIVATE KEY-----". 155 | * 156 | * - Parameter privkey: RSA private key (PKCS#1 or PKCS#8) 157 | * 158 | * - Throws: `RSAUtilsError` if the input key is not a valid RSA PKCS#8 private key 159 | * 160 | * - Returns: the RSA private key with header stripped. 161 | */ 162 | @available(iOS, introduced: 1.2.0) 163 | private static func stripPrivateKeyHeader(_ privkey: Data) throws -> Data? { 164 | if ( privkey.count == 0 ) { 165 | return nil 166 | } 167 | 168 | var keyAsArray = [UInt8](repeating: 0, count: privkey.count / MemoryLayout.size) 169 | (privkey as NSData).getBytes(&keyAsArray, length: privkey.count) 170 | 171 | //PKCS#8: magic byte at offset 22, check if it's actually ASN.1 172 | var idx = 22 173 | if ( keyAsArray[idx] != 0x04 ) { 174 | return privkey 175 | } 176 | idx += 1 177 | 178 | //now we need to find out how long the key is, so we can extract the correct hunk 179 | //of bytes from the buffer. 180 | var len = Int(keyAsArray[idx]) 181 | idx += 1 182 | let det = len & 0x80 //check if the high bit set 183 | if (det == 0) { 184 | //no? then the length of the key is a number that fits in one byte, (< 128) 185 | len = len & 0x7f 186 | } else { 187 | //otherwise, the length of the key is a number that doesn't fit in one byte (> 127) 188 | var byteCount = Int(len & 0x7f) 189 | if (byteCount + idx > privkey.count) { 190 | return nil 191 | } 192 | //so we need to snip off byteCount bytes from the front, and reverse their order 193 | var accum: UInt = 0 194 | var idx2 = idx 195 | idx += byteCount 196 | while (byteCount > 0) { 197 | //after each byte, we shove it over, accumulating the value into accum 198 | accum = (accum << 8) + UInt(keyAsArray[idx2]) 199 | idx2 += 1 200 | byteCount -= 1 201 | } 202 | // now we have read all the bytes of the key length, and converted them to a number, 203 | // which is the number of bytes in the actual key. we use this below to extract the 204 | // key bytes and operate on them 205 | len = Int(accum) 206 | } 207 | return privkey.subdata(in: idx.. SecKey? { 223 | let fullRange = NSRange(location: 0, length: pubkeyBase64.lengthOfBytes(using: .utf8)) 224 | let regExp = try! NSRegularExpression(pattern: "(-----BEGIN.*?-----)|(-----END.*?-----)|\\s+", options: []) 225 | let myPubkeyBase64 = regExp.stringByReplacingMatches(in: pubkeyBase64, options: [], range: fullRange, withTemplate: "") 226 | return try addRSAPublicKey(base64Decode(myPubkeyBase64), tagName: tagName) 227 | } 228 | 229 | /** 230 | * Adds a RSA pubic key to keychain and returns its SecKey reference. 231 | * 232 | * - Parameter pubkey: X509 public key 233 | * - Parameter tagName: tag name to store RSA key to keychain 234 | * 235 | * - Throws: `RSAUtilsError` if the input key is not a valid X509 public key 236 | * 237 | * - Returns: SecKey reference to the RSA public key. 238 | */ 239 | @available(iOS, introduced: 1.2.0) 240 | private static func addRSAPublicKey(_ pubkey: Data, tagName: String) throws -> SecKey? { 241 | // Delete any old lingering key with the same tag 242 | deleteRSAKeyFromKeychain(tagName) 243 | 244 | let pubkeyData = try stripPublicKeyHeader(pubkey) 245 | if ( pubkeyData == nil ) { 246 | return nil 247 | } 248 | 249 | // Add persistent version of the key to system keychain 250 | //var prt1: Unmanaged? 251 | let queryFilter: [String : Any] = [ 252 | (kSecClass as String) : kSecClassKey, 253 | (kSecAttrKeyType as String) : kSecAttrKeyTypeRSA, 254 | (kSecAttrApplicationTag as String) : tagName, 255 | (kSecValueData as String) : pubkeyData!, 256 | (kSecAttrKeyClass as String) : kSecAttrKeyClassPublic, 257 | (kSecReturnPersistentRef as String): true 258 | ] as [String : Any] 259 | let result = SecItemAdd(queryFilter as CFDictionary, nil) 260 | if ((result != noErr) && (result != errSecDuplicateItem)) { 261 | return nil 262 | } 263 | 264 | return getRSAKeyFromKeychain(tagName) 265 | } 266 | 267 | /** 268 | * Verifies that the supplied key is in fact a X509 public key, and strips its header. 269 | * 270 | * On disk, a X509 public key file starts with string "-----BEGIN PUBLIC KEY-----", and ends with string "-----END PUBLIC KEY-----" 271 | * 272 | * - Parameter pubkey: X509 public key 273 | * 274 | * - Throws: `RSAUtilsError` if the input key is not a valid X509 public key 275 | * 276 | * - Returns: the RSA public key with header stripped. 277 | */ 278 | @available(iOS, introduced: 1.2.0) 279 | private static func stripPublicKeyHeader(_ pubkey: Data) throws -> Data? { 280 | if ( pubkey.count == 0 ) { 281 | return nil 282 | } 283 | 284 | var keyAsArray = [UInt8](repeating: 0, count: pubkey.count / MemoryLayout.size) 285 | (pubkey as NSData).getBytes(&keyAsArray, length: pubkey.count) 286 | 287 | var idx = 0 288 | if (keyAsArray[idx] != 0x30) { 289 | throw RSAUtilsError("Provided key doesn't have a valid ASN.1 structure (first byte should be 0x30).") 290 | //return nil 291 | } 292 | idx += 1 293 | 294 | if (keyAsArray[idx] > 0x80) { 295 | idx += Int(keyAsArray[idx]) - 0x80 + 1 296 | } else { 297 | idx += 1 298 | } 299 | 300 | /* 301 | * If current byte is 0x02, it means the key doesn't have a X509 header (it contains only modulo & public exponent). In this case, we can just return the provided DER data as is 302 | */ 303 | if (Int(keyAsArray[idx]) == 0x02) { 304 | return pubkey 305 | } 306 | 307 | let seqiod = [UInt8](arrayLiteral: 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00) 308 | for i in idx.. 0x80) { 323 | idx += Int(keyAsArray[idx]) - 0x80 + 1; 324 | } else { 325 | idx += 1 326 | } 327 | 328 | if (keyAsArray[idx] != 0x00) { 329 | throw RSAUtilsError("Invalid byte at index \(idx) (\(keyAsArray[idx])) for public key header.") 330 | //return nil 331 | } 332 | idx += 1 333 | return pubkey.subdata(in: idx.. Data? { 348 | let blockSize = SecKeyGetBlockSize(rsaKeyRef) 349 | let dataSize = data.count / MemoryLayout.size 350 | let maxChunkSize = padding==SecPadding.OAEP ? (blockSize - 42) : (blockSize - 11) 351 | 352 | var dataAsArray = [UInt8](repeating: 0, count: dataSize) 353 | (data as NSData).getBytes(&dataAsArray, length: dataSize) 354 | 355 | var encryptedData = [UInt8](repeating: 0, count: 0) 356 | var idx = 0 357 | while (idx < dataAsArray.count ) { 358 | var idxEnd = idx + maxChunkSize 359 | if ( idxEnd > dataAsArray.count ) { 360 | idxEnd = dataAsArray.count 361 | } 362 | var chunkData = [UInt8](repeating: 0, count: maxChunkSize) 363 | for i in idx..(encryptedData), count: encryptedData.count) 381 | } 382 | 383 | /** 384 | * Decrypts data with a RSA key. 385 | * 386 | * - Parameter encryptedData: the data to be decrypted 387 | * - Parameter rsaKeyRef: the RSA key 388 | * - Parameter padding: padding used for decryption 389 | * 390 | * - Returns: the decrypted data 391 | */ 392 | @available(iOS, introduced: 1.2.0) 393 | public static func decryptWithRSAKey(_ encryptedData: Data, rsaKeyRef: SecKey, padding: SecPadding) -> Data? { 394 | let blockSize = SecKeyGetBlockSize(rsaKeyRef) 395 | let dataSize = encryptedData.count / MemoryLayout.size 396 | 397 | var encryptedDataAsArray = [UInt8](repeating: 0, count: dataSize) 398 | (encryptedData as NSData).getBytes(&encryptedDataAsArray, length: dataSize) 399 | 400 | var decryptedData = [UInt8](repeating: 0, count: 0) 401 | var idx = 0 402 | while (idx < encryptedDataAsArray.count ) { 403 | var idxEnd = idx + blockSize 404 | if ( idxEnd > encryptedDataAsArray.count ) { 405 | idxEnd = encryptedDataAsArray.count 406 | } 407 | var chunkData = [UInt8](repeating: 0, count: blockSize) 408 | for i in idx..(decryptedData), count: decryptedData.count) 426 | } 427 | 428 | @available(iOS, introduced: 1.2.0) 429 | private static func removePadding(_ data: [UInt8]) -> [UInt8] { 430 | var idxFirstZero = -1 431 | var idxNextZero = data.count 432 | for i in 0.. Data? { 465 | let keyRef = getRSAKeyFromKeychain(tagName) 466 | if ( keyRef == nil ) { 467 | return nil 468 | } 469 | 470 | return encryptWithRSAKey(data, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 471 | } 472 | 473 | /** 474 | * Encrypts a string using a RSA key from keychain specified by `tagName`. 475 | * 476 | * Note: The RSA key must be added to keychain by calling `addRSAPublicKey()` or `addRSAPrivateKey()` period to calling this function. 477 | * 478 | * - Parameter str: string to be encrypted 479 | * - Parameter tagName: tag name to query for RSA key from keychain. 480 | * 481 | * - Returns: the data in encrypted form 482 | */ 483 | @available(iOS, introduced: 1.2.0) 484 | public static func encryptWithRSAKey(str: String, tagName: String) -> Data? { 485 | let keyRef = getRSAKeyFromKeychain(tagName) 486 | if ( keyRef == nil ) { 487 | return nil 488 | } 489 | 490 | return encryptWithRSAKey(str.data(using: .utf8)!, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 491 | } 492 | 493 | /** 494 | * Decrypts an encrypted data using a RSA key from keychain specified by `tagName`. 495 | * 496 | * Note: The RSA key must be added to keychain by calling `addRSAPublicKey()` or `addRSAPrivateKey()` period to calling this function. 497 | * 498 | * - Parameter encryptedData: data to be decrypted 499 | * - Parameter tagName: tag name to query for RSA key from keychain. 500 | * 501 | * - Returns: the decrypted data 502 | */ 503 | @available(iOS, introduced: 1.2.0) 504 | public static func decryptWithRSAKey(encryptedData: Data, tagName: String) -> Data? { 505 | let keyRef = getRSAKeyFromKeychain(tagName) 506 | if ( keyRef == nil ) { 507 | return nil 508 | } 509 | 510 | return decryptWithRSAKey(encryptedData, rsaKeyRef: keyRef!, padding: PADDING_FOR_DECRYPT) 511 | } 512 | 513 | /*----------------------------------------------------------------------*/ 514 | 515 | /** 516 | * Encrypts data using RSA public key. 517 | * 518 | * Note: the public key will be stored in keychain with tag as `pubkeyBase64.hashValue`. 519 | * 520 | * - Parameter data: data to be encrypted 521 | * - Parameter pubkeyBase64: X509 public key in base64 (data between "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----") 522 | * 523 | * - Throws: `RSAUtilsError` if the supplied key is not a valid X509 public key 524 | * 525 | * - Returns: the data in encrypted form 526 | */ 527 | @available(iOS, introduced: 1.2.0) 528 | public static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String) throws -> Data? { 529 | let tagName = "PUBIC-" + String(pubkeyBase64.hashValue) 530 | var keyRef = getRSAKeyFromKeychain(tagName) 531 | if ( keyRef == nil ) { 532 | keyRef = try addRSAPublicKey(pubkeyBase64, tagName: tagName) 533 | } 534 | if ( keyRef == nil ) { 535 | return nil 536 | } 537 | 538 | return encryptWithRSAKey(data, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 539 | } 540 | 541 | /** 542 | * Encrypts a string using RSA public key. 543 | * 544 | * Note: the public key will be stored in keychain with tag as `pubkeyBase64.hashValue`. 545 | * 546 | * - Parameter str: string to be encrypted 547 | * - Parameter pubkeyBase64: X509 public key in base64 (data between "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----") 548 | * 549 | * - Throws: `RSAUtilsError` if the supplied key is not a valid X509 public key 550 | * 551 | * - Returns: the data in encrypted form 552 | */ 553 | @available(iOS, introduced: 1.2.0) 554 | public static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String) throws -> Data? { 555 | let tagName = "PUBIC-" + String(pubkeyBase64.hashValue) 556 | var keyRef = getRSAKeyFromKeychain(tagName) 557 | if ( keyRef == nil ) { 558 | keyRef = try addRSAPublicKey(pubkeyBase64, tagName: tagName) 559 | } 560 | if ( keyRef == nil ) { 561 | return nil 562 | } 563 | 564 | return encryptWithRSAKey(str.data(using: .utf8)!, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 565 | } 566 | 567 | /** 568 | * Encrypts data using RSA public key. 569 | * 570 | * Note: the public key will be stored in keychain specified by tagName. 571 | * 572 | * - Parameter data: data to be encrypted 573 | * - Parameter pubkeyBase64: X509 public key in base64 (data between "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----") 574 | * - Parameter tagName: tag name to store RSA key to keychain 575 | * 576 | * - Throws: `RSAUtilsError` if the supplied key is not a valid X509 public key 577 | * 578 | * - Returns: the data in encrypted form 579 | */ 580 | @available(iOS, introduced: 1.2.0) 581 | public static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String, tagName: String) throws -> Data? { 582 | var keyRef = getRSAKeyFromKeychain(tagName) 583 | if ( keyRef == nil ) { 584 | keyRef = try addRSAPublicKey(pubkeyBase64, tagName: tagName) 585 | } 586 | if ( keyRef == nil ) { 587 | return nil 588 | } 589 | 590 | return encryptWithRSAKey(data, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 591 | } 592 | 593 | /** 594 | * Encrypts a string using RSA public key. 595 | * 596 | * Note: the public key will be stored in keychain specified by tagName. 597 | * 598 | * - Parameter str: string to be encrypted 599 | * - Parameter pubkeyBase64: X509 public key in base64 (data between "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----") 600 | * - Parameter tagName: tag name to store RSA key to keychain 601 | * 602 | * - Throws: `RSAUtilsError` if the supplied key is not a valid X509 public key 603 | * 604 | * - Returns: the data in encrypted form 605 | */ 606 | @available(iOS, introduced: 1.2.0) 607 | public static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String, tagName: String) throws -> Data? { 608 | var keyRef = getRSAKeyFromKeychain(tagName) 609 | if ( keyRef == nil ) { 610 | keyRef = try addRSAPublicKey(pubkeyBase64, tagName: tagName) 611 | } 612 | if ( keyRef == nil ) { 613 | return nil 614 | } 615 | 616 | return encryptWithRSAKey(str.data(using: .utf8)!, rsaKeyRef: keyRef!, padding: SecPadding.PKCS1) 617 | } 618 | 619 | /*----------------------------------------------------------------------*/ 620 | 621 | /** 622 | * Decrypts an encrypted data using a RSA private key. 623 | * 624 | * Note: the private key will be stored in keychain with tag as `privkeyBase64.hashValue`. 625 | * 626 | * - Parameter encryptedData: data to be decrypted 627 | * - Parameter privkeyBase64: RSA PKCS#8 private key in base64 (data between "-----BEGIN PRIVATE KEY-----" and "-----END PRIVATE KEY-----") 628 | * 629 | * - Throws: `RSAUtilsError` if the supplied key is not a valid RSA PKCS#8 private key 630 | * 631 | * - Returns: the decrypted data 632 | */ 633 | @available(iOS, introduced: 1.2.0) 634 | public static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String) throws -> Data? { 635 | let tagName = "PRIVATE-" + String(privkeyBase64.hashValue) 636 | var keyRef = getRSAKeyFromKeychain(tagName) 637 | if ( keyRef == nil ) { 638 | keyRef = try addRSAPrivateKey(privkeyBase64, tagName: tagName) 639 | } 640 | if ( keyRef == nil ) { 641 | return nil 642 | } 643 | 644 | return decryptWithRSAKey(encryptedData, rsaKeyRef: keyRef!, padding: PADDING_FOR_DECRYPT) 645 | } 646 | 647 | /** 648 | * Decrypts an encrypted data using a RSA private key. 649 | * 650 | * Note: the private key will be stored in keychain specified by tagName. 651 | * 652 | * - Parameter encryptedData: data to be decrypted 653 | * - Parameter privkeyBase64: RSA PKCS#8 private key in base64 (data between "-----BEGIN PRIVATE KEY-----" and "-----END PRIVATE KEY-----") 654 | * - Parameter tagName: tag name to store RSA key to keychain 655 | * 656 | * - Throws: `RSAUtilsError` if the supplied key is not a valid RSA PKCS#8 private key 657 | * 658 | * - Returns: the data in encrypted form 659 | */ 660 | @available(iOS, introduced: 1.2.0) 661 | public static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String, tagName: String) throws -> Data? { 662 | var keyRef = getRSAKeyFromKeychain(tagName) 663 | if ( keyRef == nil ) { 664 | keyRef = try addRSAPrivateKey(privkeyBase64, tagName: tagName) 665 | } 666 | if ( keyRef == nil ) { 667 | return nil 668 | } 669 | 670 | return decryptWithRSAKey(encryptedData, rsaKeyRef: keyRef!, padding: SecPadding.OAEP) 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /SwiftUtils/SnowflakeIdGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnowflakeIdGenerator.swift 3 | // SwiftUtils 4 | // 5 | // Created by Thanh Nguyen on 8/14/16. 6 | // Copyright © 2016 Thanh Nguyen . All rights reserved. 7 | //---------------------------------------------------------------------- 8 | // Swift implementation of Twitter's Snowflake. 9 | // * 128-bit ID: 10 | // <64-bits: timestamp><48-bits: node id><16-bits: sequence number> 11 | // (where timestamp is UNIX timestamp in milliseconds) 12 | // * 64-bit ID: 13 | // <41-bits: timestamp><21-bits: node id><2-bits: sequence number> 14 | // (where timestamp is UNIX timestamp in milliseconds, minus the epoch) 15 | // * 64-bit-nil ID: 16 | // <41-bits: timestamp><23-bits: node id> 17 | // (where timestamp is UNIX timestamp in milliseconds, minus the epoch) 18 | //---------------------------------------------------------------------- 19 | 20 | import Foundation 21 | import UIKit 22 | 23 | public class SnowflakeIdGenerator { 24 | private static let MASK_TIMESTAMP_64 :UInt64 = (1 << 41) - 1 // 41 bits 25 | private static let MASK_NODE_ID_64 :UInt64 = (1 << 21) - 1 // 21 bits 26 | private static let MASK_SEQUENCE_64 :UInt64 = (1 << 2) - 1 // 2 bits 27 | private static let MAX_SEQUENCE_64 :UInt64 = (1 << 2) - 1 // 2 bits 28 | private static let SHIFT_TIMESTAMP_64 :UInt64 = 21+2 29 | private static let SHIFT_NODE_ID_64 :UInt64 = 2 30 | 31 | private static let MASK_TIMESTAMP_64NIL :UInt64 = (1 << 41) - 1 // 41 bits 32 | private static let MASK_NODE_ID_64NIL :UInt64 = (1 << 23) - 1 // 23 bits 33 | private static let SHIFT_TIMESTAMP_64NIL :UInt64 = 23 34 | 35 | private static let TIMESTAMP_EPOCH :UInt64 = 1456790400000 // 1-Mar-2016 GMT 36 | 37 | private static let MASK_NODE_ID_128 :UInt64 = 0xFFFFFFFFFFFF // 48 bits 38 | private static let MASK_SEQUENCE_128 :UInt64 = 0xFFFF // 16 bits 39 | private static let MAX_SEQUENCE_128 :UInt64 = 0xFFFF // 16 bits 40 | private static let SHIFT_TIMESTAMP_128 :UInt64 = 64 41 | private static let SHIFT_NODE_ID_128 :UInt64 = 16 42 | 43 | private var nodeId :UInt64 = 0 44 | private var template64 :UInt64 = 0 45 | private var template64Nil :UInt64 = 0 46 | private var sequenceMillisec :UInt64 = 0 47 | private var lastTimestampMillisec :UInt64 = 0 48 | 49 | init(_ nodeId: UInt64 = 0) { 50 | if ( nodeId != 0 ) { 51 | self.nodeId = nodeId 52 | } else { 53 | self.nodeId = UInt64(UIDevice.current.identifierForVendor!.hashValue) 54 | } 55 | template64 = (self.nodeId & SnowflakeIdGenerator.MASK_NODE_ID_64) << SnowflakeIdGenerator.SHIFT_NODE_ID_64 56 | template64Nil = (self.nodeId & SnowflakeIdGenerator.MASK_NODE_ID_64NIL) 57 | } 58 | 59 | public func getNodeId() -> UInt64 { 60 | return nodeId 61 | } 62 | 63 | public func getSequenceMillisec() -> UInt64 { 64 | return sequenceMillisec 65 | } 66 | 67 | public func getLastTimestampMillisec() -> UInt64 { 68 | return lastTimestampMillisec 69 | } 70 | 71 | /*----------------------------------------------------------------------*/ 72 | 73 | /** 74 | * Extracts the UNIX timestamp from a 64-bit id generated by `generateId64()`. 75 | * 76 | * - Parameter id: the id generated by `generateId64()` 77 | * 78 | * - Returns: the extracted UNIX timestamp in milliseconds 79 | */ 80 | @available(iOS, introduced: 1.0) 81 | public static func extractTimestamp64(_ id: UInt64) -> UInt64 { 82 | return SnowflakeIdGenerator.TIMESTAMP_EPOCH + (id >> SnowflakeIdGenerator.SHIFT_TIMESTAMP_64) 83 | } 84 | 85 | /** 86 | * Extracts the UNIX timestamp from a 64-bit id generated by `generateId64Hex()`. 87 | * 88 | * - Parameter idHex: the id generated by `generateId64Hex()` 89 | * 90 | * - Returns: the extracted UNIX timestamp in milliseconds 91 | */ 92 | @available(iOS, introduced: 1.0) 93 | public static func extractTimestamp64Hex(_ idHex: String) -> UInt64 { 94 | let id = strtouq(idHex, nil, 16) 95 | return extractTimestamp64(id) 96 | } 97 | 98 | /** 99 | * Extracts the UNIX timestamp from a 64-bit id generated by `generateId64Bin()`. 100 | * 101 | * - Parameter idBin: the id generated by `generateId64Bin()` 102 | * 103 | * - Returns: the extracted UNIX timestamp in milliseconds 104 | */ 105 | @available(iOS, introduced: 1.0) 106 | public static func extractTimestamp64Bin(_ idBin: String) -> UInt64 { 107 | let id = strtouq(idBin, nil, 2) 108 | return extractTimestamp64(id) 109 | } 110 | 111 | /** 112 | * Extracts the timestamp part from a 64-bit id generated by `generateId64()` as a Date. 113 | * 114 | * - Parameter id: the id generated by `generateId64()` 115 | * 116 | * - Returns: the extracted timestamp part as a Date 117 | */ 118 | @available(iOS, introduced: 1.0) 119 | public static func extractTimestamp64AsDate(_ id: UInt64) -> Date { 120 | let timestamp = extractTimestamp64(id) 121 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 122 | } 123 | 124 | /** 125 | * Extracts the timestamp part from a 64-bit id generated by `generateId64Hex()` as a Date. 126 | * 127 | * - Parameter idHex: the id generated by `generateId64Hex()` 128 | * 129 | * - Returns: the extracted timestamp part as a Date 130 | */ 131 | @available(iOS, introduced: 1.0) 132 | public static func extractTimestamp64HexAsDate(_ idHex: String) -> Date { 133 | let timestamp = extractTimestamp64Hex(idHex) 134 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 135 | } 136 | 137 | /** 138 | * Extracts the timestamp part from a 64-bit id generated by `generateId64Bin()` as a Date. 139 | * 140 | * - Parameter idBin: the id generated by `generateId64Bin()` 141 | * 142 | * - Returns: the extracted timestamp part as a Date 143 | */ 144 | @available(iOS, introduced: 1.0) 145 | public static func extractTimestamp64BinAsDate(_ idBin: String) -> Date { 146 | let timestamp = extractTimestamp64Bin(idBin) 147 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 148 | } 149 | 150 | /** 151 | * Generates a 64-bit id. 152 | * 153 | * Format: `<41-bits: timestamp><21-bits: node id><2-bits: sequence number>` 154 | * Where timestamp is in milliseconds, minus the epoch. 155 | * 156 | * - Returns: 64-bit id 157 | */ 158 | @available(iOS, introduced: 1.0) 159 | public func generateId64() -> UInt64 { 160 | let result = synchronizd(lock: self) { 161 | let timestamp: UInt64 = DateTimeUtils.currentUnixTimestampMillisec() 162 | var sequence: UInt64 = 0 163 | if (timestamp == self.lastTimestampMillisec) { 164 | // increase sequence 165 | sequence = self.sequenceMillisec.advanced(by: 1) 166 | if (sequence > SnowflakeIdGenerator.MAX_SEQUENCE_64) { 167 | // reset sequence 168 | self.sequenceMillisec = 0 169 | DateTimeUtils.waitTillNextMillisec(timestamp) 170 | return self.generateId64() 171 | } else { 172 | self.sequenceMillisec = sequence 173 | } 174 | } else { 175 | // reset sequence 176 | self.sequenceMillisec = sequence 177 | self.lastTimestampMillisec = timestamp 178 | } 179 | let timestampPart = (timestamp - SnowflakeIdGenerator.TIMESTAMP_EPOCH) & SnowflakeIdGenerator.MASK_TIMESTAMP_64 180 | let result = timestampPart << SnowflakeIdGenerator.SHIFT_TIMESTAMP_64 | self.template64 181 | | (sequence & SnowflakeIdGenerator.MASK_SEQUENCE_64) 182 | return result 183 | 184 | } 185 | return result as! UInt64 186 | } 187 | 188 | /** 189 | * Generates a 64-bit id as a hex string. 190 | * 191 | * Format: `<41-bits: timestamp><21-bits: node id><2-bits: sequence number>` 192 | * Where timestamp is in milliseconds, minus the epoch. 193 | * 194 | * - Returns: 64-bit id as a hex string 195 | */ 196 | @available(iOS, introduced: 1.0) 197 | public func generateId64Hex() -> String { 198 | let result = self.generateId64() 199 | return String(result, radix: 16) 200 | } 201 | 202 | /** 203 | * Generates a 64-bit id as a binary string. 204 | * 205 | * Format: `<41-bits: timestamp><21-bits: node id><2-bits: sequence number>` 206 | * Where timestamp is in milliseconds, minus the epoch. 207 | * 208 | * - Returns: 64-bit id as a binary string 209 | */ 210 | @available(iOS, introduced: 1.0) 211 | public func generateId64Bin() -> String { 212 | let result = self.generateId64() 213 | return String(result, radix: 2) 214 | } 215 | 216 | /*----------------------------------------------------------------------*/ 217 | 218 | /** 219 | * Extracts the UNIX timestamp from a 64-bit-nil id generated by `generateId64Nil()`. 220 | * 221 | * - Parameter id: the id generated by `generateId64Nil()` 222 | * 223 | * - Returns: the extracted UNIX timestamp in milliseconds 224 | */ 225 | @available(iOS, introduced: 1.0) 226 | public static func extractTimestamp64Nil(_ id: UInt64) -> UInt64 { 227 | return SnowflakeIdGenerator.TIMESTAMP_EPOCH + (id >> SnowflakeIdGenerator.SHIFT_TIMESTAMP_64NIL) 228 | } 229 | 230 | /** 231 | * Extracts the UNIX timestamp from a 64-bit-nil id generated by `generateId64NilHex()`. 232 | * 233 | * - Parameter idHex: the id generated by `generateId64NilHex()` 234 | * 235 | * - Returns: the extracted UNIX timestamp in milliseconds 236 | */ 237 | @available(iOS, introduced: 1.0) 238 | public static func extractTimestamp64NilHex(_ idHex: String) -> UInt64 { 239 | let id = strtouq(idHex, nil, 16) 240 | return extractTimestamp64Nil(id) 241 | } 242 | 243 | /** 244 | * Extracts the UNIX timestamp from a 64-bit-nil id generated by `generateId64NilBin()`. 245 | * 246 | * - Parameter idBin: the id generated by `generateId64NilBin()` 247 | * 248 | * - Returns: the extracted UNIX timestamp in milliseconds 249 | */ 250 | @available(iOS, introduced: 1.0) 251 | public static func extractTimestamp64NilBin(_ idBin: String) -> UInt64 { 252 | let id = strtouq(idBin, nil, 2) 253 | return extractTimestamp64Nil(id) 254 | } 255 | 256 | /** 257 | * Extracts the timestamp part from a 64-bit-nil id generated by `generateId64Nil()` as a Date. 258 | * 259 | * - Parameter id: the id generated by `generateId64Nil()` 260 | * 261 | * - Returns: the extracted timestamp part as a Date 262 | */ 263 | @available(iOS, introduced: 1.0) 264 | public static func extractTimestamp64NilAsDate(_ id: UInt64) -> Date { 265 | let timestamp = extractTimestamp64Nil(id) 266 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 267 | } 268 | 269 | /** 270 | * Extracts the timestamp part from a 64-bit-nil id generated by `generateId64NilHex()` as a Date. 271 | * 272 | * - Parameter idHex: the id generated by `generateId64Nil()` 273 | * 274 | * - Returns: the extracted timestamp part as a Date 275 | */ 276 | @available(iOS, introduced: 1.0) 277 | public static func extractTimestamp64NilHexAsDate(_ idHex: String) -> Date { 278 | let timestamp = extractTimestamp64NilHex(idHex) 279 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 280 | } 281 | 282 | /** 283 | * Extracts the timestamp part from a 64-bit-nil id generated by `generateId64NilBin()` as a Date. 284 | * 285 | * - Parameter idBin: the id generated by `generateId64NilBin()` 286 | * 287 | * - Returns: the extracted timestamp part as a Date 288 | */ 289 | @available(iOS, introduced: 1.0) 290 | public static func extractTimestamp64NilBinAsDate(_ idBin: String) -> Date { 291 | let timestamp = extractTimestamp64NilBin(idBin) 292 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 293 | } 294 | 295 | /** 296 | * Generates a 64-bit-nil id. 297 | * 298 | * Format: `<41-bits: timestamp><23-bits: node id>` 299 | * Where timestamp is in milliseconds, minus the epoch. 300 | * 301 | * - Returns: 64-bit-nil id 302 | */ 303 | @available(iOS, introduced: 1.0) 304 | public func generateId64Nil() -> UInt64 { 305 | let result = synchronizd(lock: self) { 306 | let timestamp: UInt64 = DateTimeUtils.currentUnixTimestampMillisec() 307 | if (timestamp == self.lastTimestampMillisec) { 308 | DateTimeUtils.waitTillNextMillisec(timestamp) 309 | return self.generateId64Nil() 310 | } else { 311 | self.lastTimestampMillisec = timestamp 312 | } 313 | let timestampPart = (timestamp - SnowflakeIdGenerator.TIMESTAMP_EPOCH) & SnowflakeIdGenerator.MASK_TIMESTAMP_64NIL 314 | let result = timestampPart << SnowflakeIdGenerator.SHIFT_TIMESTAMP_64NIL | self.template64Nil 315 | return result 316 | } 317 | return result as! UInt64 318 | } 319 | 320 | /** 321 | * Generates a 64-bit-nil id as a hex string 322 | * 323 | * Format: `<41-bits: timestamp><23-bits: node id>` 324 | * Where timestamp is in milliseconds, minus the epoch. 325 | * 326 | * - Returns: 64-bit-nil id as a hex string 327 | */ 328 | @available(iOS, introduced: 1.0) 329 | public func generateId64NilHex() -> String { 330 | let result = self.generateId64Nil() 331 | return String(result, radix: 16) 332 | } 333 | 334 | /** 335 | * Generates a 64-bit-nil id as a binary string 336 | * 337 | * Format: `<41-bits: timestamp><23-bits: node id>` 338 | * Where timestamp is in milliseconds, minus the epoch. 339 | * 340 | * - Returns: 64-bit-nil id as a binary string 341 | */ 342 | @available(iOS, introduced: 1.0) 343 | public func generateId64NilBin() -> String { 344 | let result = self.generateId64Nil() 345 | return String(result, radix: 2) 346 | } 347 | 348 | /*----------------------------------------------------------------------*/ 349 | 350 | /** 351 | * Extracts the UNIX timestamp from a 128-bit id generated by `generateId128Hex()`. 352 | * 353 | * - Parameter idHex: the id generated by `generateId128Hex()` 354 | * 355 | * - Returns: the extracted UNIX timestamp in milliseconds 356 | */ 357 | @available(iOS, introduced: 1.0) 358 | public static func extractTimestamp128Hex(_ idHex: String) -> UInt64 { 359 | let index = idHex.index(idHex.endIndex, offsetBy: -16) 360 | let timestamp = strtouq(idHex.substring(to: index), nil, 16) 361 | return timestamp 362 | } 363 | 364 | /** 365 | * Extracts the UNIX timestamp from a 128-bit id generated by `generateId128Bin()`. 366 | * 367 | * - Parameter idBin: the id generated by `generateId128Bin()` 368 | * 369 | * - Returns: the extracted UNIX timestamp in milliseconds 370 | */ 371 | @available(iOS, introduced: 1.0) 372 | public static func extractTimestamp128Bin(_ idBin: String) -> UInt64 { 373 | let index = idBin.index(idBin.endIndex, offsetBy: -64) 374 | let timestamp = strtouq(idBin.substring(to: index), nil, 2) 375 | return timestamp 376 | } 377 | 378 | /** 379 | * Extracts the timestamp part from a 128-bit id generated by `generateId128Hex()` as a Date. 380 | * 381 | * - Parameter idHex: the id generated by `generateId128Hex()` 382 | * 383 | * - Returns: the extracted timestamp part as a Date 384 | */ 385 | @available(iOS, introduced: 1.0) 386 | public static func extractTimestamp128HexAsDate(_ idHex: String) -> Date { 387 | let timestamp = extractTimestamp128Hex(idHex) 388 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 389 | } 390 | 391 | /** 392 | * Extracts the timestamp part from a 128-bit id generated by `generateId128Bin()` as a Date. 393 | * 394 | * - Parameter idBin: the id generated by `generateId128Bin()` 395 | * 396 | * - Returns: the extracted timestamp part as a Date 397 | */ 398 | @available(iOS, introduced: 1.0) 399 | public static func extractTimestamp128BinAsDate(_ idBin: String) -> Date { 400 | let timestamp = extractTimestamp128Bin(idBin) 401 | return Date(timeIntervalSince1970: Double(timestamp) / 1000.0) 402 | } 403 | 404 | /** 405 | * Generates a 128-bit id. 406 | * 407 | * Format: `<64-bits: timestamp><48-bits: node id><16-bits: sequence number>` 408 | * Where timestamp is in milliseconds. 409 | * 410 | * - Returns: 128-bit id as an array of two UInt64s 411 | */ 412 | @available(iOS, introduced: 1.0) 413 | public func generateId128() -> [UInt64] { 414 | let result = synchronizd(lock: self) { 415 | let timestamp: UInt64 = DateTimeUtils.currentUnixTimestampMillisec() 416 | var sequence: UInt64 = 0 417 | if (timestamp == self.lastTimestampMillisec) { 418 | // increase sequence 419 | sequence = self.sequenceMillisec.advanced(by: 1) 420 | if (sequence > SnowflakeIdGenerator.MAX_SEQUENCE_128) { 421 | // reset sequence 422 | self.sequenceMillisec = 0 423 | DateTimeUtils.waitTillNextMillisec(timestamp) 424 | return self.generateId128() 425 | } else { 426 | self.sequenceMillisec = sequence 427 | } 428 | } else { 429 | // reset sequence 430 | self.sequenceMillisec = sequence 431 | self.lastTimestampMillisec = timestamp 432 | } 433 | let first = timestamp 434 | let second = (self.nodeId << SnowflakeIdGenerator.SHIFT_NODE_ID_128) | (sequence & SnowflakeIdGenerator.MASK_SEQUENCE_128) 435 | return [first, second] 436 | } 437 | return result as! [UInt64] 438 | } 439 | 440 | /** 441 | * Generates a 128-bit id as a hex string. 442 | * 443 | * Format: `<64-bits: timestamp><48-bits: node id><16-bits: sequence number>` 444 | * Where timestamp is in milliseconds. 445 | * 446 | * - Returns: 128-bit id as a hex string 447 | */ 448 | @available(iOS, introduced: 1.0) 449 | public func generateId128Hex() -> String { 450 | let result = self.generateId128() 451 | var tail = String(result[1], radix: 16) 452 | while ( tail.count < 16 ) { 453 | tail.insert("0", at: tail.startIndex) 454 | } 455 | return String(result[0], radix: 16) + tail 456 | } 457 | 458 | /** 459 | * Generates a 128-bit id as a binary string. 460 | * 461 | * Format: `<64-bits: timestamp><48-bits: node id><16-bits: sequence number>` 462 | * Where timestamp is in milliseconds. 463 | * 464 | * - Returns: 128-bit id as a binary string 465 | */ 466 | @available(iOS, introduced: 1.0) 467 | public func generateId128Bin() -> String { 468 | let result = self.generateId128() 469 | var tail = String(result[1], radix: 2) 470 | while ( tail.count < 64 ) { 471 | tail.insert("0", at: tail.startIndex) 472 | } 473 | return String(result[0], radix: 2) + tail 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /SwiftUtils/SyncUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncUtils.swift 3 | // SwiftUtils 4 | // 5 | // Created by Thanh Nguyen on 8/15/16. 6 | // Copyright © 2016 Thanh Nguyen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Synchronizes a code block 13 | 14 | Credit: http://yuhua-chen.logdown.com/posts/253806-synchronized-on-swift 15 | 16 | Usage: 17 | 18 | ``` 19 | synchronizd(self) { 20 | .... code here .... 21 | } 22 | ``` 23 | 24 | - Parameter lock: object for mutex lock 25 | - Parameter closure: code to execute 26 | - Returns: the returned value from closure 27 | */ 28 | @discardableResult public func synchronizd(lock: AnyObject, closure:()->Any?) -> Any? { 29 | objc_sync_enter(lock) 30 | let result = closure() 31 | objc_sync_exit(lock) 32 | return result 33 | } 34 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5F2BE55F24A0AA94003139DB /* SwiftUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F2BE55E24A0AA94003139DB /* SwiftUtils.framework */; }; 11 | 5F2BE56024A0AA94003139DB /* SwiftUtils.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5F2BE55E24A0AA94003139DB /* SwiftUtils.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 5F3B73331EF291AF00876E32 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3B73321EF291AF00876E32 /* AppDelegate.swift */; }; 13 | 5F3B73351EF291AF00876E32 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3B73341EF291AF00876E32 /* ViewController.swift */; }; 14 | 5F3B73381EF291AF00876E32 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F3B73361EF291AF00876E32 /* Main.storyboard */; }; 15 | 5F3B733A1EF291AF00876E32 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F3B73391EF291AF00876E32 /* Assets.xcassets */; }; 16 | 5F3B733D1EF291AF00876E32 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F3B733B1EF291AF00876E32 /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 5F2BE56124A0AA94003139DB /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | 5F2BE56024A0AA94003139DB /* SwiftUtils.framework in Embed Frameworks */, 27 | ); 28 | name = "Embed Frameworks"; 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 5F2BE55E24A0AA94003139DB /* SwiftUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 5F3B732F1EF291AF00876E32 /* SwiftUtilsPlayground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUtilsPlayground.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 5F3B73321EF291AF00876E32 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 5F3B73341EF291AF00876E32 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 38 | 5F3B73371EF291AF00876E32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 5F3B73391EF291AF00876E32 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 5F3B733C1EF291AF00876E32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 5F3B733E1EF291AF00876E32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 5F3B73441EF291B800876E32 /* SwiftUtilsPlayground.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUtilsPlayground.entitlements; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 5F3B732C1EF291AF00876E32 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 5F2BE55F24A0AA94003139DB /* SwiftUtils.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 5F2BE55D24A0AA94003139DB /* Frameworks */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 5F2BE55E24A0AA94003139DB /* SwiftUtils.framework */, 61 | ); 62 | name = Frameworks; 63 | sourceTree = ""; 64 | }; 65 | 5F3B73261EF291AF00876E32 = { 66 | isa = PBXGroup; 67 | children = ( 68 | 5F3B73311EF291AF00876E32 /* SwiftUtilsPlayground */, 69 | 5F3B73301EF291AF00876E32 /* Products */, 70 | 5F2BE55D24A0AA94003139DB /* Frameworks */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | 5F3B73301EF291AF00876E32 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 5F3B732F1EF291AF00876E32 /* SwiftUtilsPlayground.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | 5F3B73311EF291AF00876E32 /* SwiftUtilsPlayground */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 5F3B73441EF291B800876E32 /* SwiftUtilsPlayground.entitlements */, 86 | 5F3B73321EF291AF00876E32 /* AppDelegate.swift */, 87 | 5F3B73341EF291AF00876E32 /* ViewController.swift */, 88 | 5F3B73361EF291AF00876E32 /* Main.storyboard */, 89 | 5F3B73391EF291AF00876E32 /* Assets.xcassets */, 90 | 5F3B733B1EF291AF00876E32 /* LaunchScreen.storyboard */, 91 | 5F3B733E1EF291AF00876E32 /* Info.plist */, 92 | ); 93 | path = SwiftUtilsPlayground; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | 5F3B732E1EF291AF00876E32 /* SwiftUtilsPlayground */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = 5F3B73411EF291AF00876E32 /* Build configuration list for PBXNativeTarget "SwiftUtilsPlayground" */; 102 | buildPhases = ( 103 | 5F3B732B1EF291AF00876E32 /* Sources */, 104 | 5F3B732C1EF291AF00876E32 /* Frameworks */, 105 | 5F3B732D1EF291AF00876E32 /* Resources */, 106 | 5F2BE56124A0AA94003139DB /* Embed Frameworks */, 107 | ); 108 | buildRules = ( 109 | ); 110 | dependencies = ( 111 | ); 112 | name = SwiftUtilsPlayground; 113 | productName = SwiftUtilsPlayground; 114 | productReference = 5F3B732F1EF291AF00876E32 /* SwiftUtilsPlayground.app */; 115 | productType = "com.apple.product-type.application"; 116 | }; 117 | /* End PBXNativeTarget section */ 118 | 119 | /* Begin PBXProject section */ 120 | 5F3B73271EF291AF00876E32 /* Project object */ = { 121 | isa = PBXProject; 122 | attributes = { 123 | LastSwiftUpdateCheck = 0830; 124 | LastUpgradeCheck = 1140; 125 | ORGANIZATIONNAME = "Thanh Nguyen"; 126 | TargetAttributes = { 127 | 5F3B732E1EF291AF00876E32 = { 128 | CreatedOnToolsVersion = 8.3.1; 129 | DevelopmentTeam = 7T2WS9KVUZ; 130 | LastSwiftMigration = 1140; 131 | ProvisioningStyle = Automatic; 132 | SystemCapabilities = { 133 | com.apple.Keychain = { 134 | enabled = 1; 135 | }; 136 | }; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = 5F3B732A1EF291AF00876E32 /* Build configuration list for PBXProject "SwiftUtilsPlayground" */; 141 | compatibilityVersion = "Xcode 3.1"; 142 | developmentRegion = en; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = 5F3B73261EF291AF00876E32; 149 | productRefGroup = 5F3B73301EF291AF00876E32 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | 5F3B732E1EF291AF00876E32 /* SwiftUtilsPlayground */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | 5F3B732D1EF291AF00876E32 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | 5F3B733D1EF291AF00876E32 /* LaunchScreen.storyboard in Resources */, 164 | 5F3B733A1EF291AF00876E32 /* Assets.xcassets in Resources */, 165 | 5F3B73381EF291AF00876E32 /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | 5F3B732B1EF291AF00876E32 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 5F3B73351EF291AF00876E32 /* ViewController.swift in Sources */, 177 | 5F3B73331EF291AF00876E32 /* AppDelegate.swift in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | 5F3B73361EF291AF00876E32 /* Main.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 5F3B73371EF291AF00876E32 /* Base */, 188 | ); 189 | name = Main.storyboard; 190 | sourceTree = ""; 191 | }; 192 | 5F3B733B1EF291AF00876E32 /* LaunchScreen.storyboard */ = { 193 | isa = PBXVariantGroup; 194 | children = ( 195 | 5F3B733C1EF291AF00876E32 /* Base */, 196 | ); 197 | name = LaunchScreen.storyboard; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXVariantGroup section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | 5F3B733F1EF291AF00876E32 /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 211 | CLANG_CXX_LIBRARY = "libc++"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_COMMA = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INFINITE_RECURSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 227 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 230 | CLANG_WARN_STRICT_PROTOTYPES = YES; 231 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | DEBUG_INFORMATION_FORMAT = dwarf; 237 | ENABLE_STRICT_OBJC_MSGSEND = YES; 238 | ENABLE_TESTABILITY = YES; 239 | GCC_C_LANGUAGE_STANDARD = gnu99; 240 | GCC_DYNAMIC_NO_PIC = NO; 241 | GCC_NO_COMMON_BLOCKS = YES; 242 | GCC_OPTIMIZATION_LEVEL = 0; 243 | GCC_PREPROCESSOR_DEFINITIONS = ( 244 | "DEBUG=1", 245 | "$(inherited)", 246 | ); 247 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 248 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 249 | GCC_WARN_UNDECLARED_SELECTOR = YES; 250 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 251 | GCC_WARN_UNUSED_FUNCTION = YES; 252 | GCC_WARN_UNUSED_VARIABLE = YES; 253 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 254 | MTL_ENABLE_DEBUG_INFO = YES; 255 | ONLY_ACTIVE_ARCH = YES; 256 | SDKROOT = iphoneos; 257 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 258 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 259 | SWIFT_VERSION = 4.0; 260 | TARGETED_DEVICE_FAMILY = "1,2"; 261 | VALIDATE_PRODUCT = YES; 262 | }; 263 | name = Debug; 264 | }; 265 | 5F3B73401EF291AF00876E32 /* Release */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 270 | CLANG_ANALYZER_NONNULL = YES; 271 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_MODULES = YES; 275 | CLANG_ENABLE_OBJC_ARC = YES; 276 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_COMMA = YES; 279 | CLANG_WARN_CONSTANT_CONVERSION = YES; 280 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 283 | CLANG_WARN_EMPTY_BODY = YES; 284 | CLANG_WARN_ENUM_CONVERSION = YES; 285 | CLANG_WARN_INFINITE_RECURSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 289 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_TESTABILITY = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_NO_COMMON_BLOCKS = YES; 304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 306 | GCC_WARN_UNDECLARED_SELECTOR = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 308 | GCC_WARN_UNUSED_FUNCTION = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 311 | MTL_ENABLE_DEBUG_INFO = NO; 312 | SDKROOT = iphoneos; 313 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 314 | SWIFT_VERSION = 4.0; 315 | TARGETED_DEVICE_FAMILY = "1,2"; 316 | VALIDATE_PRODUCT = YES; 317 | }; 318 | name = Release; 319 | }; 320 | 5F3B73421EF291AF00876E32 /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 324 | CODE_SIGN_ENTITLEMENTS = SwiftUtilsPlayground/SwiftUtilsPlayground.entitlements; 325 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 326 | INFOPLIST_FILE = SwiftUtilsPlayground/Info.plist; 327 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 329 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtilsPlayground; 330 | PRODUCT_NAME = "$(TARGET_NAME)"; 331 | SWIFT_VERSION = 4.0; 332 | }; 333 | name = Debug; 334 | }; 335 | 5F3B73431EF291AF00876E32 /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | CODE_SIGN_ENTITLEMENTS = SwiftUtilsPlayground/SwiftUtilsPlayground.entitlements; 340 | DEVELOPMENT_TEAM = 7T2WS9KVUZ; 341 | INFOPLIST_FILE = SwiftUtilsPlayground/Info.plist; 342 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 343 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 344 | PRODUCT_BUNDLE_IDENTIFIER = com.github.btnguyen2k.ios.SwiftUtilsPlayground; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 4.0; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | 5F3B732A1EF291AF00876E32 /* Build configuration list for PBXProject "SwiftUtilsPlayground" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 5F3B733F1EF291AF00876E32 /* Debug */, 357 | 5F3B73401EF291AF00876E32 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | 5F3B73411EF291AF00876E32 /* Build configuration list for PBXNativeTarget "SwiftUtilsPlayground" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 5F3B73421EF291AF00876E32 /* Debug */, 366 | 5F3B73431EF291AF00876E32 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = 5F3B73271EF291AF00876E32 /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftUtilsPlayground 4 | // 5 | // Created by Thanh Nguyen on 6/15/17. 6 | // Copyright © 2017 Thanh Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/SwiftUtilsPlayground.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)com.github.btnguyen2k.ios.SwiftUtilsPlayground 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftUtilsPlayground/SwiftUtilsPlayground/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftUtilsPlayground 4 | // 5 | // Created by Thanh Nguyen on 6/15/17. 6 | // Copyright © 2017 Thanh Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUtils 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view, typically from a nib. 17 | } 18 | 19 | override func didReceiveMemoryWarning() { 20 | super.didReceiveMemoryWarning() 21 | // Dispose of any resources that can be recreated. 22 | } 23 | 24 | 25 | @IBOutlet weak var btnTestClicked: UIButton! 26 | 27 | @IBAction func btnTest(_ sender: Any) { 28 | let privKey64 = "MIIEowIBAAKCAQEApjYx0U5cJLAkx3ATqxk5qv7raEv/romlDPa2WFjahplh4lFXD9dzX1fDLWPqwlxp/ZuUlYf3p50oVogoUe2+5IQ/qtpUEaB5Acc8qcx9FekKvxSG8SmCtc3NsC08Bsd6Rw4M6MGB51xeuSbB3cqh7UUhKiFZflUnudMJ7t3mPwEyvgLpA44MJ7nYnG28S0NqTUnM24IVRbPBYvd8xT6swum4nvwGQvbzP8PKqdYd/LBXkCzWMig9ZplGDBNPrAywoWdaGeYIoK4OO3ZhkSKGqAFSEZBj8HYNpHsj3NBqXsRfh0gaAV8tOKP79sBqa6hCL5LQr+nj5QIqYqa4NmUWFQIDAQABAoIBAEQmHmD6Zw9n5XyceIQD0MBKy0y1NH1k1HavRW6N5/OeOiMoRLErHpPuEQmwWRYd/BtOyipRWHqyFlL8esO03jsl+lDcOfIMQPE4tqfMP1FXft9/1CBXa8+NNZPRyDXjXkB6A1L/3Hp3RiNWP9Dnf2MgoAt9JwuEcMhhPkZhV0FnW8SeWh5bdmZstGsoLg6adtR8Bcz0RmE41SCzfIdWNxPUfwuMSitDonTttUOWEvYju8uTSt3VfPcYt8WBNj7ijFwWPRIiVYy118DNpKsmK3ZQQ88diNClPoXbb1XD7FRVhc5x9TqWl7Mpw3T9Wd6O8d+izn2Ny3Om6m0n5CpwJDUCgYEA3cNVvs/aWeqo0ov9QKTNv+XK1LsczAJEfNUa8IPpj35zzy21lC2cOWKKXaQeyIwI9YjeKD7OGdOdynN+Zpv6v+z/+4VNvf5BKWYwBTNaHjeo9gvJY8y49ZUAxXJgT6+wuc+PHVBOtowBed50V3aq+eYcYrkaIeOF9nC7cTcfNo8CgYEAv99SwZHXmxbJOF47LWqk5+k8bhIuPtruU3flW7Aw6tTARXQtf0N1GXPkaiRycFUuTg6YO7YNUK3NyuWKexAoZJ+IsYJp3CZBmUmRMN43xdPRtCTI0iQ7xdxMoyuTiMQTXqWEzSCvMK6LmXA7ss+2RgYGJxRto6OVZDyWsY2y2xsCgYBIoga+hWN12dbLhxA+kPvWo1PokxReeUunO0Ekj3/7AKa7r7PuQXYOkYjSHMP8WWlByj9BHTjx2o6u1V8MCoB5Rg2des64sML05URLH8OfbAmJ9NhUOKRHwzLKeKi00M1oPmQYbMorHfYJzWN7liFv0f9wEkJN4TBqRAgQj8ZIXwKBgQCCwPiPawjH6pZDsIuZXqR0WhBRXlhfgyiIfxsxCXe8gDFq40oiAOFq7/xHjQDjpljA3tdFUcHvKhIk45okVRWBxOoNNbdYTEXF5UlpSPYu/TNaFPs5oXGOW5tqUsbem+grRpaih1R702bUsJQuPbiULYTB2v0AV/j1bwiuhMbO4wKBgGTPf+XhpBdZLMfiuUI0hrVB/QminXMNsw4BU5CDW3K4hzo2fKKVhPkIKZ2RgU0hAqODdyJaEft0wN3X4fq+Tu4KIqz6RR8+BVzfUaAEyUE9E/UHLnO380iQ+7HwkSBGERXgFpZDf++L5sKT+pDT2jZ4tLyE8axIqWTWa22WV8J/" 29 | let pubKey64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApjYx0U5cJLAkx3ATqxk5qv7raEv/romlDPa2WFjahplh4lFXD9dzX1fDLWPqwlxp/ZuUlYf3p50oVogoUe2+5IQ/qtpUEaB5Acc8qcx9FekKvxSG8SmCtc3NsC08Bsd6Rw4M6MGB51xeuSbB3cqh7UUhKiFZflUnudMJ7t3mPwEyvgLpA44MJ7nYnG28S0NqTUnM24IVRbPBYvd8xT6swum4nvwGQvbzP8PKqdYd/LBXkCzWMig9ZplGDBNPrAywoWdaGeYIoK4OO3ZhkSKGqAFSEZBj8HYNpHsj3NBqXsRfh0gaAV8tOKP79sBqa6hCL5LQr+nj5QIqYqa4NmUWFQIDAQAB" 30 | 31 | let tagPublic = "com.github.btnguyen2k.ios.SwiftUtilsPlayground.public" 32 | let tagPrivate = "com.github.btnguyen2k.ios.SwiftUtilsPlayground.private" 33 | let keySecPub = try! RSAUtils.addRSAPublicKey(pubKey64, tagName: tagPublic) 34 | let keySecPriv = try! RSAUtils.addRSAPrivateKey(privKey64, tagName: tagPrivate) 35 | NSLog("Public Key : \(keySecPub!)") 36 | NSLog("Private Key: \(keySecPriv!)") 37 | if keySecPub==nil||keySecPriv==nil{ 38 | return 39 | } 40 | 41 | let orgText = "This a string" 42 | NSLog("Original text : \(orgText)") 43 | let secPadding = SecPadding.OAEP 44 | 45 | let eData = RSAUtils.encryptWithRSAKey(orgText.data(using: .utf8)!, rsaKeyRef: keySecPub!, padding: secPadding) 46 | if eData==nil{ 47 | NSLog("ERROR: Encrypt with RSA public key") 48 | return 49 | } 50 | NSLog("Encrypted data: \(eData!)") 51 | NSLog("Encrypted data: \(eData!.base64EncodedString())") 52 | 53 | let dData = RSAUtils.decryptWithRSAKey(eData!, rsaKeyRef: keySecPriv!, padding: secPadding) 54 | if dData==nil{ 55 | NSLog("ERROR: Decrypt with RSA private key") 56 | return 57 | } 58 | NSLog("Decrypted data: \(dData!)") 59 | NSLog("Decrypted text: \(String(data: dData!, encoding: .utf8)!)") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SwiftUtilsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftUtilsTests/SwiftUtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUtilsTests.swift 3 | // SwiftUtilsTests 4 | // 5 | // Created by Thanh Nguyen on 8/14/16. 6 | // Copyright © 2016 Thanh Nguyen. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SwiftUtils 11 | 12 | class SwiftUtilsTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------