PDA

View Full Version : Class Method -> Function -> Instance Method?




Blakeasd
Jun 17, 2013, 08:51 PM
Hello,

I am working on an application with Objective-C and Cocoa. I've gotten myself in a peculiar predicament. I have an NSView on an NSWindow. In the NSView's mouseDown: method I call a class method for another class.

Now jump over to this other class. The class method I called from the other class needs to do this: (webView is an object in this class)

[self.webView setHidden:TRUE];

Unfortunately, being a class method it can't call upon objects inside the class to perform methods.

I then (perhaps foolishly) came up with the idea on crafting a function to call from the class method. I then came to the same problem -- I can't call methods upon objects again. So I built an instance method to call from the function. Which I still can't do because I get 'undefined identifier self'.

This whole thing can be best explained by this diagram:

NSVIEW SUBCLASS CLASS

[DHSwipeClipView openInfoPane];

DHSwipeClipView CLASS


+(void)openInfoPane{
NSLog(@"To the function!");
openInfoPaneFunction();
}

void openInfoPaneFunction(){

NSLog(@"To the instance method!");
//I CAN'T CALL THE openInfoPaneMethod here!!
}

-(void)openInfoPaneMethod{

[self.webView setHidden:TRUE];

}



How can I call the instance method from the class method?

The premise of the question is how can I call the instance method from the class method?

Hopefully it isn't too difficult to understand. Any Help is Appreciated!



chown33
Jun 17, 2013, 10:13 PM
Where you have this code:
[DHSwipeClipView openInfoPane];
is there or isn't there an instance of DHSwipeClipView?

If there isn't, then there needs to be one. Why? Because the following is an instance method:
-(void)openInfoPaneMethod{

[self.webView setHidden:TRUE];

}

and self means the instance, and self.webView refers to an instance property (I presume).

Asking the class DHSwipeClipView to somehow conjure up an instance will be fruitless. You need an instance already, and it needs to have already had its webView property assigned. Without a valid webView property, the expression self.webView will be nil, and telling nil to setHidden:TRUE will be fruitless.

If there is an instance of DHSwipeClipView at the point where you have this code:
[DHSwipeClipView openInfoPane];
then replace that line of code with this:
[myInstance openInfoPaneMethod];
where myInstance is whatever variable name or expression is needed to refer to the instance.


This code:
void openInfoPaneFunction(){

has no parameters, so there are no instances available in the function.

If you've stored an instance in a global or a static variable, then that is available in the function. Otherwise you need to pass a parameter.


Without seeing more code, I'm guessing you're fundamentally confused about the difference between a class (such as DHSwipeClipView), and an instance of that class. You make an instance by calling alloc on the class. You then initialize the instance. The idiomatic form of this is:
[[DHSwipeClipView alloc] init];
You are then generally expected to assign the init'ed instance to a variable, so it can be used.

Here's an example declaring a variable, with an initializer expression that alloc's and init's an instance:
DHSwipeClipView * myInstance = [[DHSwipeClipView alloc] init];
You will then presumably do something to ensure myInstance has a valid webView property, or maybe that's done in the -init method. I'm just guessing; you'd have to post more code.

With the webView property valid, then this should work:
[myInstance openInfoPaneMethod];


If the above doesn't make sense, go through it carefully.

If there's a specific thing you don't understand, ask about that specific thing.

If you don't understand the difference between a class and an instance of that class, go back to whatever you're learning from and restudy the part about instances vs. classes. This distinction is fundamental.

Blakeasd
Jun 17, 2013, 10:38 PM
There isn't an instance of DHSwipeClipView because I call openInfoPane which is a class method and not openInfoPaneMethod which is an instance method. There's actually a difference here, though I realize my naming scheme is quite confusing in this case.

ElectricSheep
Jun 17, 2013, 10:55 PM
I'm curious as to what it is about your design that requires you to use a class method rather than instantiating an DHSwipeClipView.

chown33
Jun 18, 2013, 12:49 AM
There isn't an instance of DHSwipeClipView because I call openInfoPane which is a class method and not openInfoPaneMethod which is an instance method.

If there isn't an instance of DHSwipeClipView, then how could the code possibly call an instance method?

That's not a rhetorical question. It goes directly to the fundamental issue. There is no instance of DHSwipeClipView. Yet somehow you expect to be able to call an instance method of that non-existent instance. Somewhere, somehow, your logic is profoundly flawed.

You haven't described what the DHSwipeClipView class does, or is expected to do, or why there is both a class method and an instance method with so much similarity. You haven't shown the class's @interface, so no one knows what the webView property is or does. Without descriptions and explanations of your intent, no one can tell if the logical flaw is in the design, the concept behind the design, or in the implementation.

It's as if someone told me to saddle a horse, and pointed me to an empty stall. I'd say, "I see no horse here". I can't saddle a non-existent horse. Nor can I saddle the species Equus caballus (http://en.wikipedia.org/wiki/Equus_caballus) (i.e. domesticated horse) because you can't put a saddle on a species, only on an instance of a species, i.e. an actual horse.


There's actually a difference here, though I realize my naming scheme is quite confusing in this case.
I fully understand the difference between a class method, a function, and an instance method. The names are a bit odd, but that's nothing compared to the logical flaw of not having an instance, yet expecting to call an instance method of it.

Again, I suspect you don't fully understand the difference between a class, which has a class method, and an instance of that class, which has an instance method.

PatrickCocoa
Jun 18, 2013, 10:49 AM
Again, I suspect you don't fully understand the difference between a class, which has a class method, and an instance of that class, which has an instance method.

Possibly OP believes that since the method is there in his code, other parts of his code should be able to "call that method".

In other words, he's thinking about the code as he sees it in Xcode, not about the objects that exist while the code is running. After all, since the method he wants to call is right there and he can see it, why can't the running code see that method and call it? The answer is that the method only exists in the running code once an instance of the class that contains that method is created.

gnasher729
Jun 18, 2013, 11:55 AM
+(void)openInfoPane{
DHSwipeClipView* myView = .....;
[myView.webView setHidden:YES];
}


You just have to add the missing bit of code. (And YES and NO are standard identifiers in Objective-C and Objective-C++. TRUE and FALSE are not standard identifiers in any C-like language).

Blakeasd
Jun 18, 2013, 12:16 PM
I think I did a poor job of originally explaining the problem. There seems to be much confusion of the issue at stake.

Here is a section of the @interface for for the DHSwipeClipView:


+(void)openInfoPane;
void openInfoPaneFunction();
-(void)openInfoPaneMethod;


Also in the DHSwipeClipView I have an instance object of DHSwipeWebView:

DHSwipeWebView *webView;

and part of the implementation of DHSwipClipView:


+(void)openInfoPane{
NSLog(@"Here we go!");
openInfoPaneFunction();
//WISH I COULD CALL openInfoPaneMethod: here or call setHidden: on the DHSwipeWebView instance
}

void openInfoPaneFunction(){

NSLog(@"Function has been activated");
//WISH I COULD CALL openInfoPaneMethod: here or call setHidden: on the DHSwipeWebView instance

}

-(void)openInfoPaneMethod{

[self.webView setHidden:TRUE];

}


Here is the MouseDownView implementation that receives the mouseDown:



-(void)mouseDown:(NSEvent *)theEvent{
NSLog(@"Show info pane button has been clicked!");
[DHSwipeClipView openInfoPane];

}


There is no instance of DHSwipeClipView in my MouseDownView class because in the mouseDown: method I am using the class method

which is this:

+(void)openInfoPane

So when mouseDown: is triggered it calls that class method that you see above. (At the top of this reply is the implementation of what it does)

Class methods cannot call instances of objects inside of classes (webView is what I want to call setHidden: on). I'd like to be able to do this inside of the class method:

[self.webView setHidden:TRUE];

Unfortunately it has to be done inside of an instance method. I thought that if I put a function inside of the class method, that function could call the instance method with the code that acts on the webView.

Functions as I've no discovered can't do something like:

[self openInfoPaneMethod]

Did I explain the situation more clearly this time? Hopefully this isn't too confusing.

ElectricSheep
Jun 18, 2013, 12:22 PM
The fundamental question here is why is this:

+(void)openInfoPane

modeled as a class method, and not as an instance method?

EDIT:

It should be noted that the fact you are having to jump through so many hoops to call an instance method for an object which likely doesn't even exist is a huge flag that your approach is fundamentally off track. I suspect what you really should be doing is either using the instance method directly or implementing a singleton pattern.

Blakeasd
Jun 18, 2013, 12:32 PM
It is modeled as a class method in attempt to fit in with the "structure" of the DHSwipe classes. The DHSwipeClasses are some classes I downloaded that add the Chrome swipe navigation to a WebView. The DHSwipeClipView is the only place where I can call methods on the DHSwipe *webView. I have a WebView in my .xib interface (whose class is another class I created which is a subclass of DHSwipeWebView), but IBOutlets render useless on it for some reason.

The moral of my project seems to be that if I want to provide some action on my WebView, it must go through the DHSwipeClipView.

The DHSwipe classes are on GitHub (https://github.com/salomvary/soundcleod/tree/master/DHSwipeWebView)

Blakeasd
Jun 18, 2013, 04:42 PM
It seems that a more interesting issue has come up. Take a look at the interface of DHSwipeClipView:


#import <Cocoa/Cocoa.h>
#import "DHSwipeWebView.h"
#import "mWebView.h"

@interface DHSwipeClipView : NSClipView {
CGFloat currentSum;
NSTimer *drawTimer;
BOOL canGoLeft;
BOOL canGoRight;
DHSwipeWebView *webView;
BOOL isHandlingEvent;
BOOL _haveAdditionalClip;
NSRect _additionalClip;
CGFloat scrollDeltaX;
CGFloat scrollDeltaY;
IBOutlet NSButton *showInfoButton;
}

@property (retain) NSTimer *drawTimer;
@property (assign) CGFloat currentSum;
@property (retain) DHSwipeWebView *webView;
@property (assign) BOOL isHandlingEvent;
- (id)initWithFrame:(NSRect)frame webView:(DHSwipeWebView *)aWebView;
+(void)openInfoPane;
void openInfoPaneFunction();
-(void)openInfoPaneMethod;
-(IBAction)showInfoPaneButton:(id)sender;
@end



Now the implementation:


@implementation DHSwipeClipView

@synthesize drawTimer;
@synthesize currentSum;
@synthesize webView;
@synthesize isHandlingEvent;

- (id)initWithFrame:(NSRect)frame webView:(DHSwipeWebView *)aWebView
{
if(!(self = [super initWithFrame:frame]))
{
return nil;
}
self.webView = aWebView;


[self releaseGState];

return self;
}



- (void)resetAdditionalClip
{
_haveAdditionalClip = NO;
}

- (void)setAdditionalClip:(NSRect)additionalClip
{
_haveAdditionalClip = YES;
_additionalClip = additionalClip;
}

- (BOOL)hasAdditionalClip
{
return _haveAdditionalClip;
}

- (NSRect)additionalClip
{
return _additionalClip;
}

// From https://gist.github.com/b6bcb09a9fc0e9557c27
- (NSView *)hitTest:(NSPoint)aPoint
{
NSEvent *currentEvent = [NSApp currentEvent];
NSScrollView *scrollView = [self enclosingScrollView];
if([currentEvent type] == NSLeftMouseDown)
{
// if we have a vertical scroller and it accepts the current hit
if([scrollView hasVerticalScroller] && [[scrollView verticalScroller] hitTest:aPoint] != nil)
{
[[scrollView verticalScroller] mouseDown:currentEvent];
}
// if we have a horizontal scroller and it accepts the current hit
if([scrollView hasVerticalScroller] && [[scrollView horizontalScroller] hitTest:aPoint] != nil)
{
[[scrollView horizontalScroller] mouseDown:currentEvent];
}
}
return [super hitTest:aPoint];
}

- (void)scrollWheel:(NSEvent *)event
{
if(![NSEvent isSwipeTrackingFromScrollEventsEnabled])
{
[super scrollWheel:event];
return;
}
if([event phase] == NSEventPhaseBegan)
{
currentSum = 0;
NSScrollView *scrollView = [[[[webView mainFrame] frameView] documentView] enclosingScrollView];
NSRect bounds = [[scrollView contentView] bounds];
canGoLeft = canGoRight = NO;
if(bounds.origin.x <= 0)
{
canGoLeft = YES; // && [webView canGoBack]
}
if(bounds.origin.x + bounds.size.width >= [[scrollView documentView] bounds].size.width)
{
canGoRight = YES; // && [webView canGoForward]
}
scrollDeltaX = 0;
scrollDeltaY = 0;
isHandlingEvent = canGoLeft || canGoRight;
}
else if([event phase] == NSEventPhaseChanged)
{
if(!isHandlingEvent)
{
if(currentSum != 0)
{
currentSum = 0;
[self launchDrawTimer];
}
}
else
{
scrollDeltaX += [event scrollingDeltaX];
scrollDeltaY += [event scrollingDeltaY];

float absoluteSumX = fabsf(scrollDeltaX);
float absoluteSumY = fabsf(scrollDeltaY);
if((absoluteSumX < absoluteSumY && currentSum == 0))
{
isHandlingEvent = NO;
if(currentSum != 0)
{
currentSum = 0;
[self launchDrawTimer];
}
}
else
{
CGFloat flippedDeltaX = scrollDeltaX * -1;
if(flippedDeltaX == 0 || (flippedDeltaX < 0 && !canGoLeft) || (flippedDeltaX > 0 && !canGoRight))
{
if(currentSum != 0)
{
currentSum = 0;
[self launchDrawTimer];
}
}
else
{
// Draw the back/forward indicators
currentSum = flippedDeltaX/1000;
[self launchDrawTimer];
return;
}
}
}
}
else if([event phase] == NSEventPhaseEnded)
{
if(currentSum < -0.3 && canGoLeft)
{
NSLog(@"Go back");
[self.webView goBack];

}
else if(currentSum >= 0.3 && canGoRight)
{
NSLog(@"Go forward");
[self.webView goForward];

}
isHandlingEvent = NO;
if(currentSum != 0)
{
currentSum = 0;
[self launchDrawTimer];
}
}
else if([event phase] == NSEventPhaseMayBegin || [event phase] == NSEventPhaseCancelled)
{
isHandlingEvent = NO;
if(currentSum != 0)
{
currentSum = 0;
[self launchDrawTimer];
}
}
[super scrollWheel:event];
}

- (void)launchDrawTimer
{
// a timer is needed because events are queued and processing and drawing
// takes longer than they are delivered, so the queue fills up
if(!drawTimer || ![drawTimer isValid])
{
self.drawTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f/25 target:webView.swipeIndicator selector:@selector(display) userInfo:nil repeats:NO];
}
}

+(void)openInfoPane{
NSLog(@"Here we go!");
openInfoPaneFunction();

}

void openInfoPaneFunction(){

NSLog(@"Function has been activated");

}

-(void)openInfoPaneMethod{

[self.webView setHidden:TRUE];
NSLog(@"uh");

}

-(void)awakeFromNib{

[self openInfoPaneMethod];


}

-(IBAction)showInfoPaneButton:(id)sender{

if (showInfoButton.state == NSOnState) {
NSLog(@"WebView is visible");
[self.webView setHidden:TRUE];
}

if(showInfoButton.state == NSOffState){

NSLog(@"Infopane is opened");
[self.webView setHidden:FALSE];
[self.webView goBack];

}


}

@end



I decided to try and put the setHidden: code inside an IBAction in the DHSwipeClipView instead. The WebView ignores it though through the action?! The log is printed, but the WebView won't hide! If I put the code in the eventPhase region under [webView goBack]; , the webView actually hides. I then tried hiding the webView in awakeFromNib: That didnt work either

What could possibly cause this to happen?

gnasher729
Jun 19, 2013, 02:53 AM
Look, class methods are there for situations where you don't have any object. For example the most important class method of all, "alloc", which you need to call when you don't have an object. You have an object, you just can't figure out where it is.

Your object is in a nib file, and there should be an IBOutlet somewhere referring to it, and you call object methods on that outlet. If it's not in a nib file, or if you don't hav a call to [[xxx alloc] init] then you don't have an object, and this isn't going to work.


What could possibly cause this to happen?

The answer to this is obvious: Your code.