├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── SafeCollection.podspec ├── Sources └── SafeCollection │ └── SafeCollection.swift ├── Tests └── SafeCollectionTests │ └── SafeCollectionTests.swift └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | sourcekitten-output.json 3 | docs/ 4 | /.build 5 | /Packages 6 | /*.xcodeproj 7 | **/xcuserdata 8 | **/xcshareddata 9 | Pods/ 10 | Carthage/ 11 | Examples/**/Podfile.lock 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.2 2 | language: objective-c 3 | sudo: required 4 | env: 5 | global: 6 | - PROJECT="SafeCollection.xcodeproj" 7 | - SCHEME="SafeCollection-Package" 8 | - IOS_SDK="iphonesimulator12.2" 9 | - MACOS_SDK="macosx10.14" 10 | - TVOS_SDK="appletvsimulator12.2" 11 | - WATCHOS_SDK="watchsimulator5.2" 12 | - FRAMEWORK="SafeCollection" 13 | matrix: 14 | - SDK="$IOS_SDK" TEST=1 DESTINATION="platform=iOS Simulator,name=iPhone 8,OS=12.2" 15 | - SDK="$MACOS_SDK" TEST=1 DESTINATION="arch=x86_64" 16 | - SDK="$TVOS_SDK" TEST=1 DESTINATION="OS=12.2,name=Apple TV 4K" 17 | - SDK="$WATCHOS_SDK" TEST=0 DESTINATION="OS=5.2,name=Apple Watch Series 4 - 44mm" 18 | 19 | install: 20 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" 21 | - swift --version 22 | 23 | before_script: 24 | - set -o pipefail 25 | - swift package generate-xcodeproj 26 | 27 | script: 28 | - if [ $TEST == 1 ]; then 29 | xcodebuild clean build test 30 | -project "$PROJECT" 31 | -scheme "$SCHEME" 32 | -sdk "$SDK" 33 | -destination "$DESTINATION" 34 | -configuration Debug 35 | -enableCodeCoverage YES 36 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 37 | else 38 | xcodebuild clean build 39 | -project "$PROJECT" 40 | -scheme "$SCHEME" 41 | -sdk "$SDK" 42 | -destination "$DESTINATION" 43 | -configuration Debug 44 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 45 | fi 46 | 47 | after_success: 48 | - if [ $TEST == 1 ]; then 49 | bash <(curl -s https://codecov.io/bash) -X xcodeplist -J 'SafeCollection'; 50 | fi 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Suyeol Jeon (xoul.kr) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SafeCollection", 7 | platforms: [ 8 | .macOS(.v10_10), .iOS(.v8), .tvOS(.v9) 9 | ], 10 | products: [ 11 | .library(name: "SafeCollection", targets: ["SafeCollection"]), 12 | ], 13 | targets: [ 14 | .target(name: "SafeCollection"), 15 | .testTarget(name: "SafeCollectionTests", dependencies: ["SafeCollection"]), 16 | ], 17 | swiftLanguageVersions: [.v5] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SafeCollection 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 4 | [![Build Status](https://travis-ci.org/devxoul/SafeCollection.svg)](https://travis-ci.org/devxoul/SafeCollection) 5 | [![CocoaPods](http://img.shields.io/cocoapods/v/SafeCollection.svg)](https://cocoapods.org/pods/SafeCollection) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | 8 | SafeCollection provides a safer way to deal with subscripts. Inspired by Swift's [LazyCollection](https://developer.apple.com/documentation/swift/lazycollection). 9 | 10 | ## At a Glance 11 | 12 | ```swift 13 | let arr = ["A", "B", "C"] 14 | 15 | arr[0] // "A" 16 | arr[100] // fatal error: Array index out of range 😟 17 | 18 | arr.safe[0] // Optional("A") 19 | arr.safe[100] // nil 😄 20 | ``` 21 | 22 | ## Installation 23 | 24 | - **For iOS 8+ projects** with [CocoaPods](https://cocoapods.org): 25 | 26 | ```ruby 27 | pod 'SafeCollection' 28 | ``` 29 | 30 | - **For iOS 8+ projects** with [Carthage](https://github.com/Carthage/Carthage): 31 | 32 | ``` 33 | github "devxoul/SafeCollection" 34 | ``` 35 | 36 | - **Using [Swift Package Manager](https://swift.org/package-manager)**: 37 | 38 | ```swift 39 | import PackageDescription 40 | 41 | let package = Package( 42 | name: "MyAwesomeApp", 43 | dependencies: [ 44 | .package(url: "https://github.com/devxoul/SafeCollection", .upToNextMajor(from: "3.0.0")), 45 | ] 46 | ) 47 | ``` 48 | 49 | ## License 50 | 51 | SafeCollection is under MIT license. See the [LICENSE](LICENSE) file for more info. 52 | -------------------------------------------------------------------------------- /SafeCollection.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SafeCollection" 3 | s.version = "3.1.0" 4 | s.summary = "Safe Collection for Swift" 5 | s.homepage = "https://github.com/devxoul/SafeCollection" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Suyeol Jeon" => "devxoul@gmail.com" } 8 | s.source = { :git => "https://github.com/devxoul/SafeCollection.git", 9 | :tag => s.version.to_s } 10 | s.source_files = "Sources/**/*.swift" 11 | s.requires_arc = true 12 | s.swift_version = "5.0" 13 | 14 | s.ios.deployment_target = "8.0" 15 | s.osx.deployment_target = "10.9" 16 | s.tvos.deployment_target = "9.0" 17 | end 18 | -------------------------------------------------------------------------------- /Sources/SafeCollection/SafeCollection.swift: -------------------------------------------------------------------------------- 1 | public struct SafeCollection: Collection where C: Collection { 2 | public typealias Index = C.Index 3 | public typealias Element = Optional 4 | 5 | private var collection: C 6 | 7 | init(_ collection: C) { 8 | self.collection = collection 9 | } 10 | 11 | public subscript(position: Index) -> Element { 12 | guard self.collection.indices.contains(position) else { return nil } 13 | return self.collection[position] 14 | } 15 | 16 | public var startIndex: Index { 17 | return self.collection.startIndex 18 | } 19 | 20 | public var endIndex: Index { 21 | return self.collection.endIndex 22 | } 23 | 24 | public func index(after i: Index) -> Index { 25 | return self.collection.index(after: i) 26 | } 27 | } 28 | 29 | public struct SafeMutableCollection: MutableCollection where C: MutableCollection { 30 | public typealias Index = C.Index 31 | public typealias Element = Optional 32 | 33 | fileprivate var collection: C 34 | 35 | init(_ collection: C) { 36 | self.collection = collection 37 | } 38 | 39 | public subscript(position: Index) -> Element { 40 | get { 41 | guard self.collection.indices.contains(position) else { return nil } 42 | return self.collection[position] 43 | } 44 | set { 45 | guard let value = newValue else { return } 46 | guard self.collection.indices.contains(position) else { return } 47 | self.collection[position] = value 48 | } 49 | } 50 | 51 | public var startIndex: Index { 52 | return self.collection.startIndex 53 | } 54 | 55 | public var endIndex: Index { 56 | return self.collection.endIndex 57 | } 58 | 59 | public func index(after i: Index) -> Index { 60 | return self.collection.index(after: i) 61 | } 62 | } 63 | 64 | public extension Collection { 65 | var safe: SafeCollection { 66 | return .init(self) 67 | } 68 | } 69 | 70 | public extension MutableCollection { 71 | var safe: SafeMutableCollection { 72 | get { return .init(self) } 73 | set { self = newValue.collection } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/SafeCollectionTests/SafeCollectionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import SafeCollection 3 | 4 | class SafeCollectionTests: XCTestCase { 5 | func testSafeCollection() { 6 | let arr = ["A", "B", "C"] 7 | XCTAssertEqual(arr.safe[0], "A") 8 | XCTAssertEqual(arr.safe[1], "B") 9 | XCTAssertEqual(arr.safe[2], "C") 10 | XCTAssertNil(arr.safe[3]) 11 | 12 | var mutableArr = ["A", "B", "C"] 13 | XCTAssertEqual(mutableArr.safe[0], "A") 14 | XCTAssertEqual(mutableArr.safe[1], "B") 15 | XCTAssertEqual(mutableArr.safe[2], "C") 16 | XCTAssertNil(mutableArr.safe[3]) 17 | mutableArr.safe[2] = "D" 18 | XCTAssertEqual(mutableArr.safe[2], "D") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests/" 3 | 4 | coverage: 5 | status: 6 | project: no 7 | patch: no 8 | changes: no 9 | --------------------------------------------------------------------------------