My isa-swizzling breaks KVO - objective-c

I'm trying to implement isa swizzling because I need some actions to happen in dealloc method of certain object. I'm overriding - (Class)class; method to return original class (as KVO does). Everything works fine, until I try to add observer to swizzled object. It just crashes.
0x00000000 in 0x00000000 ()
0x0091d22a in
_NSKeyValueRetainedObservationInfoForObject ()
0x0092ec88 in -[NSObject(NSKeyValueObserverRegistration) _addObserver:forProperty:options:context:] ()
0x0092d6fd in -[NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:] ()
Here is implementation of swizzling
- (void)swizzleClass
{
NSString *proxyClassName = [NSString stringWithFormat:#"MDSwizzled_%#", NSStringFromClass(self->isa)];
Class proxyClass = NSClassFromString(proxyClassName);
if (!proxyClass)
proxyClass = [self createProxyClassWithName:proxyClassName];
object_setClass(self, proxyClass);
}
- (Class)createProxyClassWithName:(NSString *)proxyClassName
{
const char *c_proxyClassName = [proxyClassName cStringUsingEncoding:NSUTF8StringEncoding];
Class proxyClass = objc_allocateClassPair(self->isa, c_proxyClassName, 0);
Class dummyClass = [MDDummy class];
class_addMethodFromClass(proxyClass, dummyClass, #selector(dealloc));
class_addMethodFromClass(proxyClass, dummyClass, #selector(class));
objc_registerClassPair(proxyClass);
return proxyClass;
}
MDDummy it's just a class holding method in convinietn way (there is no difference between this and adding raw functions).
#implementation MDDummy
- (void)dealloc
{
//Special thinngs
[super dealloc];
}
- (Class)class
{
return //original class;
}
#end
EDIT:
Here's implementation of class_addMethodFromClass function:
void class_addMethodFromClass(Class class, Class sourceClass, SEL selector)
{
Method method = class_getInstanceMethod(sourceClass, selector);
IMP methodImplementation = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(class, selector, methodImplementation, types);
}

You should check out how Mike Ash handles this: https://github.com/mikeash/MAZeroingWeakRef
Summary: handle swizzling a KVO-swizzled subclass differently--you'll have to patch the KVO methods in the KVO subclass...

Related

UISceneDelegate of Method Swizzling in iOS13?

iOS 13 has a new way of sending app lifecycle events:
#implementation SceneDelegate
- (void)sceneDidDisconnect:(UIScene *)scene {
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
}
- (void)sceneWillResignActive:(UIScene *)scene {
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
}
#end
SWIZZLE code:
#implementation UIScene (SWIZZLE)
- (void)my_setDelegate:(id <UISceneDelegate>)delegate {
// do custom work
[self my_setDelegate:delegate];
}
+ (void)load {
[self swizzle:#selector(setDelegate:) with:#selector(my_setDelegate:)];
}
#end
typedef IMP *IMPPointer;
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if (!imp) {
imp = method_getImplementation(method);
}
}
if (imp && store) { *store = imp; }
return (imp != NULL);
}
#implementation NSObject (RuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
return class_swizzleMethodAndStore(self, original, replacement, store);
}
#end
Document:
#pragma mark - Delegate
// UIScene is strongly retained by UIKit like UIApplication, however, unlike UIApplication, the delegate may not need to live for the whole lifetime of the process.
// A strong ref here relieves clients of the responsibility of managing the delegate lifetime directly.
#property (nullable, nonatomic, strong) id<UISceneDelegate> delegate;
that is no't work ,how to do swizzle UIWindowScene delegate ?

Can you cast an Objective-C object using a Class object?

By Objective-C object I mean something like MyViewController, and a class object MyViewController.superclass.
For example, in this function how would you cast self using targetClass?
// This code doesn't compile
- (void) useOtherClassImplementation :(Class) targetClass :(SEL) targetSelector {
if ([self isKindOfClass: targetClass]) {
((void (*)(id, SEL))[((targetClass *) self) methodForSelector:selector])(self, selector);
}
}
Is there a way to do something like ((targetClass *) self), which doesn't compile?
Case study
Overview:
When ViewController appears, ViewController.viewDidAppear is called and the swizzled implementation runs. After ViewController.viewDidAppear swizzled implementation runs, the original implementation is called. Good.
When the ViewController.viewDidAppear original implementation runs, UIViewController.viewDidAppear is called by super.viewDidAppear(). The swizzled implementation for UIViewController.viewDidAppear is called and run, and in that swizzled implementation self is used to call the original implementation BUT since self is ViewController and not UIViewController at runtime, ViewController.viewDidAppear swizzled implementation is called again and thus a recursive loop begins.
In other words, the recursive loop starts when a child's method, which has been swizzled, calls its super's method, which has also been swizzled. In the swizzled method self is used to call the original implementation, and since self at runtime is the most child class (in this example ViewController) the super's swizzled method calls the child's original method again, and so the cycle repeats.
Goal:
Find a way to call a swizzled class's original implementation.
When self at runtime could be some child, and both the parent and child have their methods swizzled where the child method calls the parent method, there has to be a way to explicitly choose which class's implementation to run by using the runtime function class_getInstanceMethod
Tried and failed:
Casting self as another class because I cannot find out how to use the Class object to cast. To use this swizzling code in a more generic case, a Class object storing the original class has to be used instead of explicitly writing the class type.
ViewController.swift
// Child class ViewController inherits from parent class UIViewController
class ViewController: UIViewController {
override func viewDidLoad() {
_ = ViewController.swizzleViewDidAppearParentAndChild
}
override func viewDidAppear(_ animated: Bool) {
// NOTICE the call to parent's function
super.viewDidAppear(animated)
// never reaches here
print("In viewcontroller viewdidappear")
}
// swizzles in the block for both UIViewController and ViewController
// recursively prints
// TestApp.ViewController is about to perform viewDidAppear:
//
static var swizzleViewDidAppearParentAndChild: Void = {
SwizzledObject.createTrampoline(for: UIViewController.self, selector: #selector(UIViewController.viewDidAppear(_:)), with: printBeforePerforming)
SwizzledObject.createTrampoline(for: ViewController.self, selector: #selector(ViewController.viewDidAppear(_:)), with: printBeforePerforming)
}()
// a block to be used before a method call
static var printBeforePerforming: SelectorTrampolineBlock {
return { target, selector in
print("\(NSStringFromClass(type(of: target as AnyObject))) is about to perform \(NSStringFromSelector(selector!))")
}
}
}
NSObject+Swizzling.h
#import <Foundation/Foundation.h>
#interface SwizzledObject : NSObject
typedef void (^ SelectorTrampolineBlock)(id target, SEL targetSelector);
+ (SEL) createTrampolineForClass:(Class)targetClass selector:(SEL)targetSelector withBlock:(SelectorTrampolineBlock) block;
#end
NSObject+Swizzling.m
#import "NSObject+Swizzling.h"
#import <objc/runtime.h>
#implementation SwizzledObject
// creates a method at runtime that calls the trampolineBlock, and then performs original method
+ (SEL) createTrampolineForClass:(Class)targetClass selector:(SEL)targetSelector withBlock:(SelectorTrampolineBlock) block {
SEL trampolineSelector = NSSelectorFromString([NSString stringWithFormat:#"performBefore__%#", NSStringFromSelector(targetSelector)]);
Method originalMethod = class_getInstanceMethod(targetClass, targetSelector);
if (originalMethod == nil) {
return nil;
}
IMP dynamicImp = imp_implementationWithBlock(^(id self, bool param) {
block(self, targetSelector);
if (!self || ![self respondsToSelector:trampolineSelector]) {return;}
((void (*)(id, SEL, bool))[self methodForSelector:trampolineSelector])(self, trampolineSelector, param);
});
class_addMethod(targetClass, trampolineSelector, dynamicImp, method_getTypeEncoding(originalMethod));
Method newMethod = class_getInstanceMethod(targetClass, targetSelector);
if (newMethod == nil) {
return nil;
}
[SwizzledObject injectSelector:targetClass :trampolineSelector :targetClass :targetSelector];
return trampolineSelector;
}
// Switches/swizzles method
+ (BOOL) injectSelector:(Class) swizzledClass :(SEL) swizzledSelector :(Class) originalClass :(SEL) orignalSelector {
NSLog(#"Injecting selector %# for class %# with %#", NSStringFromSelector(orignalSelector), NSStringFromClass(originalClass), NSStringFromSelector(swizzledSelector));
Method newMeth = class_getInstanceMethod(swizzledClass, swizzledSelector);
IMP imp = method_getImplementation(newMeth);
const char* methodTypeEncoding = method_getTypeEncoding(newMeth);
BOOL existing = class_getInstanceMethod(originalClass, orignalSelector) != NULL;
if (existing) {
class_addMethod(originalClass, swizzledSelector, imp, methodTypeEncoding);
newMeth = class_getInstanceMethod(originalClass, swizzledSelector);
Method orgMeth = class_getInstanceMethod(originalClass, orignalSelector);
method_exchangeImplementations(orgMeth, newMeth);
}
else {
class_addMethod(originalClass, orignalSelector, imp, methodTypeEncoding);
}
return existing;
}
#end
Output
2018-04-04 17:50:43.201458-0700 TestApp[26612:6527489] Injecting selector viewDidAppear: for class UIViewController with performBefore__viewDidAppear:
2018-04-04 17:50:43.202641-0700 TestApp[26612:6527489] Injecting selector viewDidAppear: for class TestApp.ViewController with performBefore__viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
(infinitely prints previous line)
Here is an example of how you might do it:
- (void)useSuperclassImplementation:(Class)targetClass targetSelector:(SEL)targetSelector {
if ([self isKindOfClass: targetClass] && [targetClass respondsToSelector:targetSelector]) {
((void (*)(id, SEL))[targetClass methodForSelector:targetSelector])(self, targetSelector);
}
}
You could use [targetClass performSelector:targetSelector]; and ignore the warning
There's a detailed explanation of the solution on this answer: https://stackoverflow.com/a/20058585/1755720
edit:
struct objc_super superInfo = {
.receiver = [self class],
.super_class = targetClass
};
id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper;
(*objc_superAllocTyped)(&superInfo, targetSelector);
^ is also another option to invoke super directly, but it's not too safe as you would really need to be certain the target class is the superclass - and I need to ask, why are you doing this? There might be a simpler solution to the problem.
Phrasing this as casting is just confusing, to the reader and probably yourself. Type casting is a purely static, compile-time thing. targetClass, being a variable, is, of course, a dynamic, run-time thing. At run-time, the static type of the message receiver expression has no bearing on the behavior of the code. That information is more or less gone at that point. Both [self someMethod...] and [(AnyType*)self someMethod...] will have been compiled to the exact same code.
Are you just looking for:
[targetClass instanceMethodForSelector:selector]
where you currently have:
[((targetClass *) self) methodForSelector:selector]
?

Singleton in Objective C (Non-ARC)

I have found how to implement singleton in objective c (Non-ARC).
Matt Galloway's singleton
// AppTools.h in my code
#interface AppTools : NSObject {
NSString *className;
}
#property ( nonatomic, retain ) NSString *className;
+ ( id ) sharedInstance;
#end // AppTools
// AppTools.m in my code
static AppTools *sharedAppToolsInstance = nil;
#implementation AppTools
#synthesize className;
- ( id ) init {
self = [ super init ];
if ( self ) {
className = [ [ NSString alloc ] initWithString: #"AppTools" ];
}
return self;
} // init
- ( void ) dealloc {
// Should never be called, but just here for clarity really.
[ className release ];
[ super dealloc ];
} // dealloc
+ ( id ) sharedInstance {
#synchronized( self ) {
if ( sharedAppToolsInstance == nil )
sharedAppToolsInstance = [ [ super allocWithZone: NULL ] init ];
}
return sharedAppToolsInstance;
} // sharedInstance
+ ( id ) allocWithZone: ( NSZone * )zone {
return [ [ self sharedInstance ] retain ];
} // allocWithZone:
- ( id ) copyWithZone: ( NSZone * )zone {
return self;
} // copyWithZone:
- ( id ) retain {
return self;
} // retain
- ( unsigned int ) retainCount {
return UINT_MAX; // denotes an object that cannot be released
} // retainCount
- ( oneway void ) release {
// never release
} // release
- ( id ) autorelease {
return self;
} // autorelease
I'd like to know how to work allocWithZone: in sharedInstance method.
On this, the allocWithZone: method's receiver is 'super' and 'super' is NSObject.
Though return value is NSObject instance, it is substituted to sharedInstance.
Where is className's memory room then?
I don't know how to work this part of the code.
Thank in advance.
I think that code that you have posted is too difficult to use for creating singletons.
In all my project I am using the following code to use singletons. It is very simple, thread-safe and works perfectly:
+ (CustomClass *)shared
{
static CustomClass *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[CustomClass alloc] init];
});
return singleton;
}
And I believe you could use it in ARC and non-ARC projects.
UPDATE:
As mentioned in comments it is actually shared object and not singleton because there could be created more the one instance of particular class. But it is close enough.
In my opinion, if you are not writing open source code/libraries and nobody else will use it then it might be much easier to use shared objects and treat them as singletons.
I use the same code as Nekto but singleton = [[CustomClass alloc] init]; should be singleton = [[self alloc] init];.
Imagine a subclass CustomSubclass. If you call
CustomSubclass *sharedObject = [CustomSubclass shared];
you will not get a CustomSubclass but a CustomClass.
You ask "Where is className's memory room then?"
Most classes do not implement alloc or allocWithZone themselves but rely on the implementation inherited from NSObject. The NSObject implementation allocates an object of the original calling class.
So in your example AppTools does override allocWithZone, this implementation invokes NSObject's allocWithZone via a call to super, and NSObject's method performs the actual allocation and returns an object of type AppTools.
[Note: If you are wondering how NSObject's implementation knows what kind of object to allocate then this is simple - calling an inherited method does not alter the self argument to the method, alloc/allocWithZone are class methods, and the self argument of a class method references the class object (rather than an instance object of the class) itself.]

Mocking a method in OCMock for all instances of a class

I want to mock an instance method for all instances of a class using OCMock however I don't have the instance of the class to override it rather it is created inside the method that I'm testing.
So my question is: is it possible to override this method for all instances of a class or will I need to inject that instance into the method rather than create it inside the method?
i.e.
[[ClassThatHasTheInstanceMethodToOverride andCall:#selector(callThisMethodInstead) onObject:self] someInstanceMethod];
I got there in the end with this set of methods:
Method originalMethod = nil;
Method swizzleMethod = nil;
#import <objc/runtime.h>
....
- (void) swizzleInstanceMethodForInstancesOfClass:(Class)targetClass selector:(SEL)selector
{
originalMethod = class_getInstanceMethod(targetClass, selector);
swizzleMethod = class_getInstanceMethod([self class], selector);
method_exchangeImplementations(originalMethod, swizzleMethod);
}
- (void) deswizzle
{
method_exchangeImplementations(swizzleMethod, originalMethod);
swizzleMethod = nil;
originalMethod = nil;
}

How to simplify callback logic with a Block?

Let's say I need to communicate with a class that provides a protocol and calls delegate methods when an operation is complete, as so:
#protocol SomeObjectDelegate
#required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
#interface SomeObject : NSObject
{
}
#end
Now, I've decided that while I could make another class implement the stuffDone: delegate method, I've decided that I'd rather encapsulate the process into a block which is written somewhere close to where SomeObject is instantiated, called, etc. How might I do this? Or in other words, if you look at this famous article on blocks (in the Replace Callbacks section); how might I write a method in SomeObject that accepts a completionHandler: of sorts?
It sounds like you wish to communicate with an existing class which is designed to take a delegate object. There are a number of approaches, including:
using a category to add block-based variants of the appropriate methods;
use a derived class to add the block-based variants; and
write a class which implements the protocol and calls your blocks.
Here is one way to do (3). First let's assume your SomeObject is:
#protocol SomeObjectDelegate
#required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
#interface SomeObject : NSObject
{
}
+ (void) testCallback:(id<SomeObjectDelegate>)delegate;
#end
#implementation SomeObject
+ (void) testCallback:(id<SomeObjectDelegate>)delegate
{
[delegate stuffDone:[NSNumber numberWithInt:42]];
[delegate stuffFailed];
}
#end
so we have some way to test - you will have a real SomeObject.
Now define a class which implements the protocol and calls your supplied blocks:
#import "SomeObject.h"
typedef void (^StuffDoneBlock)(id anObject);
typedef void (^StuffFailedBlock)();
#interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate>
{
StuffDoneBlock stuffDoneCallback;
StuffFailedBlock stuffFailedCallback;
}
- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;
- (void)dealloc;
+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;
// protocol
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
This class saves the blocks you pass in and calls them in response to the protocol callbacks. The implementation is straightforward:
#implementation SomeObjectBlockDelegate
- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
if (self = [super init])
{
// copy blocks onto heap
stuffDoneCallback = Block_copy(done);
stuffFailedCallback = Block_copy(fail);
}
return self;
}
- (void)dealloc
{
Block_release(stuffDoneCallback);
Block_release(stuffFailedCallback);
[super dealloc];
}
+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease];
}
// protocol
- (void)stuffDone:(id)anObject
{
stuffDoneCallback(anObject);
}
- (void)stuffFailed
{
stuffFailedCallback();
}
#end
The only thing you need to remember is to Block_copy() the blocks when initializing and to Block_release() them later - this is because blocks are stack allocated and your object may outlive its creating stack frame; Block_copy() creates a copy in the heap.
Now you can all a delegate-based method passing it blocks:
[SomeObject testCallback:[SomeObjectBlockDelegate
someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(#"Done: %#", anObject); }
andOnFail:^{ NSLog(#"Failed"); }
]
];
You can use this technique to wrap blocks for any protocol.
ARC Addendum
In response to the comment: to make this ARC compatible just remove the calls to Block_copy() leaving direct assignments:
stuffDoneCallback = done;
stuffFailedCallback = fail;
and remove the dealloc method. You can also change Blockcopy to copy, i.e. stuffDoneCallback = [done copy];, and this is what you might assume is needed from reading the ARC documentation. However it is not as the assignment is to a strong variable which causes ARC to retain the assigned value - and retaining a stack block copies it to the heap. Therefore the ARC code generated produces the same results with or without the copy.
You could do something like this:
typedef void (^AZCallback)(NSError *);
AZCallback callback = ^(NSError *error) {
if (error == nil) {
NSLog(#"succeeded!");
} else {
NSLog(#"failed: %#", error);
}
};
SomeObject *o = [[SomeObject alloc] init];
[o setCallback:callback]; // you *MUST* -copy the block
[o doStuff];
...etc;
Then inside SomeObject, you could do:
if ([self hadError]) {
callback([self error]);
} else {
callback(nil);
}
The below link explains how the call backs using delegates could be easily replaced with blocks.
The examples includes UITableview,UIAlertview and ModalViewController.
click me
Hope this helps.