I've implemented a Swizzling category for UIViewControllers, which just NSlogs when they are presented:
#import "UIViewController+Logger.h"
#import <objc/runtime.h>
#implementation UIViewController (Logger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(presentViewController:animated:completion:);
SEL swizzledSelector = #selector(logAndPresentViewController:animated:completion:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)logAndPresentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion {
NSLog(#"viewControllerToPresent: %#", viewControllerToPresent);
[self logAndPresentViewController:viewControllerToPresent animated:flag completion:completion];
}
#end
Obviously, this works fine only inside the swizzled application, but I was wondering if I can also "hook" the shared presentViewController so that it is called when a presenting a view outside my application. I was thinking to maybe load the UIViewController dynamically with dlopen and than get a pointer to it's global image symbols. Is it possible? If not - way isn't it?
Disclaimer - I am doing this for debugging on my own development device, and will not upload the app to App Store.
Related
Under MRC, "Method Swizzling" was used to exchange system method RETAIN. If no other code is added in the new method, the error will not be reported. I added NSLog and the error will be reported
Is the Runtime symbol binding changing the implementation of a retained method?
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface NSObject (whenRetained)
- (void)swizzle;
#end
NS_ASSUME_NONNULL_END
#import "NSObject+whenRetained.h"
#import <objc/runtime.h>
#implementation NSObject (whenRetained)
- (void)swizzle {
// NSLog(#"123"); // The program wouldn't crash without it
[self swizzle];
// NSLog(#"123"); //The program wouldn't crash without it
}
+ (void)load {
Class class = [self class];
SEL originalSelector = #selector(release);
SEL swizzledSelector = #selector(swizzle);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
#end
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc] init];
[obj release];
}
Picture 1
Picture 2
First of all, you should add the involved code text instead of posting screen shots.
To your question: Your code is illegal, no matter if you add NSLog or not to the swizzle implementation.
The problem is that you call swizzle twice, this will lead to a double call to release, so your object gets overreleased hence your program crashes.
If this crash only appears when you add extra code to your swizzle implementation, this is just some coincidence.
I'm trying out a code example of The NSHipster Fake Book to swizzle the viewWillAppear: method of UIViewController. But It seems that it doesn't work expectedly since the xxx_viewWillAppear: method has never been invoked. I just cannot find out why. Please help me, thanks.
#import "ViewController.h"
#import <objc/runtime.h>
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#end
#implementation UIViewController (Tracking)
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = object_getClass(self);
SEL originalSelector = #selector(viewWillAppear:);
SEL swizzledSelector = #selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL addMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (addMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
-(void)xxx_viewWillAppear:(BOOL)animated
{
[self xxx_viewWillAppear:animated];
NSLog(#"viewWillAppear: %#",self);
}
#end
Since load is a class method, the self in Class cls = object_getClass(self); refers to the meta class of UIViewController, not the actual class you want (UIViewController).
If you change it to Class cls = [self class];, then cls will be the UIViewController class itself and it should work.
For QA purposes I want to ensure that instances of specific classes have been deallocated properly (so the actual count of instances is sufficient). I looked into the Objective-C runtime reference, but I could not find the appropriate function. I have checked similar questions but did not find a satisfying answer.
Edit
I took TheCodingArt's mockup and completed it, the result can be obtained at
https://www.generomobile.de/gmi/allocchecker.m
The difficulty was to swizzle dealloc because ARC forbids passing the selector of dealloc for swizzling. I stumbled across this interesting swizzling tutorial at http://defagos.github.io/yet_another_article_about_method_swizzling/
NSString and other class clusters obviously are not freed by dealloc as one can see in the sample. But for my own classes it works our current IOS project and gives some interesting insights.
One solution is to setup a static counter in your .m file. Increment the counter in the designated init method and decrement the counter in the dealloc method. Provide a class method to read the count value.
Don't do this as a rule. This should only be done for testing.
Lets say you want to track the instance count of SomeClass. You can do:
SomeClass.h
#interface SomeClass : NSObject
+ (NSInteger)instanceCount;
// everything else you need
#end
SomeClass.m
#import "SomeClass.h"
static NSInteger instanceCount = 0;
#implementation SomeClass
- (instancetype)init {
self = [super init];
if (self) {
instanceCount++;
// everything else you need
}
return self;
}
// all your other code
+ (NSInteger)instanceCount {
return instanceCount;
}
- (void)dealloc {
instanceCount--;
}
#end
Per request, I've mocked up a category that will keep count of allocated objects using method swizzling and a singleton. This was a quick mock up, so there are a few issues with it (one being that initializing anything that is contained within the storage class will cause an infinite loop). Mind you, this is for keeping track of many objects and should not be used in a production environment. The best methodology overall is to use the instruments tool.
#import "NSObject+Initializer.h"
#import <objc/runtime.h>
#interface ObjectCounter : NSObject
+ (instancetype)sharedObjectCounter;
#property (strong, nonatomic) NSMutableDictionary *objectCounterDictionary;
#end
#implementation ObjectCounter
+ (instancetype)sharedObjectCounter
{
static ObjectCounter *objectCounter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objectCounter = [ObjectCounter new];
objectCounter.objectCounterDictionary = [NSMutableDictionary new];
});
return objectCounter;
}
#end
#implementation NSObject (Initializer)
+ (NSNumber *)initializedCount
{
NSLog(#"Dict: %#", [ObjectCounter sharedObjectCounter].objectCounterDictionary);
return [ObjectCounter sharedObjectCounter].objectCounterDictionary[NSStringFromClass([self class])];
}
+ (id)alloc_swizzled
{
NSLog(#"Swizzled");
NSString *className = NSStringFromClass([self class]);
if (![className isEqualToString:NSStringFromClass([NSMutableDictionary class])] && ![className isEqualToString:NSStringFromClass([ObjectCounter class])]) {
ObjectCounter *counter = [ObjectCounter sharedObjectCounter];
NSMutableDictionary *objectDictionary = counter.objectCounterDictionary;
NSNumber *count = objectDictionary[className];
count = count ? #(count.integerValue + 1) : #0;
objectDictionary[className] = count;
}
return [self alloc_swizzled];
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(alloc);
SEL swizzledSelector = #selector(alloc_swizzled);
Method originalMethod = class_getClassMethod(class, originalSelector);
Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
You can get a count without subclassing by adding a category and overriding alloc, This is really hacky but it's fine for testing and good for classes you don't own. I have no idea if this would work without ARC.
#implementation UILabel (LableCounter)
static NSInteger labelCount = 0;
+ (id)alloc
{
labelCount++;
return [super alloc];
}
-(void)dealloc{
labelCount--;
}
#end
I'm trying to swizzle some methods in an application made with Objective-C. I'm getting this error:
Symbol not found: _OBJC_CLASS_$_IASAvatarViewController
After opening the executable in Hopper, I see that all of the classes are prefixed with objc_class_ instead. So the class name of the method I'm trying to swizzle is objc_class_IASAvatarViewController. First off, I'm dying to know how the class identifiers have turned out that way (some sort of name mangling?). And second, I'd like to know if it's possible to have my dylib reference the correct identifier.
DylibTest.h:
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#interface IASBaseViewController : UIViewController
#end
#interface IASAvatarViewController : IASBaseViewController
#end
#interface IASAvatarViewController (Swizzle)
#end
DylibTest.m
#import "DylibTest.h"
#import <dlfcn.h>
#implementation IASAvatarViewController (Swizzle)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(viewDidLoad);
SEL swizzledSelector = #selector(xxx_viewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xxx_viewDidLoad {
[[[UIAlertView alloc] initWithTitle:#"Swizzled method" message:#"Ya swizzle" delegate:nil cancelButtonTitle:#"Yeah. Okay" otherButtonTitles:nil] show];
[self xxx_viewDidLoad];
}
#end
And a screen shot of Hopper with the Objective-C classes:
Edit: Got enough reputation to directly post image.
Edit2: class-dump of the file: http://pastebin.com/DcUD5AL5
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
How to trigger an action in a Class Category when the object gets deallocated without subclassing and overriding the dealloc method. I'm trying to implement a category on UIView.
swizzle dealloc. it is evil but we do it too :D
in +load of your category swizzle dealloc and you're in
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface T : NSObject
#end
#interface T (myCat)
#end
#implementation T
- (void)dealloc {
NSLog(#"2");
}
#end
#implementation T (myCat)
+ (void)load {
SEL originalSelector = #selector(NSSelectorFromString(dealloc));
SEL overrideSelector = #selector(xchg_dealloc);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
- (void)xchg_dealloc {
NSLog(#"1");
[self xchg_dealloc]; //calls original
}
#end
IF you don't like swizzling there is a maybe less evil but more fragile & convoluted way
you use a Helper object that gets deallocated by the runtime as some sort of marker
#interface T_Helper : NSObject
#public
__weak T *parent;
#end
#implementation T_Helper
- (void)dealloc {
[parent my_dealloc];
}
#end
#implementation T (myCat)
- (void)doSomethingThatLaterWantsDealloc {
T_Helper *helper = [T_Helper alloc] init];
helper->parent = self;
objc_setAssociatedObject(self, "helper", helper, OBJ_ASSOSIATION_RETAIN);
}
- (void)my_dealloc {
NSLog(#"1");
}
#end