-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Refactor GULSwizzledObject to ARC to unblock SwiftPM support #5862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d61d0c7
2cf28db
ff41de7
b00ed00
336e6db
7fd904d
9dd8c07
68aaab6
296e550
ae5b046
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,19 +75,25 @@ + (nullable id)getAssociatedObject:(id)object key:(NSString *)key { | |
* @return An instance of this class. | ||
*/ | ||
- (instancetype)initWithObject:(id)object { | ||
if (object == nil) { | ||
return nil; | ||
} | ||
|
||
GULObjectSwizzler *existingSwizzler = | ||
[[self class] getAssociatedObject:object key:kSwizzlerAssociatedObjectKey]; | ||
if ([existingSwizzler isKindOfClass:[GULObjectSwizzler class]]) { | ||
// The object has been swizzled already, no need to swizzle again. | ||
return existingSwizzler; | ||
} | ||
|
||
self = [super init]; | ||
if (self) { | ||
__strong id swizzledObject = object; | ||
if (swizzledObject) { | ||
_swizzledObject = swizzledObject; | ||
_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."); | ||
} else { | ||
return nil; | ||
} | ||
_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; | ||
} | ||
|
@@ -98,10 +104,9 @@ - (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL | |
: class_getInstanceMethod(aClass, selector); | ||
Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass; | ||
IMP implementation = method_getImplementation(method); | ||
|
||
const char *typeEncoding = method_getTypeEncoding(method); | ||
BOOL success __unused = class_addMethod(targetClass, selector, implementation, typeEncoding); | ||
NSAssert(success, @"Unable to add selector %@ to class %@", NSStringFromSelector(selector), | ||
NSStringFromClass(targetClass)); | ||
class_replaceMethod(targetClass, selector, implementation, typeEncoding); | ||
} | ||
|
||
- (void)setAssociatedObjectWithKey:(NSString *)key | ||
|
@@ -123,11 +128,20 @@ - (nullable id)getAssociatedObjectForKey:(NSString *)key { | |
|
||
- (void)swizzle { | ||
__strong id swizzledObject = _swizzledObject; | ||
|
||
GULObjectSwizzler *existingSwizzler = | ||
[[self class] getAssociatedObject:swizzledObject key:kSwizzlerAssociatedObjectKey]; | ||
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:kSwizzlerAssociatedObjectKey | ||
value:self | ||
association:GUL_ASSOCIATION_RETAIN_NONATOMIC]; | ||
association:GUL_ASSOCIATION_RETAIN]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this atomic? Were there any thread safety issues we had on this earlier? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, there were no thread safety issues observed by me. I added it because technically the associated object may be accessed from different threads. E.g. invoking a method on some swizzled objects implies access to the associated object, see here. Though I recognize that atomic may introduce additional performance cost, I would prefer to keep it atomic since we don't have control over the swizzled object life cycle and it's better to be slower than e.g. crashing. WDYT? |
||
|
||
[GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self]; | ||
|
||
|
@@ -144,16 +158,36 @@ - (void)swizzle { | |
} | ||
} | ||
|
||
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass { | ||
// If the swizzled object had 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. | ||
- (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. | ||
if (isInstanceOfGeneratedSubclass) { | ||
objc_disposeClassPair(_generatedClass); | ||
// 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); | ||
}); | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any cleanups to the older method that needs to be done before replacing to the new implementation?
With addMethod, we would fail if an implementation already existed. With this change, we might mask the failure and force replace with the new implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My assumption here was that replacing the method implementation is a preferable action.
What type of handling we would prefer to have here? We can reintroduce the debug assertion in the case the method was replaced or we can return previous behaviour - silently don't replace the implementation and assert under DEBUG only.