Commit dfa21bff by Dmitriy Stepanets

OneWeaterCore integration

parent d0ecc49d
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......
......@@ -12,7 +12,7 @@
<key>OneWeatherNotificationServiceExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>41</integer>
<integer>44</integer>
</dict>
<key>PG (Playground) 1.xcscheme</key>
<dict>
......
......@@ -2,6 +2,9 @@
<Workspace
version = "1.0">
<FileRef
location = "group:OneWeatherCore/OneWeatherCore.xcodeproj">
</FileRef>
<FileRef
location = "group:1Weather.xcodeproj">
</FileRef>
<FileRef
......
......@@ -9,49 +9,49 @@
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
<integer>42</integer>
</dict>
<key>PG (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
<integer>43</integer>
</dict>
<key>PG (Playground) 3.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
<integer>44</integer>
</dict>
<key>PG (Playground) 4.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>10</integer>
<integer>46</integer>
</dict>
<key>PG (Playground) 5.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>11</integer>
<integer>47</integer>
</dict>
<key>PG (Playground) 6.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>12</integer>
<integer>48</integer>
</dict>
<key>PG (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>1</integer>
<integer>2</integer>
</dict>
</dict>
</dict>
......
......@@ -6,6 +6,7 @@
//
import Foundation
import OneWeatherCore
enum WdtWeatherCode: String, Codable {
// "ic_static_smoke"
......
......@@ -6,6 +6,7 @@
//
import Foundation
import OneWeatherCore
struct WdtSurfaceObservation: Codable {
public var dateTimeLocal: String? // can be nil for MICRO (partial) update
......
......@@ -7,23 +7,6 @@
import UIKit
public enum AppTheme: Int {
case light = 0
case dark
case system
var nameForEvent: String {
switch self {
case .light:
return "light"
case .dark:
return "dark"
case .system:
return "system"
}
}
}
public struct ThemeManager {
public struct Colors {
static let locationBlue = UIColor(hex: 0x1071F0)
......
......@@ -6,6 +6,8 @@
//
import UIKit
import OneWeatherCore
import Localize_Swift
class ForecastConditionView: UIView {
//Private
......
......@@ -6,6 +6,7 @@
//
import UIKit
import OneWeatherCore
protocol RadarLayerCellDelegate: AnyObject {
func cellPinButtonTouched(atLayer:RadarLayer)
......
......@@ -6,6 +6,7 @@
//
import UIKit
import OneWeatherCore
class SettingsDetailsCell: UITableViewCell {
//Private
......
......@@ -8,8 +8,8 @@
import Foundation
import FirebaseCrashlytics
final class Logger {
public final class Logger {
public var minLogLevel: LogLevel
public enum LogLevel: Int {
......@@ -20,18 +20,18 @@ final class Logger {
case error
}
static let prefix = "psm_1w"
public static let prefix = "psm_1w"
var componentName: String
init(componentName: String, minLogLevel: LogLevel = .debug) {
public init(componentName: String, minLogLevel: LogLevel = .debug) {
self.componentName = componentName
self.minLogLevel = minLogLevel
}
private let maxCharsPerMessage = 900 // The Apple limit for NSLog is 1024. Leaving some space for timestamp, etc. Not sure if they are a part of 1024, but I think they are. Also, account for our own prefix (roughly)
func log(level: LogLevel, message: String) {
public func log(level: LogLevel, message: String) {
#if DEBUG
guard level.rawValue >= minLogLevel.rawValue else {
return
......@@ -55,23 +55,23 @@ final class Logger {
#endif
}
func verbose(_ message: String) {
public func verbose(_ message: String) {
log(level: .verbose, message: message)
}
func debug(_ message: String) {
public func debug(_ message: String) {
log(level: .debug, message: message)
}
func info(_ message: String) {
public func info(_ message: String) {
log(level: .info, message: message)
}
func warning(_ message: String) {
public func warning(_ message: String) {
log(level: .warning, message: message)
}
func error(_ message: String) {
public func error(_ message: String) {
log(level: .error, message: message)
}
......
//
// MulticastDelegate.swift
// OneWeather
//
// Created by Demid Merzlyakov on 11.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import Foundation
public class MulticastDelegate <T> {
public init() {}
private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()
public func add(delegate: T) {
if delegates.contains(delegate as AnyObject) {
return
}
delegates.add(delegate as AnyObject)
}
public func remove(delegate: T) {
for oneDelegate in delegates.allObjects.reversed() {
if oneDelegate === delegate as AnyObject {
delegates.remove(oneDelegate)
}
}
}
public func invoke(invocation: (T) -> ()) {
for delegate in delegates.allObjects.reversed() {
invocation(delegate as! T)
}
}
}
public func += <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.add(delegate: right)
}
public func -= <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.remove(delegate: right)
}
......@@ -47,8 +47,8 @@ struct UserDefaultsUnitValue<T> {
}
@propertyWrapper
struct UserDefaultsBasicValue<T> {
var wrappedValue: T {
public struct UserDefaultsBasicValue<T> {
public var wrappedValue: T {
get {
let value = UserDefaults.standard.value(forKey: key) as? T
return value ?? defaultValue
......
......@@ -7,11 +7,11 @@
import Foundation
extension MeasurementFormatter {
public extension MeasurementFormatter {
fileprivate static let oneWeatherSharedFormatter = MeasurementFormatter()
}
extension Measurement {
public extension Measurement {
private static func formatter(style: Formatter.UnitStyle, unitOptions: MeasurementFormatter.UnitOptions = .providedUnit) -> MeasurementFormatter {
let fmt = MeasurementFormatter.oneWeatherSharedFormatter
fmt.locale = Settings.shared.locale
......@@ -34,7 +34,7 @@ extension Measurement {
}
}
extension Temperature {
public extension Temperature {
var settingsConvertedValue:Double {
return self.converted(to: Settings.shared.temperatureType).value.rounded(.down)
}
......@@ -48,7 +48,7 @@ extension Temperature {
}
}
extension WindSpeed {
public extension WindSpeed {
var settingsConvertedValue:Double {
return self.converted(to: Settings.shared.windSpeedType).value.rounded(.down)
}
......@@ -58,7 +58,7 @@ extension WindSpeed {
}
}
extension Visibility {
public extension Visibility {
var settingsConvertedValue:Double {
return self.converted(to: Settings.shared.distanceType).value.rounded(.down)
}
......@@ -68,7 +68,7 @@ extension Visibility {
}
}
extension Pressure {
public extension Pressure {
var settingsConvertedValue:Double {
return self.converted(to: Settings.shared.pressureType).value.rounded(.down)
}
......
......@@ -7,7 +7,7 @@
import UIKit
extension UIColor {
public extension UIColor {
convenience init(hex: Int) {
let components = (
R: CGFloat((hex >> 16) & 0xff) / 255,
......@@ -33,7 +33,7 @@ extension UIColor {
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(255 * alpha) / 255)
}
public func toHex(alpha: Bool = false) -> String? {
func toHex(alpha: Bool = false) -> String? {
guard let components = cgColor.components, components.count >= 3 else {
return nil
}
......
......@@ -12,7 +12,7 @@ public enum AppInterfaceStyle {
case dark
}
extension UIView {
public extension UIView {
var interfaceStyle:AppInterfaceStyle {
switch Settings.shared.appTheme {
case .light:
......
......@@ -7,6 +7,7 @@
import Foundation
import UIKit
import Localize_Swift
public enum WeatherType: String, CaseIterable {
case clear = "clear"
......@@ -59,7 +60,7 @@ public enum WeatherConditionType: CaseIterable {
case visibility
case wind
var localized:String {
public var localized:String {
switch self {
case .precipitation:
return "condition.precipitation".localized()
......@@ -78,7 +79,7 @@ public enum WeatherConditionType: CaseIterable {
}
}
var image:UIImage? {
public var image:UIImage? {
switch self {
case .precipitation:
return UIImage(named: "precipitation")
......@@ -116,7 +117,7 @@ public enum WindDirection: String, Codable, CaseIterable {
case northWest = "NW"
case northNorthWest = "NNW"
var degrees:CGFloat {
public var degrees:CGFloat {
switch self {
case .north:
return 0
......@@ -153,7 +154,7 @@ public enum WindDirection: String, Codable, CaseIterable {
}
}
var fullLocalized: String {
public var fullLocalized: String {
return ("wind.direction." + self.rawValue).localized()
}
}
......@@ -254,7 +255,7 @@ public enum MoonPhase: String, Codable {
case waningCrescentMoon = "Waning Crescent Moon"
case unknown = ""
var localized:String {
public var localized:String {
switch self {
case .newMoon:
return "moon.phase.new".localized()
......@@ -277,7 +278,7 @@ public enum MoonPhase: String, Codable {
}
}
var image:UIImage? {
public var image:UIImage? {
switch self {
case .newMoon:
return UIImage(named: "moon_new")
......@@ -300,7 +301,7 @@ public enum MoonPhase: String, Codable {
}
}
var pathImage:UIImage? {
public var pathImage:UIImage? {
switch self {
case .newMoon:
return UIImage(named: "moon_path_new")
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
......@@ -61,7 +61,7 @@ final class GeoNamesPlace: NSObject {
sb.append(", ")
}
if (sb.count > 0) {
sb = sb.trim()
sb = sb.trimmingCharacters(in: CharacterSet.whitespaces)
}
return sb
}
......
......@@ -7,7 +7,7 @@
import UIKit
struct RadarLayer {
var pinned = false
let layer:RadarLayerType
public struct RadarLayer {
public var pinned = false
public let layer:RadarLayerType
}
......@@ -7,7 +7,7 @@
import UIKit
protocol RadarLayerType {
public protocol RadarLayerType {
var id:String { get }
var name:String { get }
var values:[String] { get }
......
......@@ -21,15 +21,15 @@ public enum SevereLayerType:String, CaseIterable, RadarLayerType {
case wind = "radar.severeLayer.wind"
case winter = "radar.severeLayer.winter"
var id:String {
public var id:String {
return self.rawValue
}
var name:String {
public var name:String {
return self.rawValue.localized()
}
var swarmLayer:SwarmGroup {
public var swarmLayer:SwarmGroup {
switch self {
case .fire:
return .fire
......@@ -56,7 +56,7 @@ public enum SevereLayerType:String, CaseIterable, RadarLayerType {
}
}
var images:[UIImage?] {
public var images:[UIImage?] {
if self == .hurricaneTropicalTracks {
return [UIImage(named: "severe_forecastTrack"),
UIImage(named: "severe_pastTrack"),
......@@ -70,7 +70,7 @@ public enum SevereLayerType:String, CaseIterable, RadarLayerType {
return []
}
var values:[String] {
public var values:[String] {
switch self {
case .fire:
return ["Watch", "Warning"]
......@@ -97,7 +97,7 @@ public enum SevereLayerType:String, CaseIterable, RadarLayerType {
}
}
var colors:[CGColor] {
public var colors:[CGColor] {
switch self {
case .fire:
return [UIColor(hex: 0xFFDDAD),
......
......@@ -17,15 +17,15 @@ public enum WeatherLayerType:String, CaseIterable, RadarLayerType {
case windSpeed = "radar.weatherLayer.windSpeed"
case uvIndex = "radar.weatherLayer.uvIndex"
var id:String {
public var id:String {
return self.rawValue
}
var name:String {
public var name:String {
return self.rawValue.localized()
}
var swarmLayer:SwarmBaseLayer {
public var swarmLayer:SwarmBaseLayer {
switch self {
case .radar:
return .radar
......@@ -44,7 +44,7 @@ public enum WeatherLayerType:String, CaseIterable, RadarLayerType {
}
}
var values:[String] {
public var values:[String] {
switch self {
case .radar:
return ["Rain" , "Mixed", "Snow"]
......@@ -63,7 +63,7 @@ public enum WeatherLayerType:String, CaseIterable, RadarLayerType {
}
}
var colors:[CGColor] {
public var colors:[CGColor] {
switch self {
case .radar:
return [UIColor.red, UIColor.black].map{$0.cgColor}
......
......@@ -38,6 +38,35 @@ public struct CurrentWeather: Equatable, Hashable {
public var approximateMoonrise: Date?
public var moonState: CelestialState? = .normal
public var moonPhase: MoonPhase? = .unknown
public init(lastTimeUpdated: Date, date: Date, timeZone: TimeZone, weekDay: WeekDay, type: WeatherType = .unknown, isDay: Bool, uv: Int? = nil, minTemp: Temperature? = nil, maxTemp: Temperature? = nil, windSpeed: WindSpeed? = nil, windDirection: WindDirection? = nil, precipitationProbability: Percent? = nil, temp: Temperature? = nil, dewPoint: Temperature? = nil, apparentTemp: Temperature? = nil, humidity: Percent? = nil, visibility: Visibility? = nil, pressure: Pressure? = nil, sunrise: Date? = nil, sunset: Date? = nil, sunState: CelestialState? = .normal, moonrise: Date? = nil, moonset: Date? = nil, approximateMoonrise: Date? = nil, moonState: CelestialState? = .normal, moonPhase: MoonPhase? = .unknown) {
self.lastTimeUpdated = lastTimeUpdated
self.date = date
self.timeZone = timeZone
self.weekDay = weekDay
self.type = type
self.isDay = isDay
self.uv = uv
self.minTemp = minTemp
self.maxTemp = maxTemp
self.windSpeed = windSpeed
self.windDirection = windDirection
self.precipitationProbability = precipitationProbability
self.temp = temp
self.dewPoint = dewPoint
self.apparentTemp = apparentTemp
self.humidity = humidity
self.visibility = visibility
self.pressure = pressure
self.sunrise = sunrise
self.sunset = sunset
self.sunState = sunState
self.moonrise = moonrise
self.moonset = moonset
self.approximateMoonrise = approximateMoonrise
self.moonState = moonState
self.moonPhase = moonPhase
}
}
extension CurrentWeather: UpdatableModelObjectInTime {
......
//
// OneWeatherCore.h
// OneWeatherCore
//
// Created by Dmitry Stepanets on 19.05.2021.
//
#import <Foundation/Foundation.h>
//! Project version number for OneWeatherCore.
FOUNDATION_EXPORT double OneWeatherCoreVersionNumber;
//! Project version string for OneWeatherCore.
FOUNDATION_EXPORT const unsigned char OneWeatherCoreVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <OneWeatherCore/PublicHeader.h>
......@@ -12,7 +12,24 @@ protocol SettingsDelegate: AnyObject {
func settingsDidChange()
}
class Settings {
public enum AppTheme: Int {
case light = 0
case dark
case system
var nameForEvent: String {
switch self {
case .light:
return "light"
case .dark:
return "dark"
case .system:
return "system"
}
}
}
public class Settings {
static let shared = Settings()
let delegate = MulticastDelegate<SettingsDelegate>()
private init() {}
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
//
// OneWeatherCoreTests.swift
// OneWeatherCoreTests
//
// Created by Dmitry Stepanets on 19.05.2021.
//
import XCTest
@testable import OneWeatherCore
class OneWeatherCoreTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
# Uncomment the next line to define a global platform for your project
platform :ios, '11.0'
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
workspace '1Weather'
#Shared pods
def shared_pods
pod 'Localize-Swift'
pod 'Firebase/Crashlytics'
pod 'GoogleUtilities'
# If updating the podspec, make sure to add a tag and push it to origin
pod 'Swarm', :git => 'git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git', :branch => 'develop'
end
#Application
target '1Weather' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
shared_pods
# Pods for 1Weather
pod 'SnapKit'
pod 'BezierKit'
pod 'Localize-Swift'
pod 'lottie-ios'
pod 'Cirque', :git => 'https://github.com/StepanetsDmtry/Cirque.git'
pod 'XMLCoder', '~> 0.12.0'
......@@ -16,10 +29,6 @@ target '1Weather' do
pod 'Flurry-iOS-SDK/FlurrySDK'
pod 'MoEngage-iOS-SDK'
# If updating the podspec, make sure to add a tag and push it to origin
pod 'Swarm', :git => 'git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git', :branch => 'develop'
pod 'Firebase/Crashlytics'
# Recommended: Add the Firebase pod for Google Analytics
pod 'Firebase/Analytics'
pod 'Firebase/RemoteConfig'
......@@ -35,6 +44,11 @@ target '1Weather' do
pod 'PKHUD', '~> 5.0'
end
#Core
target 'OneWeatherCore' do
project 'OneWeatherCore/OneWeatherCore.project'
shared_pods
end
target 'OneWeatherNotificationServiceExtension' do
use_frameworks!
......
......@@ -115,12 +115,24 @@ PODS:
- Google-Mobile-Ads-SDK (>= 8.2.0)
- mopub-ios-sdk (= 5.16.2)
- GoogleUserMessagingPlatform (2.0.0)
- GoogleUtilities (7.4.1):
- GoogleUtilities/AppDelegateSwizzler (= 7.4.1)
- GoogleUtilities/Environment (= 7.4.1)
- GoogleUtilities/ISASwizzler (= 7.4.1)
- GoogleUtilities/Logger (= 7.4.1)
- GoogleUtilities/MethodSwizzler (= 7.4.1)
- GoogleUtilities/Network (= 7.4.1)
- "GoogleUtilities/NSData+zlib (= 7.4.1)"
- GoogleUtilities/Reachability (= 7.4.1)
- GoogleUtilities/SwizzlerTestHelpers (= 7.4.1)
- GoogleUtilities/UserDefaults (= 7.4.1)
- GoogleUtilities/AppDelegateSwizzler (7.4.1):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.4.1):
- PromisesObjC (~> 1.2)
- GoogleUtilities/ISASwizzler (7.4.1)
- GoogleUtilities/Logger (7.4.1):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (7.4.1):
......@@ -132,6 +144,8 @@ PODS:
- "GoogleUtilities/NSData+zlib (7.4.1)"
- GoogleUtilities/Reachability (7.4.1):
- GoogleUtilities/Logger
- GoogleUtilities/SwizzlerTestHelpers (7.4.1):
- GoogleUtilities/MethodSwizzler
- GoogleUtilities/UserDefaults (7.4.1):
- GoogleUtilities/Logger
- Localize-Swift (3.2.0):
......@@ -177,6 +191,7 @@ DEPENDENCIES:
- GoogleMobileAdsMediationFacebook
- GoogleMobileAdsMediationFyber
- GoogleMobileAdsMediationMoPub
- GoogleUtilities
- Localize-Swift
- lottie-ios
- MoEngage-iOS-SDK
......@@ -276,6 +291,6 @@ SPEC CHECKSUMS:
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: 2a940ee71b11c8df13e5b37774edf13ede6b06bb
PODFILE CHECKSUM: 0c2405861201a6f3431e069ff33505c571f65b68
COCOAPODS: 1.10.1
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h"
FOUNDATION_EXPORT NSString *kGULSwizzlerAssociatedObjectKey;
@interface GULObjectSwizzler (Internal)
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass;
@end
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h"
#import <objc/runtime.h>
#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
@implementation GULObjectSwizzler {
// The swizzled object.
__weak id _swizzledObject;
// The original class of the object.
Class _originalClass;
// The dynamically generated subclass of _originalClass.
Class _generatedClass;
}
#pragma mark - Class methods
+ (void)setAssociatedObject:(id)object
key:(NSString *)key
value:(nullable id)value
association:(GUL_ASSOCIATION)association {
objc_AssociationPolicy resolvedAssociation;
switch (association) {
case GUL_ASSOCIATION_ASSIGN:
resolvedAssociation = OBJC_ASSOCIATION_ASSIGN;
break;
case GUL_ASSOCIATION_RETAIN_NONATOMIC:
resolvedAssociation = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
break;
case GUL_ASSOCIATION_COPY_NONATOMIC:
resolvedAssociation = OBJC_ASSOCIATION_COPY_NONATOMIC;
break;
case GUL_ASSOCIATION_RETAIN:
resolvedAssociation = OBJC_ASSOCIATION_RETAIN;
break;
case GUL_ASSOCIATION_COPY:
resolvedAssociation = OBJC_ASSOCIATION_COPY;
break;
default:
break;
}
objc_setAssociatedObject(object, key.UTF8String, value, resolvedAssociation);
}
+ (nullable id)getAssociatedObject:(id)object key:(NSString *)key {
return objc_getAssociatedObject(object, key.UTF8String);
}
#pragma mark - Instance methods
/** Instantiates an instance of this class.
*
* @param object The object to swizzle.
* @return An instance of this class.
*/
- (instancetype)initWithObject:(id)object {
if (object == nil) {
return nil;
}
GULObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:object key:kGULSwizzlerAssociatedObjectKey];
if ([existingSwizzler isKindOfClass:[GULObjectSwizzler class]]) {
// The object has been swizzled already, no need to swizzle again.
return existingSwizzler;
}
self = [super init];
if (self) {
_swizzledObject = object;
_originalClass = object_getClass(object);
NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString],
NSStringFromClass(_originalClass)];
_generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0);
NSAssert(_generatedClass, @"Wasn't able to allocate the class pair.");
}
return self;
}
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
NSAssert(_generatedClass, @"This object has already been unswizzled.");
Method method = isClassSelector ? class_getClassMethod(aClass, selector)
: class_getInstanceMethod(aClass, selector);
Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass;
IMP implementation = method_getImplementation(method);
const char *typeEncoding = method_getTypeEncoding(method);
class_replaceMethod(targetClass, selector, implementation, typeEncoding);
}
- (void)setAssociatedObjectWithKey:(NSString *)key
value:(id)value
association:(GUL_ASSOCIATION)association {
__strong id swizzledObject = _swizzledObject;
if (swizzledObject) {
[[self class] setAssociatedObject:swizzledObject key:key value:value association:association];
}
}
- (nullable id)getAssociatedObjectForKey:(NSString *)key {
__strong id swizzledObject = _swizzledObject;
if (swizzledObject) {
return [[self class] getAssociatedObject:swizzledObject key:key];
}
return nil;
}
- (void)swizzle {
__strong id swizzledObject = _swizzledObject;
GULObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:swizzledObject key:kGULSwizzlerAssociatedObjectKey];
if (existingSwizzler != nil) {
NSAssert(existingSwizzler == self, @"The swizzled object has a different swizzler.");
// The object has been swizzled already.
return;
}
if (swizzledObject) {
[GULObjectSwizzler setAssociatedObject:swizzledObject
key:kGULSwizzlerAssociatedObjectKey
value:self
association:GUL_ASSOCIATION_RETAIN];
[GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self];
NSAssert(_originalClass == object_getClass(swizzledObject),
@"The original class is not the reported class now.");
NSAssert(class_getInstanceSize(_originalClass) == class_getInstanceSize(_generatedClass),
@"The instance size of the generated class must be equal to the original class.");
objc_registerClassPair(_generatedClass);
Class doubleCheckOriginalClass __unused = object_setClass(_swizzledObject, _generatedClass);
NSAssert(_originalClass == doubleCheckOriginalClass,
@"The original class must be the same as the class returned by object_setClass");
} else {
NSAssert(NO, @"You can't swizzle a nil object");
}
}
- (void)dealloc {
if (_generatedClass) {
if (_swizzledObject == nil) {
// The swizzled object has been deallocated already, so the generated class can be disposed
// now.
objc_disposeClassPair(_generatedClass);
return;
}
// GULSwizzledObject is retained by the swizzled object which means that the swizzled object is
// being deallocated now. Let's see if we should schedule the generated class disposal.
// If the swizzled object has a different class, it most likely indicates that the object was
// ISA swizzled one more time. In this case it is not safe to dispose the generated class. We
// will have to keep it to prevent a crash.
// TODO: Consider adding a flag that can be set by the host application to dispose the class
// pair unconditionally. It may be used by apps that use ISA Swizzling themself and are
// confident in disposing their subclasses.
BOOL isSwizzledObjectInstanceOfGeneratedClass =
object_getClass(_swizzledObject) == _generatedClass;
if (isSwizzledObjectInstanceOfGeneratedClass) {
Class generatedClass = _generatedClass;
// Schedule the generated class disposal after the swizzled object has been deallocated.
dispatch_async(dispatch_get_main_queue(), ^{
objc_disposeClassPair(generatedClass);
});
}
}
}
- (BOOL)isSwizzlingProxyObject {
return [_swizzledObject isProxy];
}
@end
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <objc/runtime.h>
#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
NSString *kGULSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
@interface GULSwizzledObject ()
@end
@implementation GULSwizzledObject
+ (void)copyDonorSelectorsUsingObjectSwizzler:(GULObjectSwizzler *)objectSwizzler {
[objectSwizzler copySelector:@selector(gul_objectSwizzler) fromClass:self isClassSelector:NO];
[objectSwizzler copySelector:@selector(gul_class) fromClass:self isClassSelector:NO];
// This is needed because NSProxy objects usually override -[NSObjectProtocol respondsToSelector:]
// and ask this question to the underlying object. Since we don't swizzle the underlying object
// but swizzle the proxy, when someone calls -[NSObjectProtocol respondsToSelector:] on the proxy,
// the answer ends up being NO even if we added new methods to the subclass through ISA Swizzling.
// To solve that, we override -[NSObjectProtocol respondsToSelector:] in such a way that takes
// into account the fact that we've added new methods.
if ([objectSwizzler isSwizzlingProxyObject]) {
[objectSwizzler copySelector:@selector(respondsToSelector:) fromClass:self isClassSelector:NO];
}
}
- (instancetype)init {
NSAssert(NO, @"Do not instantiate this class, it's only a donor class");
return nil;
}
- (GULObjectSwizzler *)gul_objectSwizzler {
return [GULObjectSwizzler getAssociatedObject:self key:kGULSwizzlerAssociatedObjectKey];
}
#pragma mark - Donor methods
- (Class)gul_class {
return [[self gul_objectSwizzler] generatedClass];
}
// Only added to a class when we detect it is a proxy.
- (BOOL)respondsToSelector:(SEL)aSelector {
Class gulClass = [[self gul_objectSwizzler] generatedClass];
return [gulClass instancesRespondToSelector:aSelector] || [super respondsToSelector:aSelector];
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Enums that map to their OBJC-prefixed counterparts. */
typedef OBJC_ENUM(uintptr_t, GUL_ASSOCIATION){
// Is a weak association.
GUL_ASSOCIATION_ASSIGN,
// Is a nonatomic strong association.
GUL_ASSOCIATION_RETAIN_NONATOMIC,
// Is a nonatomic copy association.
GUL_ASSOCIATION_COPY_NONATOMIC,
// Is an atomic strong association.
GUL_ASSOCIATION_RETAIN,
// Is an atomic copy association.
GUL_ASSOCIATION_COPY};
/** This class handles swizzling a specific instance of a class by generating a
* dynamic subclass and installing selectors and properties onto the dynamic
* subclass. Then, the instance's class is set to the dynamic subclass. There
* should be a 1:1 ratio of object swizzlers to swizzled instances.
*/
@interface GULObjectSwizzler : NSObject
/** The subclass that is generated. */
@property(nullable, nonatomic, readonly) Class generatedClass;
/** Sets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param object The object that will be queried for the associated object.
* @param key The key of the associated object.
* @param value The value to associate to the swizzled object.
* @param association The mechanism to use when associating the objects.
*/
+ (void)setAssociatedObject:(id)object
key:(NSString *)key
value:(nullable id)value
association:(GUL_ASSOCIATION)association;
/** Gets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param object The object that will be queried for the associated object.
* @param key The key of the associated object.
*/
+ (nullable id)getAssociatedObject:(id)object key:(NSString *)key;
/** Please use the designated initializer. */
- (instancetype)init NS_UNAVAILABLE;
/** Instantiates an object swizzler using an object it will operate on.
* Generates a new class pair.
*
* @note There is no need to store this object. After calling -swizzle, this
* object can be found by calling -gul_objectSwizzler
*
* @param object The object to be swizzled.
* @return An instance of this class.
*/
- (instancetype)initWithObject:(id)object NS_DESIGNATED_INITIALIZER;
/** Sets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param key The key of the associated object.
* @param value The value to associate to the swizzled object.
* @param association The mechanism to use when associating the objects.
*/
- (void)setAssociatedObjectWithKey:(NSString *)key
value:(id)value
association:(GUL_ASSOCIATION)association;
/** Gets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param key The key of the associated object.
*/
- (nullable id)getAssociatedObjectForKey:(NSString *)key;
/** Copies a selector from an existing class onto the generated dynamic subclass
* that this object will adopt. This mechanism can be used to add methods to
* specific instances of a class.
*
* @note Should not be called after calling -swizzle.
* @param selector The selector to add to the instance.
* @param aClass The class supplying an implementation of the method.
* @param isClassSelector A BOOL specifying whether the selector is a class or
* instance selector.
*/
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector;
/** Swizzles the object, changing its class to the generated class. Registers
* the class pair. */
- (void)swizzle;
/** @return The value of -[objectBeingSwizzled isProxy] */
- (BOOL)isSwizzlingProxyObject;
@end
NS_ASSUME_NONNULL_END
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GULObjectSwizzler;
/** This class exists as a method donor. These methods will be added to all objects that are
* swizzled by the object swizzler. This class should not be instantiated.
*/
@interface GULSwizzledObject : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Copies the methods below to the swizzled object.
*
* @param objectSwizzler The swizzler to use when adding the methods below.
*/
+ (void)copyDonorSelectorsUsingObjectSwizzler:(GULObjectSwizzler *)objectSwizzler;
#pragma mark - Donor methods.
/** @return The generated subclass. Used in respondsToSelector: calls. */
- (Class)gul_class;
/** @return The object swizzler that manages this object. */
- (GULObjectSwizzler *)gul_objectSwizzler;
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/** An example NSProxy that could be used to wrap an object that we have to ISA Swizzle. */
@interface GULProxy : NSProxy
+ (instancetype)proxyWithDelegate:(id)delegate;
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GoogleUtilities/SwizzlerTestHelpers/GULProxy.h"
@interface GULProxy ()
@property(nonatomic, strong) id delegateObject;
@end
@implementation GULProxy
- (instancetype)initWithDelegate:(id)delegate {
_delegateObject = delegate;
return self;
}
+ (instancetype)proxyWithDelegate:(id)delegate {
return [[GULProxy alloc] initWithDelegate:delegate];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _delegateObject;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if (_delegateObject != nil) {
[invocation setTarget:_delegateObject];
[invocation invoke];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [_delegateObject instanceMethodSignatureForSelector:selector];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_delegateObject respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_delegateObject isEqual:object];
}
- (NSUInteger)hash {
return [_delegateObject hash];
}
- (Class)superclass {
return [_delegateObject superclass];
}
- (Class)class {
return [_delegateObject class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_delegateObject isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_delegateObject isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_delegateObject conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_delegateObject description];
}
- (NSString *)debugDescription {
return [_delegateObject debugDescription];
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** A simple container class for representing the diff of a class. */
@interface GULRuntimeClassDiff : NSObject
/** The class this diff is with respect to. */
@property(nonatomic, nullable, weak) Class aClass;
/** The added class properties (as opposed to instance properties). */
@property(nonatomic) NSSet<NSString *> *addedClassProperties;
/** The added instance properties. */
@property(nonatomic) NSSet<NSString *> *addedInstanceProperties;
/** The added class selectors. */
@property(nonatomic) NSSet<NSString *> *addedClassSelectors;
/** The added instance selectors. */
@property(nonatomic) NSSet<NSString *> *addedInstanceSelectors;
/** The modified imps. */
@property(nonatomic) NSSet<NSString *> *modifiedImps;
@end
NS_ASSUME_NONNULL_END
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassDiff.h"
/** Computes the equality of possibly nil or empty NSSets.
*
* @param firstSet The first set of strings.
* @param secondSet The second set of strings.
* @return YES if both sets are zero length or nil, or the result of `isEqualToSet:`.
*/
FOUNDATION_STATIC_INLINE
BOOL IsEqual(NSSet *firstSet, NSSet *secondSet) {
return ((!firstSet || firstSet.count == 0) && (!secondSet || secondSet.count == 0)) ||
[firstSet isEqualToSet:secondSet];
}
@implementation GULRuntimeClassDiff
- (NSUInteger)hash {
return [_aClass hash] ^ [_addedClassProperties hash] ^ [_addedInstanceProperties hash] ^
[_addedClassSelectors hash] ^ [_addedInstanceSelectors hash] ^ [_modifiedImps hash];
}
- (BOOL)isEqual:(id)object {
GULRuntimeClassDiff *otherObject = (GULRuntimeClassDiff *)object;
return _aClass == otherObject->_aClass &&
IsEqual(_addedClassProperties, otherObject->_addedClassProperties) &&
IsEqual(_addedInstanceProperties, otherObject->_addedInstanceProperties) &&
IsEqual(_addedClassSelectors, otherObject->_addedClassSelectors) &&
IsEqual(_addedInstanceSelectors, otherObject->_addedInstanceSelectors) &&
IsEqual(_modifiedImps, otherObject->_modifiedImps);
}
- (NSString *)description {
NSMutableString *description = [[NSMutableString alloc] init];
[description appendFormat:@"%@:\n", NSStringFromClass(self.aClass)];
if (_addedClassProperties.count) {
[description appendString:@"\tAdded class properties:\n"];
for (NSString *addedClassProperty in _addedClassProperties) {
[description appendFormat:@"\t\t%@\n", addedClassProperty];
}
}
if (_addedInstanceProperties.count) {
[description appendString:@"\tAdded instance properties:\n"];
for (NSString *addedInstanceProperty in _addedInstanceProperties) {
[description appendFormat:@"\t\t%@\n", addedInstanceProperty];
}
}
if (_addedClassSelectors.count) {
[description appendString:@"\tAdded class selectors:\n"];
for (NSString *addedClassSelector in _addedClassSelectors) {
[description appendFormat:@"\t\t%@\n", addedClassSelector];
}
}
if (_addedInstanceSelectors.count) {
[description appendString:@"\tAdded instance selectors:\n"];
for (NSString *addedInstanceSelector in _addedInstanceSelectors) {
[description appendFormat:@"\t\t%@\n", addedInstanceSelector];
}
}
if (_modifiedImps.count) {
[description appendString:@"\tModified IMPs:\n"];
for (NSString *modifiedImp in _modifiedImps) {
[description appendFormat:@"\t\t%@\n", modifiedImp];
}
}
return description;
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GULRuntimeClassDiff;
NS_ASSUME_NONNULL_BEGIN
/** This class is able to capture the runtime state of a given class. */
@interface GULRuntimeClassSnapshot : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Instantiates an instance of this class with the given class.
*
* @param aClass The class that will be snapshot.
* @return An instance of this class.
*/
- (instancetype)initWithClass:(Class)aClass NS_DESIGNATED_INITIALIZER;
/** Captures the runtime state of this class. */
- (void)capture;
/** Calculates the diff between snapshots and returns a diff object populated with information.
*
* @param otherClassSnapshot The other snapshot to compare it to. It's assumed that the
* otherClassSnapshot was created after the caller.
* @return A diff object representing the diff between the two snapshots.
*/
- (GULRuntimeClassDiff *)diff:(GULRuntimeClassSnapshot *)otherClassSnapshot;
@end
NS_ASSUME_NONNULL_END
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassSnapshot.h"
#import <objc/runtime.h>
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassDiff.h"
@implementation GULRuntimeClassSnapshot {
/** The class this snapshot is related to. */
Class _aClass;
/** The metaclass of aClass. */
Class _metaclass;
/** The current set of class properties on aClass. */
NSMutableSet<NSString *> *_classProperties;
/** The current set of instance properties on aClass. */
NSMutableSet<NSString *> *_instanceProperties;
/** The current set of class selectors on aClass. */
NSMutableSet<NSString *> *_classSelectors;
/** The current set of instance selectors on aClass. */
NSMutableSet<NSString *> *_instanceSelectors;
/** The current set of class and instance selector IMPs on aClass. */
NSMutableSet<NSString *> *_imps;
/** The current hash of this object, updated as the state of this instance changes. */
NSUInteger _runningHash;
}
- (instancetype)init {
NSAssert(NO, @"Please use the designated initializer.");
return nil;
}
- (instancetype)initWithClass:(Class)aClass {
self = [super init];
if (self) {
_aClass = aClass;
_metaclass = object_getClass(aClass);
_classProperties = [[NSMutableSet alloc] init];
_instanceProperties = [[NSMutableSet alloc] init];
_instanceSelectors = [[NSMutableSet alloc] init];
_classSelectors = [[NSMutableSet alloc] init];
_imps = [[NSMutableSet alloc] init];
_runningHash = [NSStringFromClass(_aClass) hash] ^ [NSStringFromClass(_metaclass) hash];
}
return self;
}
- (void)capture {
[self captureProperties];
[self captureSelectorsAndImps];
}
- (GULRuntimeClassDiff *)diff:(GULRuntimeClassSnapshot *)otherClassSnapshot {
GULRuntimeClassDiff *classDiff = [[GULRuntimeClassDiff alloc] init];
if (_runningHash != [otherClassSnapshot hash]) {
classDiff.aClass = _aClass;
[self computeDiffOfProperties:otherClassSnapshot withClassDiff:classDiff];
[self computeDiffOfSelectorsAndImps:otherClassSnapshot withClassDiff:classDiff];
}
return classDiff;
}
- (NSUInteger)hash {
return _runningHash;
}
- (BOOL)isEqual:(id)object {
return self->_runningHash == ((GULRuntimeClassSnapshot *)object)->_runningHash;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@> Hash: 0x%lX", _aClass, (unsigned long)[self hash]];
}
#pragma mark - Private methods below -
#pragma mark State capturing methods
/** Captures class and instance properties and saves state in ivars. */
- (void)captureProperties {
// Capture instance properties.
unsigned int outCount;
objc_property_t *instanceProperties = class_copyPropertyList(_aClass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = instanceProperties[i];
NSString *propertyString = [NSString stringWithUTF8String:property_getName(property)];
[_instanceProperties addObject:propertyString];
_runningHash ^= [propertyString hash];
}
free(instanceProperties);
// Capture class properties.
outCount = 0;
objc_property_t *classProperties = class_copyPropertyList(_metaclass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = classProperties[i];
NSString *propertyString = [NSString stringWithUTF8String:property_getName(property)];
[_classProperties addObject:propertyString];
_runningHash ^= [propertyString hash];
}
free(classProperties);
}
/** Captures the class and instance selectors and their IMPs and saves their state in ivars. */
- (void)captureSelectorsAndImps {
// Capture instance methods and their IMPs.
unsigned int outCount;
Method *instanceMethods = class_copyMethodList(_aClass, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = instanceMethods[i];
NSString *methodString = NSStringFromSelector(method_getName(method));
[_instanceSelectors addObject:methodString];
IMP imp = method_getImplementation(method);
NSString *impString =
[NSString stringWithFormat:@"%p -[%@ %@]", imp, NSStringFromClass(_aClass), methodString];
if (![_imps containsObject:impString]) {
[_imps addObject:impString];
}
_runningHash ^= [impString hash];
}
free(instanceMethods);
// Capture class methods and their IMPs.
outCount = 0;
Method *classMethods = class_copyMethodList(_metaclass, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = classMethods[i];
NSString *methodString = NSStringFromSelector(method_getName(method));
[_classSelectors addObject:methodString];
IMP imp = method_getImplementation(method);
NSString *impString = [NSString
stringWithFormat:@"%p +[%@ %@]", imp, NSStringFromClass(_metaclass), methodString];
NSAssert(![_imps containsObject:impString],
@"This IMP/method combination has already been captured: %@:%@",
NSStringFromClass(_aClass), impString);
[_imps addObject:impString];
_runningHash ^= [impString hash];
}
free(classMethods);
}
#pragma mark Diff computation methods
/** Compute the diff of class and instance properties and populates the classDiff with that info.
*
* @param otherClassSnapshot The other class snapshot to diff against.
* @param classDiff The diff object to modify.
*/
- (void)computeDiffOfProperties:(GULRuntimeClassSnapshot *)otherClassSnapshot
withClassDiff:(GULRuntimeClassDiff *)classDiff {
if ([_classProperties hash] != [otherClassSnapshot->_classProperties hash]) {
classDiff.addedClassProperties = [otherClassSnapshot->_classProperties
objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
return ![self->_classProperties containsObject:obj];
}];
}
if ([_instanceProperties hash] != [otherClassSnapshot->_instanceProperties hash]) {
classDiff.addedInstanceProperties = [otherClassSnapshot->_instanceProperties
objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
return ![self->_instanceProperties containsObject:obj];
}];
}
}
/** Computes the diff of class and instance selectors and their IMPs and populates the classDiff.
*
* @param otherClassSnapshot The other class snapshot to diff against.
* @param classDiff The diff object to modify.
*/
- (void)computeDiffOfSelectorsAndImps:(GULRuntimeClassSnapshot *)otherClassSnapshot
withClassDiff:(GULRuntimeClassDiff *)classDiff {
if ([_classSelectors hash] != [otherClassSnapshot->_classSelectors hash]) {
classDiff.addedClassSelectors = [otherClassSnapshot->_classSelectors
objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
return ![self->_classSelectors containsObject:obj];
}];
}
if ([_instanceSelectors hash] != [otherClassSnapshot->_instanceSelectors hash]) {
classDiff.addedInstanceSelectors = [otherClassSnapshot->_instanceSelectors
objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
return ![self->_instanceSelectors containsObject:obj];
}];
}
// modifiedImps contains the prior IMP address, not the current IMP address.
classDiff.modifiedImps =
[_imps objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
return ![otherClassSnapshot->_imps containsObject:obj];
}];
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GULRuntimeClassDiff;
NS_ASSUME_NONNULL_BEGIN
/** A simple container class for storing the diff of some runtime state. */
@interface GULRuntimeDiff : NSObject
/** The added classes. */
@property(nonatomic) NSSet<NSString *> *addedClasses;
/** The removed classes. */
@property(nonatomic) NSSet<NSString *> *removedClasses;
/** The diff objects for modified classes. */
@property(nonatomic) NSSet<GULRuntimeClassDiff *> *classDiffs;
@end
NS_ASSUME_NONNULL_END
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeDiff.h"
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassDiff.h"
/** Computes the equality of possibly nil or empty NSSets.
*
* @param firstSet The first set of strings.
* @param secondSet The second set of strings.
* @return YES if both sets are zero length or nil, or the result of `isEqualToSet:`.
*/
FOUNDATION_STATIC_INLINE
BOOL IsEqual(NSSet *firstSet, NSSet *secondSet) {
return ((!firstSet || firstSet.count == 0) && (!secondSet || secondSet.count == 0)) ||
[firstSet isEqualToSet:secondSet];
}
@implementation GULRuntimeDiff
- (NSUInteger)hash {
return [_addedClasses hash] ^ [_removedClasses hash] ^ [_classDiffs hash];
}
- (BOOL)isEqual:(id)object {
GULRuntimeDiff *otherObject = (GULRuntimeDiff *)object;
return IsEqual(_addedClasses, otherObject->_addedClasses) &&
IsEqual(_removedClasses, otherObject->_removedClasses) &&
IsEqual(_classDiffs, otherObject->_classDiffs);
}
- (NSString *)description {
NSMutableString *description = [[NSMutableString alloc] init];
if (_addedClasses.count) {
[description appendString:@"Added classes:\n"];
for (NSString *classString in _addedClasses) {
[description appendFormat:@"\t%@\n", classString];
}
}
if (_removedClasses.count) {
[description appendString:@"\nRemoved classes:\n"];
for (NSString *classString in _removedClasses) {
[description appendFormat:@"\t%@\n", classString];
}
}
if (_classDiffs.count) {
[description appendString:@"\nClass diffs:\n"];
for (GULRuntimeClassDiff *classDiff in _classDiffs) {
NSString *classDiffDescription =
[[classDiff description] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
[description appendFormat:@"\t%@\n", classDiffDescription];
}
}
return description;
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GULRuntimeClassSnapshot;
@class GULRuntimeDiff;
NS_ASSUME_NONNULL_BEGIN
/** This class captures various aspects the current state of the Objective-C runtime. */
@interface GULRuntimeSnapshot : NSObject
/** Initializes an instance of this class. The designated initializer.
*
* @param classes The set of classes to track. If nil or empty, all ObjC classes are tracked.
* @return An instance of this class.
*/
- (instancetype)initWithClasses:(nullable NSSet<Class> *)classes NS_DESIGNATED_INITIALIZER;
/** Captures the state of the class set. */
- (void)capture;
/** Computes the diff between this snapshot and another snapshot.
*
* @param otherSnapshot The other snapshot, assumed to be more recent than self.
* @return A diff object populated with the diff.
*/
- (GULRuntimeDiff *)diff:(GULRuntimeSnapshot *)otherSnapshot;
@end
NS_ASSUME_NONNULL_END
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.h"
#import <objc/runtime.h>
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassDiff.h"
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassSnapshot.h"
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeDiff.h"
@implementation GULRuntimeSnapshot {
/** The set of tracked classes. */
NSSet<Class> *__nullable _classes;
/** The class snapshots for each tracked class. */
NSMutableDictionary<NSString *, GULRuntimeClassSnapshot *> *_classSnapshots;
/** The hash value of this object. */
NSUInteger _runningHash;
}
- (instancetype)init {
return [self initWithClasses:nil];
}
- (instancetype)initWithClasses:(nullable NSSet<Class> *)classes {
self = [super init];
if (self) {
_classSnapshots = [[NSMutableDictionary alloc] init];
_classes = classes;
_runningHash = [_classes hash] ^ [_classSnapshots hash];
}
return self;
}
- (NSUInteger)hash {
return _runningHash;
}
- (BOOL)isEqual:(id)object {
return [self hash] == [object hash];
}
- (NSString *)description {
return [[super description] stringByAppendingFormat:@" Hash: 0x%lX", (unsigned long)[self hash]];
}
- (void)capture {
int numberOfClasses = objc_getClassList(NULL, 0);
Class *classList = (Class *)malloc(numberOfClasses * sizeof(Class));
numberOfClasses = objc_getClassList(classList, numberOfClasses);
// If we should track specific classes, then there's no need to figure out all ObjC classes.
if (_classes) {
for (Class aClass in _classes) {
NSString *classString = NSStringFromClass(aClass);
GULRuntimeClassSnapshot *classSnapshot =
[[GULRuntimeClassSnapshot alloc] initWithClass:aClass];
_classSnapshots[classString] = classSnapshot;
[classSnapshot capture];
_runningHash ^= [classSnapshot hash];
}
} else {
for (int i = 0; i < numberOfClasses; i++) {
Class aClass = classList[i];
NSString *classString = NSStringFromClass(aClass);
GULRuntimeClassSnapshot *classSnapshot =
[[GULRuntimeClassSnapshot alloc] initWithClass:aClass];
_classSnapshots[classString] = classSnapshot;
[classSnapshot capture];
_runningHash ^= [classSnapshot hash];
}
}
free(classList);
}
- (GULRuntimeDiff *)diff:(GULRuntimeSnapshot *)otherSnapshot {
GULRuntimeDiff *runtimeDiff = [[GULRuntimeDiff alloc] init];
NSSet *setOne = [NSSet setWithArray:[_classSnapshots allKeys]];
NSSet *setTwo = [NSSet setWithArray:[otherSnapshot->_classSnapshots allKeys]];
// All items contained within setOne, but not in setTwo.
NSSet *removedClasses = [setOne
filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
id _Nullable evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
return ![setTwo containsObject:evaluatedObject];
}]];
// All items contained within setTwo, but not in setOne.
NSSet *addedClasses = [setTwo
filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
id _Nullable evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
return ![setOne containsObject:evaluatedObject];
}]];
runtimeDiff.removedClasses = removedClasses;
runtimeDiff.addedClasses = addedClasses;
NSMutableSet<GULRuntimeClassDiff *> *classDiffs = [[NSMutableSet alloc] init];
[_classSnapshots
enumerateKeysAndObjectsUsingBlock:^(
NSString *_Nonnull key, GULRuntimeClassSnapshot *_Nonnull obj, BOOL *_Nonnull stop) {
GULRuntimeClassSnapshot *classSnapshot = self->_classSnapshots[key];
GULRuntimeClassSnapshot *otherClassSnapshot = otherSnapshot->_classSnapshots[key];
GULRuntimeClassDiff *classDiff = [classSnapshot diff:otherClassSnapshot];
if ([classDiff hash]) {
NSAssert(![classDiffs containsObject:classDiff],
@"An equivalent class diff has already been stored.");
[classDiffs addObject:classDiff];
}
}];
runtimeDiff.classDiffs = classDiffs;
return runtimeDiff;
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeClassDiff.h"
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeDiff.h"
NS_ASSUME_NONNULL_BEGIN
/** A helper class that enables the snapshotting and diffing of ObjC runtime state. */
@interface GULRuntimeStateHelper : NSObject
/** Captures the current state of the entire runtime and returns the snapshot number.
*
* @return The snapshot number corresponding to this capture.
*/
+ (NSUInteger)captureRuntimeState;
/** Captures the current state of the runtime for the provided classes.
*
* @param classes The classes whose state should be snapshotted.
* @return The snapshot number corresponding to this capture.
*/
+ (NSUInteger)captureRuntimeStateOfClasses:(NSSet<Class> *)classes;
/** Prints the diff between two snapshot numbers.
*
* @param firstSnapshot The first runtime snapshot, as provided by captureRuntimeState.
* @param secondSnapshot The runtime snapshot sometime after firstSnapshot.
* @return An instance of GULRuntimeDiff that contains the diff information.
*/
+ (GULRuntimeDiff *)diffBetween:(NSUInteger)firstSnapshot secondSnapshot:(NSUInteger)secondSnapshot;
@end
NS_ASSUME_NONNULL_END
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeStateHelper.h"
#import <objc/runtime.h>
#import "GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.h"
@implementation GULRuntimeStateHelper
/** Initializes and returns the snapshot cache.
*
* @return A singleton snapshot cache.
*/
+ (NSMutableArray<GULRuntimeSnapshot *> *)snapshotCache {
static NSMutableArray<GULRuntimeSnapshot *> *snapshots;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
snapshots = [[NSMutableArray<GULRuntimeSnapshot *> alloc] init];
});
return snapshots;
}
+ (NSUInteger)captureRuntimeState {
GULRuntimeSnapshot *snapshot = [[GULRuntimeSnapshot alloc] init];
[snapshot capture];
[[self snapshotCache] addObject:snapshot];
return [self snapshotCache].count - 1;
}
+ (NSUInteger)captureRuntimeStateOfClasses:(NSSet<Class> *)classes {
GULRuntimeSnapshot *snapshot = [[GULRuntimeSnapshot alloc] initWithClasses:classes];
[snapshot capture];
[[self snapshotCache] addObject:snapshot];
return [self snapshotCache].count - 1;
}
+ (GULRuntimeDiff *)diffBetween:(NSUInteger)firstSnapshot
secondSnapshot:(NSUInteger)secondSnapshot {
NSArray *snapshotCache = [self snapshotCache];
return [snapshotCache[firstSnapshot] diff:snapshotCache[secondSnapshot]];
}
@end
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h"
#import <objc/runtime.h>
#import "GoogleUtilities/SwizzlerTestHelpers/GULSwizzlingCache.h"
extern dispatch_queue_t GetGULSwizzlingQueue(void);
@implementation GULSwizzler (Unswizzle)
+ (void)unswizzleClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector {
dispatch_sync(GetGULSwizzlingQueue(), ^{
NSAssert(aClass != nil && selector != nil, @"You cannot unswizzle a nil class or selector.");
Method method = nil;
Class resolvedClass = aClass;
if (isClassSelector) {
resolvedClass = object_getClass(aClass);
method = class_getClassMethod(aClass, selector);
} else {
method = class_getInstanceMethod(aClass, selector);
}
NSAssert(method, @"Couldn't find the method you're unswizzling in the runtime.");
IMP originalImp = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:resolvedClass
withSelector:selector];
NSAssert(originalImp, @"This class/selector combination hasn't been swizzled");
IMP currentImp = method_setImplementation(method, originalImp);
__unused BOOL didRemoveBlock = imp_removeBlock(currentImp);
NSAssert(didRemoveBlock, @"Wasn't able to remove the block of a swizzled IMP.");
[[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:currentImp
selector:selector
aClass:resolvedClass];
});
}
+ (nullable IMP)originalImplementationForClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector {
__block IMP originalImp = nil;
dispatch_sync(GetGULSwizzlingQueue(), ^{
Class resolvedClass = isClassSelector ? object_getClass(aClass) : aClass;
originalImp = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:resolvedClass
withSelector:selector];
NSAssert(originalImp, @"The IMP for this class/selector combo doesn't exist (%@, %@).",
NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
});
return originalImp;
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/** This class handles the caching and retrieval of IMPs as we swizzle and unswizzle them. It uses
* two C++ STL unordered_maps as the underlying data store. This class is NOT thread safe.
*/
@interface GULSwizzlingCache : NSObject
/** Singleton initializer.
*
* @return a singleton GULSwizzlingCache.
*/
+ (instancetype)sharedInstance;
/** Save the existing IMP that exists before we install the new IMP for a class, selector combo.
* If the currentIMP is something that we put there, it will ignore it and instead point newIMP
* to what existed before we swizzled.
*
* @param newIMP new The IMP that is going to replace the current IMP.
* @param currentIMP The IMP returned by class_getMethodImplementation.
* @param aClass The class that we're swizzling.
* @param selector The selector we're swizzling.
*/
- (void)cacheCurrentIMP:(IMP)currentIMP
forNewIMP:(IMP)newIMP
forClass:(Class)aClass
withSelector:(SEL)selector;
/** Save the existing IMP that exists before we install the new IMP for a class, selector combo.
* If the currentIMP is something that we put there, it will ignore it and instead point newIMP
* to what existed before we swizzled.
*
* @param newIMP new The IMP that is going to replace the current IMP.
* @param currentIMP The IMP returned by class_getMethodImplementation.
* @param aClass The class that we're swizzling.
* @param selector The selector we're swizzling.
*/
+ (void)cacheCurrentIMP:(IMP)currentIMP
forNewIMP:(IMP)newIMP
forClass:(Class)aClass
withSelector:(SEL)selector;
/** Returns the cached IMP that would be invoked with the class and selector combo had we
* never swizzled.
*
* @param aClass The class the selector would be invoked on.
* @param selector The selector
* @return The original IMP i.e. the one that existed right before GULSwizzler swizzled either
* this or a superclass.
*/
- (IMP)cachedIMPForClass:(Class)aClass withSelector:(SEL)selector;
/** Clears the cache of values we no longer need because we've unswizzled the relevant method.
*
* @param swizzledIMP The IMP we replaced the existing IMP with.
* @param selector The selector which that we swizzled for.
* @param aClass The class that we're swizzling.
*/
- (void)clearCacheForSwizzledIMP:(IMP)swizzledIMP selector:(SEL)selector aClass:(Class)aClass;
@end
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "GoogleUtilities/SwizzlerTestHelpers/GULSwizzlingCache.h"
#import <objc/runtime.h>
@interface GULSwizzlingCache ()
- (IMP)originalIMPOfCurrentIMP:(IMP)currentIMP;
@end
@implementation GULSwizzlingCache {
/** A mapping from the new IMP to the original IMP. */
CFMutableDictionaryRef _newToOriginalImps;
/** A mapping from a Class and SEL (stored in a CFArray) to the original IMP that existed for it.
*/
CFMutableDictionaryRef _originalImps;
}
+ (instancetype)sharedInstance {
static GULSwizzlingCache *sharedInstance;
static dispatch_once_t token;
dispatch_once(&token, ^{
sharedInstance = [[GULSwizzlingCache alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_newToOriginalImps = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, // Size.
NULL, // Keys are pointers, so this is NULL.
NULL); // Values are pointers so this is NULL.
_originalImps = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, // Size.
&kCFTypeDictionaryKeyCallBacks, // Keys are CFArrays.
NULL); // Values are pointers so this is NULL.
}
return self;
}
- (void)dealloc {
CFRelease(_newToOriginalImps);
CFRelease(_originalImps);
}
- (void)cacheCurrentIMP:(IMP)currentIMP
forNewIMP:(IMP)newIMP
forClass:(Class)aClass
withSelector:(SEL)selector {
IMP originalIMP = [self originalIMPOfCurrentIMP:currentIMP];
CFDictionaryAddValue(_newToOriginalImps, newIMP, originalIMP);
const void *classSELCArray[2] = {(__bridge void *)(aClass), selector};
CFArrayRef classSELPair = CFArrayCreate(kCFAllocatorDefault, classSELCArray,
2, // Size.
NULL); // Elements are pointers so this is NULL.
CFDictionaryAddValue(_originalImps, classSELPair, originalIMP);
CFRelease(classSELPair);
}
+ (void)cacheCurrentIMP:(IMP)currentIMP
forNewIMP:(IMP)newIMP
forClass:(Class)aClass
withSelector:(SEL)selector {
[[GULSwizzlingCache sharedInstance] cacheCurrentIMP:currentIMP
forNewIMP:newIMP
forClass:aClass
withSelector:selector];
}
- (IMP)cachedIMPForClass:(Class)aClass withSelector:(SEL)selector {
const void *classSELCArray[2] = {(__bridge void *)(aClass), selector};
CFArrayRef classSELPair = CFArrayCreate(kCFAllocatorDefault, classSELCArray,
2, // Size.
NULL); // Elements are pointers so this is NULL.
const void *returnedIMP = CFDictionaryGetValue(_originalImps, classSELPair);
CFRelease(classSELPair);
return (IMP)returnedIMP;
}
- (void)clearCacheForSwizzledIMP:(IMP)swizzledIMP selector:(SEL)selector aClass:(Class)aClass {
CFDictionaryRemoveValue(_newToOriginalImps, swizzledIMP);
const void *classSELCArray[2] = {(__bridge void *)(aClass), selector};
CFArrayRef classSELPair = CFArrayCreate(kCFAllocatorDefault, classSELCArray,
2, // Size.
NULL); // Elements are pointers so this is NULL.
CFDictionaryRemoveValue(_originalImps, classSELPair);
CFRelease(classSELPair);
}
- (IMP)originalIMPOfCurrentIMP:(IMP)currentIMP {
const void *returnedIMP = CFDictionaryGetValue(_newToOriginalImps, currentIMP);
if (returnedIMP != NULL) {
return (IMP)returnedIMP;
} else {
return currentIMP;
}
}
+ (IMP)originalIMPOfCurrentIMP:(IMP)currentIMP {
return [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:currentIMP];
}
#pragma mark - Helper methods for testing
- (void)clearCache {
CFDictionaryRemoveAllValues(_originalImps);
CFDictionaryRemoveAllValues(_newToOriginalImps);
}
- (CFMutableDictionaryRef)originalImps {
return _originalImps;
}
- (CFMutableDictionaryRef)newToOriginalImps {
return _newToOriginalImps;
}
@end
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@interface GULSwizzlingCache ()
/** Checks if we've swizzled the currentIMP and returns the original IMP that would be invoked if
* we hadn't swizzled it in the first place. This method is private because consumers don't need it
* to cache or retrieve any IMPs. It is used internally and for certain asserts in GULSwizzler.
*
* @param currentIMP The IMP returned by class_getMethodImplementation.
* @return The original IMP that would be invoked if we hadn't swizzled at all, and in cases where
* currentIMP is not something that we put there, just returns currentIMP.
*/
+ (IMP)originalIMPOfCurrentIMP:(IMP)currentIMP;
#pragma mark - Helper methods for testing
/** Clears all the cache data structures. */
- (void)clearCache;
/** Allows tests access to the originalImps CFMutableDictionaryRef.
*
* @returns the originalImps CFMutableDictionaryRef.
*/
- (CFMutableDictionaryRef)originalImps;
/** Allows tests access to the newToOriginalImps CFMutableDictionaryRef.
*
* @returns the newToOriginalImps CFMutableDictionaryRef.
*/
- (CFMutableDictionaryRef)newToOriginalImps;
@end
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <GoogleUtilities/GULSwizzler.h>
NS_ASSUME_NONNULL_BEGIN
/** This category adds methods for unswizzling that are only used for testing.
*/
@interface GULSwizzler (Unswizzle)
/** Restores the original implementation.
*
* @param aClass The class to unswizzle.
* @param selector The selector to restore the original implementation of.
* @param isClassSelector A BOOL specifying whether the selector is a class or instance selector.
*/
+ (void)unswizzleClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector;
/** Returns the original IMP for the given class and selector.
*
* @param aClass The class to use.
* @param selector The selector to find the implementation of.
* @param isClassSelector A BOOL specifying whether the selector is a class or instance selector.
* @return The implementation of the selector in the runtime before any consumer or GULSwizzler
* swizzled.
*/
+ (nullable IMP)originalImplementationForClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector;
@end
NS_ASSUME_NONNULL_END
......@@ -115,12 +115,24 @@ PODS:
- Google-Mobile-Ads-SDK (>= 8.2.0)
- mopub-ios-sdk (= 5.16.2)
- GoogleUserMessagingPlatform (2.0.0)
- GoogleUtilities (7.4.1):
- GoogleUtilities/AppDelegateSwizzler (= 7.4.1)
- GoogleUtilities/Environment (= 7.4.1)
- GoogleUtilities/ISASwizzler (= 7.4.1)
- GoogleUtilities/Logger (= 7.4.1)
- GoogleUtilities/MethodSwizzler (= 7.4.1)
- GoogleUtilities/Network (= 7.4.1)
- "GoogleUtilities/NSData+zlib (= 7.4.1)"
- GoogleUtilities/Reachability (= 7.4.1)
- GoogleUtilities/SwizzlerTestHelpers (= 7.4.1)
- GoogleUtilities/UserDefaults (= 7.4.1)
- GoogleUtilities/AppDelegateSwizzler (7.4.1):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.4.1):
- PromisesObjC (~> 1.2)
- GoogleUtilities/ISASwizzler (7.4.1)
- GoogleUtilities/Logger (7.4.1):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (7.4.1):
......@@ -132,6 +144,8 @@ PODS:
- "GoogleUtilities/NSData+zlib (7.4.1)"
- GoogleUtilities/Reachability (7.4.1):
- GoogleUtilities/Logger
- GoogleUtilities/SwizzlerTestHelpers (7.4.1):
- GoogleUtilities/MethodSwizzler
- GoogleUtilities/UserDefaults (7.4.1):
- GoogleUtilities/Logger
- Localize-Swift (3.2.0):
......@@ -177,6 +191,7 @@ DEPENDENCIES:
- GoogleMobileAdsMediationFacebook
- GoogleMobileAdsMediationFyber
- GoogleMobileAdsMediationMoPub
- GoogleUtilities
- Localize-Swift
- lottie-ios
- MoEngage-iOS-SDK
......@@ -276,6 +291,6 @@ SPEC CHECKSUMS:
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: 2a940ee71b11c8df13e5b37774edf13ede6b06bb
PODFILE CHECKSUM: 0c2405861201a6f3431e069ff33505c571f65b68
COCOAPODS: 1.10.1
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "19622742EBA51E823D6DAE3F8CDBFAD4"
......@@ -23,14 +23,15 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
......@@ -38,17 +39,14 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase-Analytics-Core-CoreOnly-Crashlytics-RemoteConfig
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public"
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase-Analytics-Core-CoreOnly-Crashlytics-RemoteConfig
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public"
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase-CoreOnly-Crashlytics
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Firebase
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase-CoreOnly-Crashlytics
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Firebase
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment