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]
?
Everything I know about programming says that instance methods can call class methods, but class methods cannot call instance methods.
This post agrees...
Call instance method from class method
Yet miraculously the class method sharedInstance manages to call instance method init. What am I missing ??
static iRpDatabase *sharedDatabase;
#implementation iRpDatabase
{
}
+(iRpDatabase*)sharedInstance
{
if(sharedDatabase == nil)
{
sharedDatabase = [[self alloc] init];
}
return sharedDatabase;
}
// this is an instance method, called from class method above.
-(id)init
{
if (self = [super init]) {
someInstanceVariable = XYZ;
[self someInstanceMethod];
}
return self;
}
The statement that a class method can't call instance methods means that the class method can't call instance methods on self since self represents the class, not an instance of the class.
In the sharedInstance method you are calling an instance method but it is being called on a specific instance of the class. That's fine.
Think of this example:
+ (void)someClassMethodOfiRpDatabase {
NSString *str = #"Hello";
NSInteger len = [str length]; // look - I called an instance method
}
This example in no different than your sharedInstance method question. It's fine to call instance methods on a specific instance of an object, even if you happen to be in some class method.
As we all know, factory methods can't call instance methods. Why does the code below work?
// .m file implementation DemoClass
// custom instance init method
- (instancetype)initWithDate:(NSDate *)date {
if (self = [super init]) {
self.lastTime = date;
}
return self;
}
// custom factory method
+ (instancetype)DemoClassWithDate:(NSDate *)date
//here calling instance method initWithDate:
return [[self alloc] initWithDate:date];
}
[self alloc] will return an instance. initWithDate is just an instance method. No reason why a class method wouldn't be allowed to call an instance method on an instance.
PS. I would highly recommend that you check your compiler settings and tell the compiler to give you a warning if the result of '=' is used as a boolean value. This will prevent many hard to find bugs. You'll have to change the if to
if ((self = [super init]) != nil)
Because it has a reference to the, newly created, instance:
return [[self alloc] initWithDate:date];
// ^^^^^^^^^^^^
// reference
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;
}
I have a following code in my singleton class
static MySingleton *gManager;
+(void)initialize
{
if(self == [MySingleton class])
{
gManager = [[MySingleton alloc] initWithServices:[[MyServices alloc] init]];
}
}
+(MySingleton *)sharedInstance
{
return (gManager);
}
Unfortunately, during the unit tests I see that gManager is an instance of type SenTestCaseRun. I cant seem to figure out why?
So a call like
[[MySingleton sharedInstance] myFunction];
results in an error that myFunction is an unknown selector although it exists in the MySingleton class.
It is of type SenTestCaseRun because I checked using NSStringFromClass function.
Any pointers? Already banged my head for 3-4 hours on this :(.
it may be better to just put the initialization code inside the shared instance method
+(MySingleton *)shared
{
static MySingleton *sharedInstance = nil;
if(sharedInstance == nil){
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
also in your code you are comparing an object to a class which will never be true instead of comparing [self class] to [MySingleton class].
Put a breakpoint in +initialize to make sure this variable is set correctly. If that doesn't explain it, use a watchpoint on it to see who's modifying it.