Skip to content

Commit 5697490

Browse files
runnerrunner
authored andcommitted
Squashed 'apollo-ios/' changes from f304227..7349dd8
7349dd8 Handle coercion of AnyHashable (#61) git-subtree-dir: apollo-ios git-subtree-split: 7349dd8
1 parent bf47a2b commit 5697490

File tree

3 files changed

+69
-33
lines changed

3 files changed

+69
-33
lines changed

Sources/Apollo/GraphQLSelectionSetMapper.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ final class GraphQLSelectionSetMapper<T: SelectionSet>: GraphQLResultAccumulator
4545
}
4646

4747
func acceptNullValue(info: FieldExecutionInfo) -> AnyHashable? {
48-
return DataDict.NullValue
48+
return DataDict._NullValue
4949
}
5050

5151
func acceptMissingValue(info: FieldExecutionInfo) throws -> AnyHashable? {
@@ -86,11 +86,3 @@ final class GraphQLSelectionSetMapper<T: SelectionSet>: GraphQLResultAccumulator
8686
return T.init(_dataDict: rootValue)
8787
}
8888
}
89-
90-
// MARK: - Null Value Definition
91-
extension DataDict {
92-
/// A common value used to represent a null value in a `DataDict`.
93-
///
94-
/// This value can be cast to `NSNull` and will bridge automatically.
95-
static let NullValue = AnyHashable(Optional<AnyHashable>.none)
96-
}

Sources/ApolloAPI/DataDict.swift

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Foundation
2+
13
/// A structure that wraps the underlying data for a ``SelectionSet``.
24
public struct DataDict: Hashable {
35
@usableFromInline var _storage: _Storage
@@ -51,11 +53,16 @@ public struct DataDict: Hashable {
5153

5254
@inlinable public subscript<T: AnyScalarType & Hashable>(_ key: String) -> T {
5355
get {
54-
#if swift(>=5.4)
55-
_data[key] as! T
56-
#else
57-
_data[key]?.base as! T
58-
#endif
56+
if DataDict._AnyHashableCanBeCoerced {
57+
return _data[key] as! T
58+
} else {
59+
let value = _data[key]
60+
if value == DataDict._NullValue {
61+
return (Optional<T>.none as Any) as! T
62+
} else {
63+
return (value?.base as? T) ?? (value._asAnyHashable as! T)
64+
}
65+
}
5966
}
6067
set {
6168
_data[key] = newValue
@@ -126,6 +133,34 @@ public struct DataDict: Hashable {
126133
}
127134
}
128135

136+
// MARK: - Null Value Definition
137+
extension DataDict {
138+
/// A common value used to represent a null value in a `DataDict`.
139+
///
140+
/// This value can be cast to `NSNull` and will bridge automatically.
141+
public static let _NullValue = {
142+
if DataDict._AnyHashableCanBeCoerced {
143+
return AnyHashable(Optional<AnyHashable>.none)
144+
} else {
145+
return NSNull()
146+
}
147+
}()
148+
149+
/// Indicates if `AnyHashable` can be coerced via casting into its underlying type.
150+
///
151+
/// In iOS versions 14.4 and lower, `AnyHashable` coercion does not work. On these platforms,
152+
/// we need to do some additional unwrapping and casting of the values to avoid crashes and other
153+
/// run time bugs.
154+
public static var _AnyHashableCanBeCoerced: Bool {
155+
if #available(iOS 14.5, *) {
156+
return true
157+
} else {
158+
return false
159+
}
160+
}
161+
162+
}
163+
129164
// MARK: - Value Conversion Helpers
130165

131166
public protocol SelectionSetEntityValue {
@@ -160,11 +195,13 @@ extension Optional: SelectionSetEntityValue where Wrapped: SelectionSetEntityVal
160195
case .none:
161196
self = .none
162197
case .some(let hashable):
163-
if let optional = hashable.base as? Optional<AnyHashable>, optional == nil {
164-
self = .none
165-
return
166-
}
198+
if DataDict._AnyHashableCanBeCoerced && hashable == DataDict._NullValue {
199+
self = .none
200+
} else if let optional = hashable.base as? Optional<AnyHashable>, optional == nil {
201+
self = .none
202+
} else {
167203
self = .some(Wrapped.init(_fieldData: data))
204+
}
168205
}
169206
}
170207

@@ -179,11 +216,11 @@ extension Array: SelectionSetEntityValue where Element: SelectionSetEntityValue
179216
fatalError("\(Self.self) expected list of data for entity.")
180217
}
181218
self = data.map {
182-
#if swift(>=5.4)
183-
Element.init(_fieldData:$0)
184-
#else
185-
Element.init(_fieldData:$0?.base as? AnyHashable)
186-
#endif
219+
if DataDict._AnyHashableCanBeCoerced {
220+
return Element.init(_fieldData:$0)
221+
} else {
222+
return Element.init(_fieldData:$0?.base as? AnyHashable)
223+
}
187224
}
188225
}
189226

Sources/ApolloTestSupport/TestMock.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public class Mock<O: MockObject>: AnyMock, Hashable {
9797

9898
public var _selectionSetMockData: JSONObject {
9999
_data.mapValues {
100-
if let mock = $0 as? AnyMock {
100+
if let mock = $0.base as? AnyMock {
101101
return mock._selectionSetMockData
102102
}
103103
if let mockArray = $0 as? Array<Any> {
@@ -188,20 +188,27 @@ fileprivate extension Array {
188188
}
189189

190190
func _unsafelyConvertToSelectionSetData() -> [AnyHashable?] {
191-
map { element in
192-
switch element {
193-
case let element as AnyMock:
194-
return element._selectionSetMockData
191+
map(_unsafelyConvertToSelectionSetData(element:))
192+
}
195193

196-
case let innerArray as Array<Any>:
197-
return innerArray._unsafelyConvertToSelectionSetData()
194+
private func _unsafelyConvertToSelectionSetData(element: Any) -> AnyHashable? {
195+
switch element {
196+
case let element as AnyMock:
197+
return element._selectionSetMockData
198198

199-
case let element as AnyHashable:
199+
case let innerArray as Array<Any>:
200+
return innerArray._unsafelyConvertToSelectionSetData()
201+
202+
case let element as AnyHashable:
203+
if DataDict._AnyHashableCanBeCoerced {
200204
return element
201205

202-
default:
203-
return nil
206+
} else {
207+
return _unsafelyConvertToSelectionSetData(element: element.base)
204208
}
209+
210+
default:
211+
return nil
205212
}
206213
}
207214
}

0 commit comments

Comments
 (0)