What is the difference when swizzling between these 2 mechanisms - ios

There is a slight variation between these 2 ways of swizzling. I just want clarification if there is something fundamentally different or wrong between them
Assuming we are swizzling viewDidLoad on UIView
First way (using class_addMethod):
#implementation UIView (SwizzleFirstWay)
+ (void)load {
SEL originalSelector = #selector(viewDidLoad);
SEL swizzledSelector = #selector(swizzled_viewDidLoad);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
class_addMethod(self,
originalSelector,
class_getMethodImplementation(self, originalSelector),
method_getTypeEncoding(originalMethod));
// Adding the method
class_addMethod(self,
swizzledSelector,
class_getMethodImplementation(self, swizzledSelector),
method_getTypeEncoding(swizzledMethod));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (void)swizzled_viewDidLoad {
// ... the swizzled implementation
// ...
// ...
[self swizzled_viewDidLoad]; // calling back to the original implementation
}
#end
Second way (without using class_addMethod):
+ (void)load {
SEL originalSelector = #selector(viewDidLoad);
SEL swizzledSelector = #selector(swizzled_viewDidLoad);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
// NOT USING class_addMethod
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (void)swizzled_viewDidLoad {
// ... the swizzled implementation
// ...
// ...
[self swizzled_viewDidLoad]; // calling back to the original implementation
}
#end

Very good question, actually. If you use method_exchangeImplementations without calling class_addMethod first, you can accidentally swizzle your superclass implementation. The reason for that is class_getInstanceMethod searches superclasses for inherited implementation in a case when a method is not implemented in the class itself. And it is, obviously, not something you want to achieve.
A quick example. Using following code
#interface UIWebView (SwizzlingTest)
- (void)swizzled_removeFromSuperview {
[self swizzled_removeFromSuperview];
}
+ (void)load {
SEL originalSelector = #selector(removeFromSuperview);
SEL swizzledSelector = #selector(swizzled_removeFromSuperview);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
#end
Results in
-[UICheckeredPatternView swizzled_removeFromSuperview]: unrecognized selector sent to instance 0x101b24250
because removeFromSuperview is inherited from UIView, but not implemented in UIWebView.

Related

How to hide the home indicator for iPhone X writing a common code in app delegate

In my project i would like to hide the home indicator without writing the same code in every view controller and instead implement it in the appDelegate. I tried
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor yellowColor];
return YES;
}
-(BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
I tried like this but its not working commonly. So how can i hide the home indicator without writing in every view controllers and instead implementing from app delegate itself?
There is a way to accomplish this. In my opinion it's not the most elegant and I would agree with Gereon that you'd be better off with creating a subclass of UIViewController, implement it there and then have all your view controllers inherit from that base class.
You can however accomplish this using Method Swizzling. See it here: https://nshipster.com/method-swizzling/. In your case you can swizzle it in AppDelegate in application: didFinishLaunchingWithOptions: and swizzle prefersHomeIndicatorAutoHidden to your custom function that returns YES.
So for swizzling I'd suggest you create a new Category of the UIViewController. And the actual swizzling:
#import <objc/runtime.h>
#implementation UIViewController (Swizzling)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(prefersHomeIndicatorAutoHidden);
SEL swizzledSelector = #selector(swizzledPrefersHomeIndicatorAutoHidden);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
const BOOL didAdd = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAdd)
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
else
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES; //Doesn't matter what you return here. In this you could return the actual property value.
}
- (BOOL)swizzledPrefersHomeIndicatorAutoHidden //This is the actual `prefersHomeIndicatorAutoHidden ` call
{
return YES;
}
#end

Click button quickly push UIViewController twice

example:
I have two buttons in current view, when I click the button it will push a UIViewController in the current UINavgitionController.
This is my code:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationController?.pushViewController(pvc, animated: true)
})
But I found a bug. When I click the button quickly, the UIViewController push twice, why does that happen?
PS: Now, I have used this code in lots of UIViewControllers, more than 200 times.
I was also getting the same issue, i resolved it by creating category of UIButton which will only take exclusive touch.
Create Category of UIButton
#import <objc/runtime.h> //Please import
#implementation UIButton (ExclusiveTouch)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(willMoveToSuperview:);
SEL swizzledSelector = #selector(inc_willMoveToSuperview:);
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)inc_willMoveToSuperview:(UIView *)newSuperview
{
// This is correct and does not cause an infinite loop!
// See the link for an explanation
[self inc_willMoveToSuperview:newSuperview];
[self setExclusiveTouch:YES];
}

Method Swizzling doesn't work for category of UIViewController

I use the normal swizzle method:
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
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);
}
}
I want to exchange viewWillAppear: with xxx_viewWillAppear:. So I create a category of UIViewController and create the method xxx_viewWillAppear:.
If I use dispatch_once in the +(void)load method to call swizzleMethod, everything goes wrong.
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzleMethod([self class], #selector(viewWillAppear:), #selector(xxx_viewWillAppear:));
});
}
It would call the viewWillAppear: in the UIViewController, and when [super viewWillAppear:animated] was called, xxx_viewWillAppear was called.
But if I put the load method in the UIViewController (not in the category), it goes right.
So, Why?
I use xcode 6 and iOS 8.

Apply global class to UIImage

Now I'm not sure if this is possible in Objective-C but I would expect it should be.
I have multiple images across my storyboard using the following/similar code on UIImages
cell.followingImage.layer.cornerRadius = cell.followingImage.frame.size.width / 2;
cell.followingImage.clipsToBounds = YES;
I want to optimise the code. I know you can apply classes to objects but I have never done this before. Is it possible to apply the class via the storyboard and automatically run that code without referencing and adding it to every controller?
So if a class is attached to the UIImage it would just run that code and make the UIImage a circle? This is probably really simple...
If I was to subclass a UIimage I'm not sure where to put the above code?
Try making a category of UIImage. Include everything you want in this custom category class like your cornerRadius and your clipToBounds code. Then when you init the images, don't init the images with [UIImage new] but with [customImageCategoryName new[. Now, all these images will, by default, have those two lines of code. The following link will show you how to create a category in Xcode How do I create a category in Xcode 6 or higher?
Do place that code in
-(instancetype)initWithCoder:(NSCoder *)aDecoder method , -(instancetype)initWithFrame:(CGRect)frame method
and init method.
That way if the imageview is from storyboard the initWithCoder will get called and other methods get called when it is programatically added.
As none of the other answers really worked nor were complete enough I used User Defined Runtime Attributes instead.
To avoid the need to write it in code I added
layer.cornerRadius with Number attribute half the width.
layer.masksToBounds with Bool attribute as YES
Method swizzling is a very good option to achieve this.
Create a UIImageView category class and swizzle initWithCoder: method
#import "UIImageView+MethodSwizzling.h"
#import <objc/runtime.h>
#implementation UIImageView (MethodSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(initWithCoder:);
SEL swizzledSelector = #selector(xxx_initWithCoder:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// 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);
}
});
}
#pragma mark - Method Swizzling
- (id)xxx_initWithCoder:(NSCoder*)aDecoder {
[self xxx_initWithCoder:aDecoder];
self.layer.cornerRadius = self.frame.size.width / 2;
self.layer.masksToBounds = YES;
NSLog(#"xxx_initWithCoder: %#", self);
return self;
}
You don't have to create subclass of UIImageView and do not need to change your object's class everywhere in XIB/Storybord.
Click here to know about method swizzling.
What your "answer" suggests is that you are using UIImageViews in the Storyboard. The best approach would be to create a subclass of UIImageView, e.g.:
//MyImageView.h
#import <UIKit/UIKit.h>
#interface MyImageView : UIImageView
#end
The code to adapt the cornerRadius and the clipsToBounds should be added to the MyImageView.m:
//MyImageView.m
- (id)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.view.frame.width.width / 2
self.layer.clipsToBounds = true;
// this line may be only needed in init since the clipsToBounds does not change due to autoLayout.
}
After writing that code, you need to set the class attribute of all UIImageViews in the Storyboard (all those that should be circular) to MyImageView instead of the default UIImageView.

Method Swizzling not firing correctly?

I am following this article to better understand how method swizzling works. I have my main view controller (this is a new project) like this:
#import "GLViewController.h"
#import <objc/runtime.h>
#implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [UIViewController class];
SEL originalSelector = #selector(viewWillAppear:);
SEL swizzledSelector = #selector(xxx_viewWillAppear:);
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)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(#"viewWillAppear: %#", self);
}
#end
#implementation GLViewController
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"glviewappear");
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If I run this, then it prints glviewappear, and if I remove
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"glviewappear");
}
then it prints viewWillAppear: <GLViewController: 0x9d11a90>. My project needs to be able to fire on both of these methods. Is there a way to do this?
There is a simple reason. You are not calling the UIViewController implementation since you are not calling [super viewWillAppear:animated].
Just do:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"glviewappear");
}
Here is a more detailed explanation
Each method in an Objective C class is nothing more than a simple function, referenced through a pointer saved in a dispatch table: the keys are the selectors, and their associated values are the pointers to each method implementation (the IMP).
When you swizzle, you just swap two pointers in the dispatch table, in order to swap the referenced functions. Since the dispatch table is attached to the class, swizzling happens only in the class on which you perform it, and not in the subclasses.
In your case, there are 3 different functions in play:
UIViewController has the pointers to the following functions in the dispatch table. These functions get swapped at runtime through swizzling.
viewWillAppear: (now pointing to xxx_viewWillAppear: implementation)
xxx_viewWillAppear: (now pointing to viewWillAppear: implementation)
GLViewController has another pointer, to its own implementation of viewWillAppear:.
If you don't call super, you are not accessing the dispatch table of the UIViewController class, so you are not invoking the implementation at all.
When you delete your viewWillAppear: method, obviously it works, since the super implementation gets automatically called.

Resources