Files
GoogleUtilities/GoogleUtilities.podspec
T
Paul BeusterienandNick Cooke 537b79836b Log ignored GULMutableDictionary writes and document nil handling (#251)
* fix(network): log ignored GULMutableDictionary writes and document nil handling

Follow-up to the nil-key fix in #249.

- Log a warning (I-NET902000 for a nil key, I-NET902001 for a nil object)
  when a write is ignored. Silently dropping the write hid the caller's bug;
  the log keeps it visible without crashing.
- Mark the object parameter of -setObject:forKeyedSubscript: as nullable. A
  nil object removes the key, as with NSMutableDictionary, and the tests rely
  on this. It also lets Swift callers write `dict[key] = nil`.
- Document the nil handling in the header.
- Make testNilObjectIsIgnored check that an existing entry is kept, and pass
  nil directly in testKeyedNilObjectRemovesKey.

* fix(network): move GULMutableDictionary log codes to 903xxx

I-NET902000-I-NET902006 are already used by GULReachabilityMessageCode, so
the new GULMutableDictionary warnings collided with reachability messages.
Use 903000/903001, which are unused in GoogleUtilities and
firebase-ios-sdk, and note the reserved reachability range next to the
enum.

* add more nil key tests

* fix(network): return nil early for nil keys in GULMutableDictionary getters

NSDictionary happens to return nil for a nil key, but this is not
documented. Return early from -objectForKey: and
-objectForKeyedSubscript: so the behavior does not depend on Foundation
and matches the nil-key guards on the write paths.

- Document the nil-key behavior of both getters in the header.
- Add tests that a nil-key lookup returns nil when the dictionary has
  entries.

* test(network): add defensive GULMutableDictionary tests

- Check that -dictionary returns a snapshot that later writes don't change.
- Split the nil-key write test into one test per write path so a failure
  names the method that caused it, and cover `dict[nil] = nil`.
- Cover removing a missing key, -removeAllObjects on an empty dictionary,
  and -description.

* changelog and spec

* test(network): assert empty precondition in testRemoveAllWhenEmpty

* test(network): verify setup in GULMutableDictionary tests

Several tests could pass even if their setup silently failed. Check that
the setup took effect:

- testDictionaryReturnsSnapshot: the later writes changed the live
  dictionary, so the snapshot assertions are meaningful.
- testKeyedNilObjectRemovesKey: the entry exists before it is removed.
- testObjectForNilKey(KeyedSubscript): the entry exists, so the tests
  show a nil key does not match it.

* fix(network): mark GULMutableDictionary getters as returning nullable

objectForKey: and objectForKeyedSubscript: are documented to return nil for a missing or nil key, but were declared non-null, so Swift imported objectForKey(_:) as Any. Declare both as nullable id so the signatures match the documented behavior. Swift callers of objectForKey(_:) now get Any?; the subscript already read as Any? because its setter is nullable.

* test(network): add GULMutableDictionary concurrent access stress test

GULMutableDictionary is documented as providing atomic accessors and
mutators and is shared across threads by GULNetwork and the app delegate
swizzlers, but no test exercised it concurrently. Mix reads, writes,
removes, and nil-key calls across threads with dispatch_apply and check
that the final state is consistent.

Passed 200 repeated runs and a Thread Sanitizer run on the iOS simulator
with no reports.

---------

Co-authored-by: Nick Cooke <nickcooke@google.com>
2026-09-29 16:11:36 -07:00

169 lines
5.5 KiB
Ruby

Pod::Spec.new do |s|
s.name = 'GoogleUtilities'
s.version = '8.1.4'
s.summary = 'Google Utilities for Apple platform SDKs'
s.description = <<-DESC
Internal Google Utilities including Network, Reachability Environment, Logger and Swizzling for
other Google CocoaPods. They're not intended for direct public usage.
DESC
s.homepage = 'https://github.com/google/GoogleUtilities'
s.license = { :type => 'Apache-2.0', :file => 'LICENSE' }
s.authors = 'Google, Inc.'
s.source = {
:git => 'https://github.com/google/GoogleUtilities.git',
:tag => 'CocoaPods-' + s.version.to_s
}
ios_deployment_target = '12.0'
osx_deployment_target = '10.15'
tvos_deployment_target = '13.0'
watchos_deployment_target = '7.0'
s.ios.deployment_target = ios_deployment_target
s.osx.deployment_target = osx_deployment_target
s.tvos.deployment_target = tvos_deployment_target
s.watchos.deployment_target = watchos_deployment_target
s.cocoapods_version = '>= 1.4.0'
s.prefix_header_file = false
s.pod_target_xcconfig = {
'GCC_C_LANGUAGE_STANDARD' => 'c99',
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"',
}
s.subspec 'Privacy' do |cs|
cs.resource_bundles = {
"GoogleUtilities_Privacy" => 'GoogleUtilities/Privacy/Resources/PrivacyInfo.xcprivacy'
}
end
s.subspec 'Environment' do |es|
es.source_files = [
'GoogleUtilities/Environment/**/*.[mh]',
'third_party/IsAppEncrypted/**/*.[mh]'
]
es.public_header_files = 'GoogleUtilities/Environment/Public/GoogleUtilities/*.h'
es.dependency 'GoogleUtilities/Privacy'
es.frameworks = [
'Security'
]
end
s.subspec 'Logger' do |ls|
ls.source_files = 'GoogleUtilities/Logger/**/*.[mh]'
ls.public_header_files = 'GoogleUtilities/Logger/Public/GoogleUtilities/*.h'
ls.dependency 'GoogleUtilities/Environment'
ls.dependency 'GoogleUtilities/Privacy'
end
s.subspec 'Network' do |ns|
ns.source_files = 'GoogleUtilities/Network/**/*.[mh]'
ns.public_header_files = 'GoogleUtilities/Network/Public/GoogleUtilities/*.h'
ns.dependency 'GoogleUtilities/NSData+zlib'
ns.dependency 'GoogleUtilities/Logger'
ns.dependency 'GoogleUtilities/Reachability'
ns.dependency 'GoogleUtilities/Privacy'
ns.frameworks = [
'Security'
]
end
s.subspec 'NSData+zlib' do |ns|
ns.source_files = 'GoogleUtilities/NSData+zlib/**/*.[mh]'
ns.public_header_files = 'GoogleUtilities/NSData+zlib/Public/GoogleUtilities/*.h'
ns.dependency 'GoogleUtilities/Privacy'
ns.libraries = [
'z'
]
end
s.subspec 'Reachability' do |rs|
rs.source_files = 'GoogleUtilities/Reachability/**/*.[mh]'
rs.public_header_files = 'GoogleUtilities/Reachability/Public/GoogleUtilities/*.h'
rs.dependency 'GoogleUtilities/Privacy'
rs.ios.frameworks = [
'SystemConfiguration'
]
rs.osx.frameworks = [
'SystemConfiguration'
]
rs.tvos.frameworks = [
'SystemConfiguration'
]
rs.dependency 'GoogleUtilities/Logger'
end
s.subspec 'AppDelegateSwizzler' do |adss|
adss.source_files = [
'GoogleUtilities/AppDelegateSwizzler/Internal/*.h',
'GoogleUtilities/AppDelegateSwizzler/Public/**/*.h',
'GoogleUtilities/AppDelegateSwizzler/*.m',
'GoogleUtilities/Common/*.h',
]
adss.public_header_files = [
'GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/*.h',
]
adss.dependency 'GoogleUtilities/Privacy'
adss.dependency 'GoogleUtilities/Logger'
adss.dependency 'GoogleUtilities/Network'
adss.dependency 'GoogleUtilities/Environment'
end
s.subspec 'MethodSwizzler' do |mss|
mss.source_files = 'GoogleUtilities/MethodSwizzler/**/*.[mh]', 'GoogleUtilities/Common/*.h'
mss.public_header_files = 'GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/*.h'
mss.dependency 'GoogleUtilities/Logger'
mss.dependency 'GoogleUtilities/Privacy'
end
s.subspec 'SwizzlerTestHelpers' do |sths|
sths.source_files = 'GoogleUtilities/SwizzlerTestHelpers/**/*.[hm]'
sths.public_header_files = 'GoogleUtilities/SwizzlerTestHelpers/Public/GoogleUtilities/*.h'
sths.dependency 'GoogleUtilities/MethodSwizzler'
end
s.subspec 'UserDefaults' do |ud|
ud.source_files = 'GoogleUtilities/UserDefaults/**/*.[hm]'
ud.public_header_files = 'GoogleUtilities/UserDefaults/Public/GoogleUtilities/*.h'
ud.dependency 'GoogleUtilities/Logger'
ud.dependency 'GoogleUtilities/Privacy'
end
s.test_spec 'unit' do |unit_tests|
unit_tests.scheme = { :code_coverage => true }
unit_tests.platforms = {
:ios => ios_deployment_target,
:osx => osx_deployment_target,
:tvos => tvos_deployment_target
}
unit_tests.source_files = [
'GoogleUtilities/Tests/Unit/**/*.[mh]',
]
# All tests require arc except Tests/Network/third_party/GTMHTTPServer.m
unit_tests.requires_arc = [
'GoogleUtilities/Tests/Unit/*/*.[mh]',
'GoogleUtilities/Tests/Unit/Environment/**/*.[mh]'
]
unit_tests.requires_app_host = true
end
s.test_spec 'unit-swift' do |unit_tests_swift|
unit_tests_swift.scheme = { :code_coverage => true }
unit_tests_swift.platforms = {
:ios => ios_deployment_target,
:osx => osx_deployment_target,
:tvos => tvos_deployment_target
}
unit_tests_swift.source_files = 'GoogleUtilities/Tests/SwiftUnit/**/*.swift',
'GoogleUtilities/Tests/SwiftUnit/**/*.h'
unit_tests_swift.requires_app_host = true
end
end