├── .gitignore ├── .mailmap ├── Info.plist ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources └── TSAO.swift ├── TSAO.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── TSAO.xcscheme ├── Tests └── TSAOTests │ ├── Info.plist │ └── Tests.swift └── swift-tsao.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | /.build/ 3 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Lily Ballard 2 | Lily Ballard 3 | -------------------------------------------------------------------------------- /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 | 3.0.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Lily Ballard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "TSAO" 5 | ) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Type-Safe Associated Objects in Swift 2 | 3 | [![Version](https://img.shields.io/badge/version-v3.0.2-blue.svg)](https://github.com/lilyball/swift-tsao/releases/latest) 4 | ![Platforms](https://img.shields.io/badge/platforms-ios%20%7C%20osx%20%7C%20watchos%20%7C%20tvos-lightgrey.svg) 5 | ![Languages](https://img.shields.io/badge/languages-swift%202.2-orange.svg) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/lilyball/swift-tsao/blob/master/LICENSE.txt) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)][Carthage] 8 | [![CocoaPods](https://img.shields.io/cocoapods/v/swift-tsao.svg)](http://cocoadocs.org/docsets/swift-tsao) 9 | 10 | [Carthage]: https://github.com/carthage/carthage 11 | 12 | TSAO is an implementation of type-safe associated objects in Swift. Objective-C 13 | associated objects are useful, but they are also untyped; every associated 14 | object is only known to be `id` at compile-time and clients must either test 15 | the class at runtime or rely on it being the expected type. 16 | 17 | Swift allows us to do better. We can associate the value type with the key used 18 | to reference the value, and this lets us provide a strongly-typed value at 19 | compile-time with no runtime overhead¹. What's more, it allows us to store 20 | value types as associated objects, not just object types, by transparently 21 | boxing the value (although this involves a heap allocation). We can also invert 22 | the normal way associated objects work and present this type-safe adaptor using 23 | the semantics of a global map from `AnyObject` to `ValueType`. 24 | 25 | It's also possible to specify the association policy. For all values, 26 | atomic/nonatomic retain is supported. For class values, assign is also 27 | supported. And for `NSCopying` values, atomic/nonatomic copy is supported. 28 | 29 | To properly use this library, the `AssocMap` values you create should be static 30 | or global values (they should live for the lifetime of the program). You aren't 31 | required to follow this rule, but any `AssocMap`s you discard will end up 32 | leaking an object (this is the only way to ensure safety without a runtime 33 | penalty). 34 | 35 | ¹ It does require a type-check, but the optimizer should in theory be able to 36 | remove this check. 37 | 38 | ### Usage example 39 | 40 | ```swift 41 | import TSAO 42 | 43 | // create a new map that stores the value type Int 44 | // note how this is a global value, so it lives for the whole program 45 | let intMap = AssocMap() 46 | 47 | // fetch the associated object from `obj` using `intMap` 48 | func lookup_int_object(obj: AnyObject) -> Int? { 49 | // The subscript getter returns a value of type `Int?` so no casting is necessary 50 | return intMap[obj] 51 | } 52 | 53 | // set the associated object for `intMap` on `obj` 54 | func set_int_object(obj: AnyObject, val: Int?) { 55 | // The subscript setter takes an `Int?` directly, trying to pass 56 | // a value of any other type would be a compile-time error 57 | intMap[obj] = val 58 | } 59 | 60 | // This map stores values of type NSString with the nonatomic copy policy 61 | let strMap = AssocMap(copyAtomic: false) 62 | 63 | // fetch the associated object from `obj` using `strMap` 64 | func lookup_str_object(obj: AnyObject) -> NSString? { 65 | // The subscrip getter returns a value of type `NSString?` 66 | return strMap[obj] 67 | } 68 | 69 | // set the associated object for `strMap` on `obj` 70 | func set_str_object(obj: AnyObject, val: NSString?) { 71 | // The subscript setter takes an `NSString?` directly, trying to pass 72 | // an `Int?` like we did with `intMap` would be a compile-time error 73 | strMap[obj] = val 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /Sources/TSAO.swift: -------------------------------------------------------------------------------- 1 | // Type-Safe Associated Objects 2 | 3 | import Foundation 4 | 5 | /// `AssocMap` represents a map from `AnyObject`s to `ValueType`, using associated 6 | /// objects as the storage mechanism. Like associated objects, the `AnyObject` 7 | /// value is not retained, and the `ValueType` will be destructed immediately 8 | /// when the `AnyObject` deinits. 9 | /// 10 | /// Maps should be toplevel or static let-bindings. Every map that is created 11 | /// will live forever, even if you remove all references to it, so creating maps 12 | /// on the fly can be a memory leak. This is done to preserve the safety of the 13 | /// associated values without a runtime penalty. 14 | /// 15 | /// Maps with a class value type may use the assign policy, and maps with a 16 | /// value type that conforms to `NSCopying` may use the copy policy. Value types 17 | /// must always use the default retain policy. 18 | public struct AssocMap { 19 | /// Initializes an `AssocMap` with a retain policy. 20 | /// - Parameter atomic: Whether the policy should be atomic. Default is `false`. 21 | public init(atomic: Bool = false) { 22 | self.init(_policy: atomic ? .OBJC_ASSOCIATION_RETAIN : .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 23 | } 24 | 25 | /// Gets or sets an associated value for a given object. 26 | public subscript(object: AnyObject) -> ValueType? { 27 | get { 28 | guard let value = _get(object) else { return nil } 29 | // use a type-check on ValueType instead of a type-check on the value 30 | // this way the optimizer can hopefully strip it out for us 31 | if ValueType.self is AnyObject.Type { 32 | return unsafeBitCast(value, to: ValueType.self) 33 | } else { 34 | let box = unsafeBitCast(value, to: _AssocValueBox.self) 35 | return box._storage 36 | } 37 | } 38 | nonmutating set { 39 | // type-check the ValueType instead of the value to avoid obj-c bridging. 40 | // This way we'll match the expected boxing behavior of the getter. 41 | // Hopefully the optimizer will strip this out for us. 42 | if ValueType.self is AnyObject.Type { 43 | _set(object, newValue as AnyObject?) 44 | } else if let v = newValue { 45 | _set(object, _AssocValueBox(v)) 46 | } else { 47 | _set(object, nil) 48 | } 49 | } 50 | } 51 | 52 | fileprivate init(_policy: objc_AssociationPolicy) { 53 | self._policy = _policy 54 | } 55 | 56 | private let _policy: objc_AssociationPolicy 57 | private let _key: _AssocKey = _AssocKey() 58 | 59 | private func _get(_ object: AnyObject) -> AnyObject? { 60 | let p = Unmanaged.passUnretained(_key).toOpaque() 61 | return objc_getAssociatedObject(object, p) as AnyObject? 62 | } 63 | 64 | private func _set(_ object: AnyObject, _ value: AnyObject?) { 65 | let p = Unmanaged.passUnretained(_key).toOpaque() 66 | objc_setAssociatedObject(object, p, value, _policy) 67 | } 68 | } 69 | 70 | extension AssocMap where ValueType: AnyObject { 71 | /// Initializes an `AssocMap` with an assign policy. 72 | /// 73 | /// This looks a bit weird, but it can be invoked as `AssocMap(assign: ())`. 74 | public init(assign: ()) { 75 | self.init(_policy: .OBJC_ASSOCIATION_ASSIGN) 76 | } 77 | } 78 | 79 | extension AssocMap where ValueType: NSCopying { 80 | /// Initializes an `AssocMap` with a copy policy. 81 | /// 82 | /// - Parameter copyAtomic: Whether the policy should be atomic. 83 | public init(copyAtomic atomic: Bool) { 84 | self.init(_policy: atomic ? .OBJC_ASSOCIATION_COPY : .OBJC_ASSOCIATION_COPY_NONATOMIC) 85 | } 86 | } 87 | 88 | /// Helper class that is used as the identity for the associated object key. 89 | private final class _AssocKey { 90 | static var allKeys: [_AssocKey] = [] 91 | static let keyQueue: DispatchQueue = { 92 | if #available(OSX 10.10, *) { // NOTE: Add iOS 8.0 to this check if using this file on a project that targets iOS 7 93 | return DispatchQueue(label: "swift-tsao key queue", qos: .background) 94 | } else { 95 | return DispatchQueue(label: "swift-tsao key queue", target: DispatchQueue.global(priority: .background)) 96 | } 97 | }() 98 | 99 | init() { 100 | // keep us alive forever 101 | _AssocKey.keyQueue.async { 102 | _AssocKey.allKeys.append(self) 103 | } 104 | } 105 | 106 | deinit { 107 | fatalError("_AssocKey should not be able to deinit") 108 | } 109 | } 110 | 111 | /// Helper class that wraps values for use with associated objects 112 | private final class _AssocValueBox { 113 | var _storage: ValueType 114 | 115 | init(_ v: ValueType) { 116 | _storage = v 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /TSAO.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A1A09571C0D414C00A91E4B /* TSAO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A1A094C1C0D414C00A91E4B /* TSAO.framework */; }; 11 | 0A1A095C1C0D414C00A91E4B /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1A095B1C0D414C00A91E4B /* Tests.swift */; }; 12 | 0A1A09681C0D427F00A91E4B /* TSAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1A09671C0D427F00A91E4B /* TSAO.swift */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXContainerItemProxy section */ 16 | 0A1A09581C0D414C00A91E4B /* PBXContainerItemProxy */ = { 17 | isa = PBXContainerItemProxy; 18 | containerPortal = 0A1A09431C0D414C00A91E4B /* Project object */; 19 | proxyType = 1; 20 | remoteGlobalIDString = 0A1A094B1C0D414C00A91E4B; 21 | remoteInfo = TSAO; 22 | }; 23 | /* End PBXContainerItemProxy section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 0A1A094C1C0D414C00A91E4B /* TSAO.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TSAO.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 0A1A09511C0D414C00A91E4B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 0A1A09561C0D414C00A91E4B /* TSAOTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TSAOTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 0A1A095B1C0D414C00A91E4B /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 30 | 0A1A095D1C0D414C00A91E4B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 0A1A09661C0D427200A91E4B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 32 | 0A1A09671C0D427F00A91E4B /* TSAO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TSAO.swift; path = Sources/TSAO.swift; sourceTree = ""; }; 33 | 0A1A09691C0D458300A91E4B /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 34 | 0A5E5A2420B5340200948888 /* swift-tsao.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = "swift-tsao.podspec"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 0A1A09481C0D414C00A91E4B /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | 0A1A09531C0D414C00A91E4B /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 0A1A09571C0D414C00A91E4B /* TSAO.framework in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 0A1A09421C0D414C00A91E4B = { 57 | isa = PBXGroup; 58 | children = ( 59 | 0A1A09661C0D427200A91E4B /* README.md */, 60 | 0A1A09691C0D458300A91E4B /* LICENSE.txt */, 61 | 0A5E5A2420B5340200948888 /* swift-tsao.podspec */, 62 | 0A1A09671C0D427F00A91E4B /* TSAO.swift */, 63 | 0A1A094E1C0D414C00A91E4B /* Supporting Files */, 64 | 0A1A095A1C0D414C00A91E4B /* Tests */, 65 | 0A1A094D1C0D414C00A91E4B /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | 0A1A094D1C0D414C00A91E4B /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 0A1A094C1C0D414C00A91E4B /* TSAO.framework */, 73 | 0A1A09561C0D414C00A91E4B /* TSAOTests.xctest */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | 0A1A094E1C0D414C00A91E4B /* Supporting Files */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 0A1A09511C0D414C00A91E4B /* Info.plist */, 82 | ); 83 | name = "Supporting Files"; 84 | sourceTree = ""; 85 | }; 86 | 0A1A095A1C0D414C00A91E4B /* Tests */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 0A79B50B1E8B782400A8C727 /* TSAOTests */, 90 | ); 91 | path = Tests; 92 | sourceTree = ""; 93 | }; 94 | 0A79B50B1E8B782400A8C727 /* TSAOTests */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 0A1A095B1C0D414C00A91E4B /* Tests.swift */, 98 | 0A1A095D1C0D414C00A91E4B /* Info.plist */, 99 | ); 100 | path = TSAOTests; 101 | sourceTree = ""; 102 | }; 103 | /* End PBXGroup section */ 104 | 105 | /* Begin PBXHeadersBuildPhase section */ 106 | 0A1A09491C0D414C00A91E4B /* Headers */ = { 107 | isa = PBXHeadersBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXHeadersBuildPhase section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | 0A1A094B1C0D414C00A91E4B /* TSAO */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = 0A1A09601C0D414C00A91E4B /* Build configuration list for PBXNativeTarget "TSAO" */; 119 | buildPhases = ( 120 | 0A1A09471C0D414C00A91E4B /* Sources */, 121 | 0A1A09481C0D414C00A91E4B /* Frameworks */, 122 | 0A1A09491C0D414C00A91E4B /* Headers */, 123 | 0A1A094A1C0D414C00A91E4B /* Resources */, 124 | ); 125 | buildRules = ( 126 | ); 127 | dependencies = ( 128 | ); 129 | name = TSAO; 130 | productName = TSAO; 131 | productReference = 0A1A094C1C0D414C00A91E4B /* TSAO.framework */; 132 | productType = "com.apple.product-type.framework"; 133 | }; 134 | 0A1A09551C0D414C00A91E4B /* TSAOTests */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = 0A1A09631C0D414C00A91E4B /* Build configuration list for PBXNativeTarget "TSAOTests" */; 137 | buildPhases = ( 138 | 0A1A09521C0D414C00A91E4B /* Sources */, 139 | 0A1A09531C0D414C00A91E4B /* Frameworks */, 140 | 0A1A09541C0D414C00A91E4B /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | 0A1A09591C0D414C00A91E4B /* PBXTargetDependency */, 146 | ); 147 | name = TSAOTests; 148 | productName = TSAOTests; 149 | productReference = 0A1A09561C0D414C00A91E4B /* TSAOTests.xctest */; 150 | productType = "com.apple.product-type.bundle.unit-test"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 0A1A09431C0D414C00A91E4B /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastSwiftUpdateCheck = 0710; 159 | LastUpgradeCheck = 1020; 160 | ORGANIZATIONNAME = "Lily Ballard"; 161 | TargetAttributes = { 162 | 0A1A094B1C0D414C00A91E4B = { 163 | CreatedOnToolsVersion = 7.1.1; 164 | LastSwiftMigration = ""; 165 | }; 166 | 0A1A09551C0D414C00A91E4B = { 167 | CreatedOnToolsVersion = 7.1.1; 168 | LastSwiftMigration = ""; 169 | }; 170 | }; 171 | }; 172 | buildConfigurationList = 0A1A09461C0D414C00A91E4B /* Build configuration list for PBXProject "TSAO" */; 173 | compatibilityVersion = "Xcode 3.2"; 174 | developmentRegion = en; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | Base, 179 | ); 180 | mainGroup = 0A1A09421C0D414C00A91E4B; 181 | productRefGroup = 0A1A094D1C0D414C00A91E4B /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | 0A1A094B1C0D414C00A91E4B /* TSAO */, 186 | 0A1A09551C0D414C00A91E4B /* TSAOTests */, 187 | ); 188 | }; 189 | /* End PBXProject section */ 190 | 191 | /* Begin PBXResourcesBuildPhase section */ 192 | 0A1A094A1C0D414C00A91E4B /* Resources */ = { 193 | isa = PBXResourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | 0A1A09541C0D414C00A91E4B /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | 0A1A09471C0D414C00A91E4B /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 0A1A09681C0D427F00A91E4B /* TSAO.swift in Sources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | 0A1A09521C0D414C00A91E4B /* Sources */ = { 218 | isa = PBXSourcesBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | 0A1A095C1C0D414C00A91E4B /* Tests.swift in Sources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXSourcesBuildPhase section */ 226 | 227 | /* Begin PBXTargetDependency section */ 228 | 0A1A09591C0D414C00A91E4B /* PBXTargetDependency */ = { 229 | isa = PBXTargetDependency; 230 | target = 0A1A094B1C0D414C00A91E4B /* TSAO */; 231 | targetProxy = 0A1A09581C0D414C00A91E4B /* PBXContainerItemProxy */; 232 | }; 233 | /* End PBXTargetDependency section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | 0A1A095E1C0D414C00A91E4B /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | APPLICATION_EXTENSION_API_ONLY = YES; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = dwarf; 267 | ENABLE_STRICT_OBJC_MSGSEND = YES; 268 | ENABLE_TESTABILITY = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_DYNAMIC_NO_PIC = NO; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_OPTIMIZATION_LEVEL = 0; 273 | GCC_PREPROCESSOR_DEFINITIONS = ( 274 | "DEBUG=1", 275 | "$(inherited)", 276 | ); 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 284 | MACOSX_DEPLOYMENT_TARGET = 10.9; 285 | MTL_ENABLE_DEBUG_INFO = YES; 286 | ONLY_ACTIVE_ARCH = YES; 287 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchsimulator watchos appletvsimulator appletvos"; 288 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 289 | TVOS_DEPLOYMENT_TARGET = 9.0; 290 | VERSIONING_SYSTEM = ""; 291 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 292 | }; 293 | name = Debug; 294 | }; 295 | 0A1A095F1C0D414C00A91E4B /* Release */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | APPLICATION_EXTENSION_API_ONLY = YES; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 324 | COPY_PHASE_STRIP = NO; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 337 | MACOSX_DEPLOYMENT_TARGET = 10.9; 338 | MTL_ENABLE_DEBUG_INFO = NO; 339 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchsimulator watchos appletvsimulator appletvos"; 340 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 341 | TVOS_DEPLOYMENT_TARGET = 9.0; 342 | VALIDATE_PRODUCT = YES; 343 | VERSIONING_SYSTEM = ""; 344 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 345 | }; 346 | name = Release; 347 | }; 348 | 0A1A09611C0D414C00A91E4B /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | CLANG_ENABLE_MODULES = YES; 352 | COMBINE_HIDPI_IMAGES = YES; 353 | DEFINES_MODULE = YES; 354 | DYLIB_COMPATIBILITY_VERSION = 1; 355 | DYLIB_CURRENT_VERSION = 1; 356 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 357 | INFOPLIST_FILE = Info.plist; 358 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.tildesoft.TSAO; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SKIP_INSTALL = YES; 363 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 364 | SWIFT_VERSION = 4.0; 365 | }; 366 | name = Debug; 367 | }; 368 | 0A1A09621C0D414C00A91E4B /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | CLANG_ENABLE_MODULES = YES; 372 | COMBINE_HIDPI_IMAGES = YES; 373 | DEFINES_MODULE = YES; 374 | DYLIB_COMPATIBILITY_VERSION = 1; 375 | DYLIB_CURRENT_VERSION = 1; 376 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 377 | INFOPLIST_FILE = Info.plist; 378 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 380 | PRODUCT_BUNDLE_IDENTIFIER = com.tildesoft.TSAO; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SKIP_INSTALL = YES; 383 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 384 | SWIFT_VERSION = 4.0; 385 | }; 386 | name = Release; 387 | }; 388 | 0A1A09641C0D414C00A91E4B /* Debug */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | APPLICATION_EXTENSION_API_ONLY = NO; 392 | COMBINE_HIDPI_IMAGES = YES; 393 | INFOPLIST_FILE = Tests/TSAOTests/Info.plist; 394 | "LD_RUNPATH_SEARCH_PATHS[arch=*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 395 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = com.tildesoft.TSAOTests; 397 | PRODUCT_NAME = "$(TARGET_NAME)"; 398 | SWIFT_VERSION = 4.0; 399 | }; 400 | name = Debug; 401 | }; 402 | 0A1A09651C0D414C00A91E4B /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | APPLICATION_EXTENSION_API_ONLY = NO; 406 | COMBINE_HIDPI_IMAGES = YES; 407 | INFOPLIST_FILE = Tests/TSAOTests/Info.plist; 408 | "LD_RUNPATH_SEARCH_PATHS[arch=*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 409 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 410 | PRODUCT_BUNDLE_IDENTIFIER = com.tildesoft.TSAOTests; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 413 | SWIFT_VERSION = 4.0; 414 | }; 415 | name = Release; 416 | }; 417 | /* End XCBuildConfiguration section */ 418 | 419 | /* Begin XCConfigurationList section */ 420 | 0A1A09461C0D414C00A91E4B /* Build configuration list for PBXProject "TSAO" */ = { 421 | isa = XCConfigurationList; 422 | buildConfigurations = ( 423 | 0A1A095E1C0D414C00A91E4B /* Debug */, 424 | 0A1A095F1C0D414C00A91E4B /* Release */, 425 | ); 426 | defaultConfigurationIsVisible = 0; 427 | defaultConfigurationName = Release; 428 | }; 429 | 0A1A09601C0D414C00A91E4B /* Build configuration list for PBXNativeTarget "TSAO" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | 0A1A09611C0D414C00A91E4B /* Debug */, 433 | 0A1A09621C0D414C00A91E4B /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | 0A1A09631C0D414C00A91E4B /* Build configuration list for PBXNativeTarget "TSAOTests" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 0A1A09641C0D414C00A91E4B /* Debug */, 442 | 0A1A09651C0D414C00A91E4B /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = 0A1A09431C0D414C00A91E4B /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /TSAO.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TSAO.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TSAO.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TSAO.xcodeproj/xcshareddata/xcschemes/TSAO.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 | -------------------------------------------------------------------------------- /Tests/TSAOTests/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 | 3.0.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/TSAOTests/Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TSAOTests.swift 3 | // TSAOTests 4 | // 5 | // Created by Lily Ballard on 11/30/15. 6 | // Copyright © 2015 Lily Ballard. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TSAO 11 | 12 | let intMap = AssocMap() 13 | let strMap = AssocMap(copyAtomic: false) 14 | let aryMap = AssocMap<[String]>() 15 | let tupleMap = AssocMap<(Int, CGRect)>() 16 | 17 | let objectRetainMap = AssocMap() 18 | let objectAssignMap = AssocMap(assign: ()) 19 | 20 | class TSAOTests: XCTestCase { 21 | var helper: NSObject! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | helper = NSObject() 26 | } 27 | 28 | override func tearDown() { 29 | helper = nil 30 | super.tearDown() 31 | } 32 | 33 | func testSettingAssociatedObjects() { 34 | intMap[helper] = 42 35 | strMap[helper] = "this is an NSString" 36 | aryMap[helper] = ["array", "of", "String", "values"] 37 | 38 | XCTAssertEqual(intMap[helper], 42) 39 | XCTAssertEqual(strMap[helper], "this is an NSString") 40 | XCTAssertEqual(aryMap[helper] ?? [], ["array", "of", "String", "values"]) 41 | } 42 | 43 | func testUnbridgeableValue() { 44 | let rect = CGRect(x: 5, y: 10, width: 15, height: 20) 45 | tupleMap[helper] = (5, rect) 46 | if let value = tupleMap[helper] { 47 | XCTAssertEqual(value.0, 5) 48 | XCTAssertEqual(value.1, rect) 49 | } else { 50 | XCTFail("tupleMap[helper] returned nil") 51 | } 52 | } 53 | 54 | func testOverwritingAssociatedObjects() { 55 | intMap[helper] = 42 56 | XCTAssertEqual(intMap[helper], 42) 57 | intMap[helper] = 1 58 | XCTAssertEqual(intMap[helper], 1) 59 | intMap[helper] = nil 60 | XCTAssertNil(intMap[helper]) 61 | } 62 | 63 | func testGettingUnsetAssociatedObject() { 64 | XCTAssertNil(intMap[helper]) 65 | intMap[helper] = 42 66 | XCTAssertEqual(intMap[helper], 42) 67 | intMap[helper] = nil 68 | XCTAssertNil(intMap[helper]) 69 | } 70 | 71 | func testCopyingAssociatedValue() { 72 | let s = NSMutableString(string: "mutable string") 73 | strMap[helper] = s 74 | s.append("was changed") 75 | XCTAssertEqual(strMap[helper], "mutable string") 76 | } 77 | 78 | func testReleaseValue() { 79 | weak var object: NSObject? 80 | autoreleasepool { 81 | let obj = NSObject() 82 | objectRetainMap[helper] = obj 83 | object = obj 84 | } 85 | autoreleasepool { 86 | XCTAssertNotNil(object) 87 | } 88 | autoreleasepool { 89 | objectRetainMap[helper] = nil 90 | } 91 | XCTAssertNil(object) 92 | } 93 | 94 | func testAssignValue() { 95 | weak var object: NSObject? 96 | autoreleasepool { 97 | let obj = NSObject() 98 | objectAssignMap[helper] = obj 99 | object = obj 100 | } 101 | XCTAssertNil(object) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /swift-tsao.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "swift-tsao" 3 | s.version = "3.0.2" 4 | s.summary = "Type-Safe Associated Objects in Swift" 5 | s.description = <<-DESC 6 | TSAO is an implementation of type-safe associated objects in Swift. 7 | Objective-C associated objects are useful, but they are also untyped; 8 | every associated object is only known to be id at compile-time and 9 | clients must either test the class at runtime or rely on it being the 10 | expected type. 11 | DESC 12 | 13 | s.homepage = "https://github.com/lilyball/swift-tsao" 14 | s.license = { :type => "MIT", :file => "LICENSE.txt" } 15 | s.author = { "Lily Ballard" => "lily@sb.org" } 16 | 17 | s.ios.deployment_target = "8.0" 18 | s.osx.deployment_target = "10.9" 19 | s.watchos.deployment_target = "2.0" 20 | s.tvos.deployment_target = "9.0" 21 | 22 | s.swift_version = '4.0' 23 | 24 | s.source = { :git => "https://github.com/lilyball/swift-tsao.git", :tag => "v#{s.version}" } 25 | s.source_files = "Sources/*.swift" 26 | end 27 | --------------------------------------------------------------------------------