| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // 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 {
- // When the Zombies instrument is enabled, a zombie is created for the swizzled object upon
- // deallocation. Because this zombie subclasses the generated class, the swizzler should not
- // dispose it during the swizzler's deallocation.
- //
- // There are other special cases where the generated class might be subclassed by a third-party
- // generated classes, for example: https://github.com/firebase/firebase-ios-sdk/issues/9083
- // To avoid errors in such cases, the environment variable `GULGeneratedClassDisposeDisabled` can
- // be set with `YES`.
- NSDictionary *environment = [[NSProcessInfo processInfo] environment];
- if ([[environment objectForKey:@"NSZombieEnabled"] boolValue] ||
- [[environment objectForKey:@"GULGeneratedClassDisposeDisabled"] boolValue]) {
- return;
- }
- 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
|