Skip to content

Commit e978a10

Browse files
committed
0 parents  commit e978a10

20 files changed

+1500
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
Package.resolved

ACKNOWLEDGMENTS

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Inspiration and some implementations are taken from:
2+
3+
––––––––––––––––––––––––––––––
4+
https://github.com/ReactiveCocoa/ReactiveSwift
5+
6+
LICENCE
7+
------------------------------
8+
Copyright (c) 2012 - 2016, GitHub, Inc. All rights reserved.
9+
10+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15+
––––––––––––––––––––––––––––––
16+
17+
––––––––––––––––––––––––––––––
18+
https://github.com/ReactiveCocoa/ReactiveCocoa
19+
20+
LICENCE
21+
------------------------------
22+
Copyright (c) 2012 - 2016, GitHub, Inc. All rights reserved.
23+
24+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
25+
26+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
27+
28+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29+
––––––––––––––––––––––––––––––
30+
31+
––––––––––––––––––––––––––––––
32+
https://github.com/CombineCommunity/CombineExt
33+
34+
LICENCE
35+
------------------------------
36+
Copyright (c) 2020 Combine Community, and/or Shai Mishali
37+
38+
Permission is hereby granted, free of charge, to any person obtaining a copy
39+
of this software and associated documentation files (the "Software"), to deal
40+
in the Software without restriction, including without limitation the rights
41+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42+
copies of the Software, and to permit persons to whom the Software is
43+
furnished to do so, subject to the following conditions:
44+
45+
The above copyright notice and this permission notice shall be included in
46+
all copies or substantial portions of the Software.
47+
48+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54+
THE SOFTWARE.
55+
––––––––––––––––––––––––––––––
56+
57+
––––––––––––––––––––––––––––––
58+
https://github.com/CombineCommunity/CombineCocoa
59+
60+
LICENCE
61+
------------------------------
62+
MIT License
63+
64+
Copyright (c) 2019 Shai Mishali
65+
66+
Permission is hereby granted, free of charge, to any person obtaining a copy
67+
of this software and associated documentation files (the "Software"), to deal
68+
in the Software without restriction, including without limitation the rights
69+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
70+
copies of the Software, and to permit persons to whom the Software is
71+
furnished to do so, subject to the following conditions:
72+
73+
The above copyright notice and this permission notice shall be included in all
74+
copies or substantial portions of the Software.
75+
76+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
81+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
82+
SOFTWARE.
83+
––––––––––––––––––––––––––––––

LICENCE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 CaptureContext
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.

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
format:
2+
swift format --in-place --recursive \
3+
./Package.swift ./Sources/CombineExtensions

Package.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// swift-tools-version:5.8
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "combine-interception",
7+
platforms: [
8+
.iOS(.v13),
9+
.macOS(.v10_15),
10+
.tvOS(.v13),
11+
.watchOS(.v6)
12+
],
13+
products: [
14+
.library(
15+
name: "CombineInterception",
16+
type: .static,
17+
targets: ["CombineInterception"]
18+
)
19+
],
20+
targets: [
21+
.target(
22+
name: "CombineInterception",
23+
dependencies: [
24+
.target(name: "CombineRuntime")
25+
]
26+
),
27+
.target(name: "CombineRuntime")
28+
]
29+
)

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# combine-extensions
2+
3+
[![SwiftPM 5.3](https://img.shields.io/badge/swiftpm-5.3-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/Platforms-iOS_13_|_macOS_10.15_|_tvOS_14_|_watchOS_7-ED523F.svg?style=flat) [![@maximkrouk](https://img.shields.io/badge/[email protected]?style=flat&logo=twitter)](https://twitter.com/capture_context)
4+
5+
Package extending Apple' `Combine` framework for interception of objc selectors.
6+
7+
## Installation
8+
9+
### Basic
10+
11+
You can add CombineInterception to an Xcode project by adding it as a package dependency.
12+
13+
1. From the **File** menu, select **Swift Packages › Add Package Dependency…**
14+
2. Enter [`"https://github.com/capturecontext/combine-interception.git"`](https://github.com/capturecontext/combine-interception.git) into the package repository URL text field
15+
3. Choose products you need to link them to your project.
16+
17+
### Recommended
18+
19+
If you use SwiftPM for your project, you can add CombineInterception to your package file.
20+
21+
```swift
22+
.package(
23+
url: "https://github.com/capturecontext/combine-interception.git",
24+
.upToNextMinor(from: "0.0.1")
25+
)
26+
```
27+
28+
Do not forget about target dependencies:
29+
30+
```swift
31+
.product(
32+
name: "CombineInterception",
33+
package: "combine-interception"
34+
)
35+
```
36+
37+
## Usage
38+
39+
### Basic
40+
41+
Observe any selectors on NSObject instances
42+
43+
```swift
44+
navigationController
45+
.publisher(for: #selector(UINavigationController.popViewController))
46+
```
47+
48+
### Library
49+
50+
If you use it to create a library it may be a good idea to export this one implicitly
51+
52+
```swift
53+
// Exports.swift
54+
@_exported import CombineInterception
55+
```
56+
57+
## License
58+
59+
This library is released under the MIT license. See [LICENSE](LICENSE) for details.
60+
61+
See [ACKNOWLEDGMENTS][ACKNOWLEDGMENTS] for inspiration references and their licences.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import Foundation
2+
3+
#if canImport(CombineRuntime)
4+
import CombineRuntime
5+
#endif
6+
7+
internal struct AssociationKey<Value> {
8+
fileprivate let address: UnsafeRawPointer
9+
fileprivate let `default`: Value!
10+
11+
/// Create an ObjC association key.
12+
///
13+
/// - warning: The key must be uniqued.
14+
///
15+
/// - parameters:
16+
/// - default: The default value, or `nil` to trap on undefined value. It is
17+
/// ignored if `Value` is an optional.
18+
init(default: Value? = nil) {
19+
self.address = UnsafeRawPointer(
20+
UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
21+
)
22+
self.default = `default`
23+
}
24+
25+
/// Create an ObjC association key from a `StaticString`.
26+
///
27+
/// - precondition: `key` has a pointer representation.
28+
///
29+
/// - parameters:
30+
/// - default: The default value, or `nil` to trap on undefined value. It is
31+
/// ignored if `Value` is an optional.
32+
init(_ key: StaticString, default: Value? = nil) {
33+
assert(key.hasPointerRepresentation)
34+
self.address = UnsafeRawPointer(key.utf8Start)
35+
self.default = `default`
36+
}
37+
38+
/// Create an ObjC association key from a `Selector`.
39+
///
40+
/// - parameters:
41+
/// - default: The default value, or `nil` to trap on undefined value. It is
42+
/// ignored if `Value` is an optional.
43+
init(_ key: Selector, default: Value? = nil) {
44+
self.address = UnsafeRawPointer(key.utf8Start)
45+
self.default = `default`
46+
}
47+
}
48+
49+
internal struct Associations<Base: AnyObject> {
50+
fileprivate let base: Base
51+
52+
init(_ base: Base) {
53+
self.base = base
54+
}
55+
}
56+
57+
extension NSObjectProtocol {
58+
/// Retrieve the associated value for the specified key. If the value does not
59+
/// exist, `initial` would be called and the returned value would be
60+
/// associated subsequently.
61+
///
62+
/// - parameters:
63+
/// - key: An optional key to differentiate different values.
64+
/// - initial: The action that supples an initial value.
65+
///
66+
/// - returns: The associated value for the specified key.
67+
internal func associatedValue<T>(
68+
forKey key: StaticString = #function,
69+
initial: (Self) -> T
70+
) -> T {
71+
let key = AssociationKey<T?>(key)
72+
73+
if let value = associations.value(forKey: key) {
74+
return value
75+
}
76+
77+
let value = initial(self)
78+
associations.setValue(value, forKey: key)
79+
80+
return value
81+
}
82+
}
83+
84+
extension NSObjectProtocol {
85+
@nonobjc internal var associations: Associations<Self> {
86+
return Associations(self)
87+
}
88+
}
89+
90+
extension Associations {
91+
/// Retrieve the associated value for the specified key.
92+
///
93+
/// - parameters:
94+
/// - key: The key.
95+
///
96+
/// - returns: The associated value, or the default value if no value has been
97+
/// associated with the key.
98+
internal func value<Value>(
99+
forKey key: AssociationKey<Value>
100+
) -> Value {
101+
return (objc_getAssociatedObject(base, key.address) as! Value?) ?? key.default
102+
}
103+
104+
/// Retrieve the associated value for the specified key.
105+
///
106+
/// - parameters:
107+
/// - key: The key.
108+
///
109+
/// - returns: The associated value, or `nil` if no value is associated with
110+
/// the key.
111+
internal func value<Value>(
112+
forKey key: AssociationKey<Value?>
113+
) -> Value? {
114+
return objc_getAssociatedObject(base, key.address) as! Value?
115+
}
116+
117+
/// Set the associated value for the specified key.
118+
///
119+
/// - parameters:
120+
/// - value: The value to be associated.
121+
/// - key: The key.
122+
internal func setValue<Value>(
123+
_ value: Value,
124+
forKey key: AssociationKey<Value>
125+
) {
126+
objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
127+
}
128+
129+
/// Set the associated value for the specified key.
130+
///
131+
/// - parameters:
132+
/// - value: The value to be associated.
133+
/// - key: The key.
134+
internal func setValue<Value>(
135+
_ value: Value?,
136+
forKey key: AssociationKey<Value?>
137+
) {
138+
objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
139+
}
140+
}
141+
142+
/// Set the associated value for the specified key.
143+
///
144+
/// - parameters:
145+
/// - value: The value to be associated.
146+
/// - key: The key.
147+
/// - address: The address of the object.
148+
internal func unsafeSetAssociatedValue<Value>(
149+
_ value: Value?,
150+
forKey key: AssociationKey<Value>,
151+
forObjectAt address: UnsafeRawPointer
152+
) {
153+
_combineRuntimeSetAssociatedObject(
154+
address,
155+
key.address,
156+
value,
157+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
158+
)
159+
}

0 commit comments

Comments
 (0)