I am trying to learn the concept of swizzling.
Even though I have added method_exchangeImplementations, still the methods are not being swizzled. Any idea as to where I am going wrong?
#import <objc/runtime.h>
#interface POCViewController ()
- (void)addSwizzle;
- (void)originalMethod;
- (void)swizzledMethod;
#end
#implementation POCViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Add swizzle
[self addSwizzle];
//Call the original method
[self originalMethod];
}
- (void)addSwizzle
{
Method original, swizz;
original = class_getClassMethod([self class], #selector(originalMethod));
swizz = class_getClassMethod([self class], #selector(swizzledMethod));
method_exchangeImplementations(original, swizz);
}
- (void)originalMethod
{
NSLog(#"Inside original method");
}
- (void)swizzledMethod
{
NSLog(#"Inside swizzled method");
[self swizzledMethod];
}
You are using class_getClassMethod to get implementations of instance methods, you should use class_getInstanceMethod instead.
method_exchangeImplementations is still used the same way
Related
I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
While referring a sample code i found this snippet can any explain why it is used.
- (id)init
{
self = [super init];
if (self) {
[[self view]setBackgroundColor:[UIColor redColor]];
}
return self;
}
and what is the difference between the following snippet.
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
init and viewDidLoad both are completely different.
viewDidLoad called, when the view is loaded into memory, this method called once during the life of the view controller object. It's a great place to do any view initialization.
init method is an initializer method. Cocoa has various types of intializer. To learn more, please check the link,
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html
I am a little new to iOS development, coming from a Java / Android background. My understanding is that your custom Protocols or Delegates are like Interfaces in Java land.
If that is the case then I believe these Protocols are also Objects as well.
Case:
Assume 2 ViewControllers, Home and Profile.
1 Presenter, let's call it StuffPresenter gets instantiated individually in both ViewControllers.
StuffPresenter has an initialization method called initWithInteractor that takes in a parameter of Interactor which is a protocol.
Both Home and Profile implement a Protocol called Interactor, which has a method called initStuffInTableView(NSMutableArray *)stuff.
So I have a dilemma where if I am in Home and StuffPresenter relays information then I switch over to Profile, StuffPresenter loads stuff in Home as well as Profile.
Why is this the case?
Here is the code I have setup:
Protocol
#protocol Interactor <NSObject>
- (void)initStuffInTableView:(NSMutableArray *)stuff;
#end
Presenter
#interface Presenter : NSobject
- (id)initWithInteractor:(id<Interactor>)interactor;
- (void)loadStuff;
#end
#implementation {
#private
id<Interactor> _interactor;
}
- (id)initWithInteractor:(id<Interactor>)interactor {
_interactor = interactor;
return self;
}
- (void)loadStuff {
// Load stuff
NSMutableArray *stuff = // Init stuff in array...
[_interactor initStuffInTableView:stuff];
}
#end
Home
#interface HomeViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
[self initPullToRefresh];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
}
- (void)initPullToRefresh {
// Init pull to refresh
// ...
[self.refreshControl addTarget:self
action:#selector(reloadStuff)
forControlEvents:UIControlEventValueChanged];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - reloadStuff
- (void)reloadStuff {
[_stuffPresenter loadStuff];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Profile
#interface ProfileViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
[_stuffPresenter loadStuff];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Problem:
When I go to Profile the app crashes, because initStuffInTableView is being called in Home. Why is this the case?
A protocol is an Objective-C language feature for specifying that a Class (or another protocol) has certain features, for the benefit of the compiler/ARC/the programmer.
A delegate, or delegation, is a design pattern which makes Model View Controller easier. To make the object doing the delegation be more flexible, generally its delegate adopts a protocol.
There are a number of issues in your code:
Your Presenter has a reference cycle with its interactors
You need to call some init method that eventually calls [super init] in your Presenter's initWithInteractor: method.
As others have pointed out, your methods which begin with init violate Objective-C conventions.
It's hard to tell from what you've posted exactly what your problem is, but I'm very suspicious of how it's structured.
You have a single class (Presenter), which you make two instances of, and pass no parameters other than the Interactor.
How could each instance know to load different "stuff" based on which View controller it received as a parameter?
I still want to understand thoroughly what is going on here, but I did solve the problem by doing the following.
In both Home and Profile, I init the StuffPresenter here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animation];
[self initPresenter];
}
And when exiting a controllerview I do the following:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_stuffPresenter = nil;
}
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.
I am new to Core Animation and having trouble implementing a CALayer object with the drawLayer method in a delegate.
I have narrowed the problem down to a very simple test. I have a main viewController named LBViewController that pushes a secondary viewController called Level2ViewController. In the level 2 controller, in viewWillAppear:, I create a CALayer object with it's delegate=self (i.e. the level 2 controller). Whether or not I actually implement the drawLayer:inContext: method I have the same problem -- when I return to the main viewController I get a zombie crash. In the profiler it appears that the object in trouble is the level 2 viewController object -- which is being dealloc'ed after it's popped.
I've tried using a subclassed CALayer object instead of the delegate and it works fine. If I comment out the delegate assignment it also runs fine. I would like to understand why delegation is causing this problem. Any advice is greatly appreciated.
Here's my code ---
Level2ViewController
#implementation Level2ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CALayer *box1 = [[CALayer alloc] init];
box1.delegate = self; // problem disappears if I comment out this assignment
box1.backgroundColor = [UIColor redColor].CGColor;
box1.frame = CGRectMake(10,10,200,300);
[self.view.layer addSublayer:box1];
[box1 setNeedsDisplay];
}
// makes no difference whether or not this method is defined as long
// as box1.delegate == self
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(theContext, 3);
CGContextAddRect(theContext, CGRectMake(5, 5, 40, 40));
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The method in LBViewController (the main controller) that pushes the level 2 view controller
- (IBAction)testAction:(id)sender {
Level2ViewController *controller = [[Level2ViewController alloc]
initWithNibName:#"Level2ViewController" bundle:nil];
controller.title = #"Level2";
// this push statement is where the profiler tells me the messaged zombie has been malloc'ed
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
You may want to set the layer's delegate to nil before the delegate object is released. So in your Leve2ViewController do this:
-(void)viewWillDisappear:(BOOL)animated
{
if (box1) {
box1.delegate = nil;
}
box1 = nil;
}
Obviously this requires, that box1 is turned into a field (so it is accessible in viewWillDisappear:)
Since you create box1in viewWillAppear: the code above uses viewWillDisappear:. Recently, when I ran into a similar problem, I had a separate delegate object in which I used init and dealloc.
Note: You call [super viewDidAppear:animated]; in viewWillAppear. Looks like a typo or copy/paste glitch :-)