Confused about missing declaration of zoneChange in logger.h


#1

Hello, i 'm a little bit confused that the method zoneChange has no declaration in logger.h. Why wasn’t it necessary to declare zoneChange ?

thank you
Mario


#2

You don’t need to declare any methods. But, if your code calls a method that the compiler hasn’t seen, you’ll get a warning (or sometimes an error) from the compiler.

But! Your code never calls zoneChange:. That method is found and executed at runtime by the NSTimer. So, you don’t need to declare it.

That said, it is a nice form of documentation to declare the method and explain its purpose with a comment in the .h file.


#3

I just looked up the developer documentation and couldn’t find any mentioning of “zoneChange”. What have I missed?


#4

You misunderstood what he said.

As Aaron says, you don’t need to declare any methods.

This means you don’t need to declare all the methods of a class in its public interface. That is, you can define methods in the implementation file without declaring them in the interface file.

Even if a class does not declare a method in its interface file, you can still call a method that is defined in its implementation file.

This is why you can use the method zoneChange: in NSTimer’s selector without first declaring it in Logger.h, provided Logger.m implements the method.

Given a selector, Objective-C Runtime System is smart enough to find the corresponding method and invoke it with the passed arguments.


#5

[quote=“AaronHillegass”]

But! Your code never calls zoneChange:. That method is found and executed at runtime by the NSTimer.[/quote]

I don’t understand how the NSTimer is executing zoneChange: at runtime in our code. I see that the NSTimer is executing sayOuch: but only see zoneChange: associated with the NSNotificationCenter object. Is this a typo or am I just confused (highly possible)?


#6

It is not the NSTimer object that is invoking the zoneChange: method; it is the NSNotificationCenter object.


#7

Actually, the compiler does complain about the lack of declaration for zoneChange.

In main.m, we set the selector for zoneChange, but main() has no knowledge of what zoneChange actually is. The program will compile with error and run, but, compiling with known errors is never a good idea.

Inserting the declaration:

into BNRLogger.h
kept the compiler happy and all is good.

zero errors, zero warnings…


#8

AntonyBurt, indeed, recent compiler versions have started complaining about this, and we didn’t catch it in time for the 2nd edition book. This is now filed as errata. Good catch!


#9

There is not a specific need for it to be private, no.

That said, it has become common/accepted practice to ask this question in the reverse: “Is there a need for it to be public?”. In general, it’s best practice to abide by a pattern some call the “humble header”. It’s the practice of making everything private by default, and then only using the header as an advertising space for things that need to be public. Not all of our materials have moved in this direction yet, but they’re getting there.


#10

Yeah, which helps me solve this problem. :smiley:

[quote=“AntonyBurt”]Actually, the compiler does complain about the lack of declaration for zoneChange.

In main.m, we set the selector for zoneChange, but main() has no knowledge of what zoneChange actually is. The program will compile with error and run, but, compiling with known errors is never a good idea.

Inserting the declaration:

into BNRLogger.h
kept the compiler happy and all is good.

zero errors, zero warnings…[/quote]


#11

[quote=“MikeyWard”]There is not a specific need for it to be private, no.

That said, it has become common/accepted practice to ask this question in the reverse: “Is there a need for it to be public?”. In general, it’s best practice to abide by a pattern some call the “humble header”. It’s the practice of making everything private by default, and then only using the header as an advertising space for things that need to be public. Not all of our materials have moved in this direction yet, but they’re getting there.[/quote]

Sine our intention is to make the method “zoneChange” private, we don’t declare it in the .h file. By fixing the compiler warning(“undeclared selector ‘zoneChange’”), we then insert the declaration in the .h file. We just compromise by making the method public. What can we do to fix the warning at the same time maintaining the method private?

Wow! This is my first post! I just started learning Objective-C for a month. :smiley:


#12

[quote]What can we do to fix the warning at the same time maintaining the method private?
[/quote]
Put the method’s declaration in BNRLogger.m:

[code]// BNRLogger.m

@interface BNRLogger ()
// Declare

  • (void)zoneChange:(NSNotification *) note;
    @end

@implementation BNRLogger

// Implement

  • (void)zoneChange:(NSNotification *) note
    {

    }

    @end[/code]

#13

I’ve already put the method’s declaration in the .m file. However, I have a warning in main.m. In order to fix the warning, I will have to add the declaration in the .h file. But I don’t want to make it public. That’s why I am curious whether there is a way to avoid the warning yet keeping the method private.


#14

If you post your code, I can take tell you whether it can be done.


#15

Here is my code, Xcode shows a warning, “Undeclared selector ‘zoneChange’”.
main.m

[code]#import <Foundation/Foundation.h>
#import "BNRLogger.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here…
// NSLog(@“Hello, World!”);
BNRLogger *logger = [[BNRLogger alloc] init];

// [[NSRunLoop currentRunLoop] run];
//
//
//Notification
//using the class factory method, defaultCenter, to create an NSNotificationCenter object
//an instance method of the NSNotificationCenter object: addObserver:selector:name:object:

    [[NSNotificationCenter defaultCenter] addObserver:logger selector:@selector(zoneChange:) name:NSSystemTimeZoneDidChangeNotification object:nil];
    
    
    NSURL *url = [NSURL URLWithString:@"http://www.gutenber.org/cache/epub/205/pg205.txt"];
    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    
    // logger is the delegate
    // we don't need to manually call methods of the logger
    // we simply implements some methods that are required as being a delegate
    // when defining the BNRLogger class we need to conform the below two protocols to handle NSURLConnection
    // <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
    // as soon as the protocols are conformed, BNRLogger will be able to have the relevant methods to handle NSURLConnection
    __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request delegate:logger startImmediately:YES];
    
    
    //using __unused to tell the compiler that the unused variable timer is purposeful, don't give me a warning, I know what I am doing.
    
    __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(updateLastTime:) userInfo:nil repeats:YES];
    
    

    [[NSRunLoop currentRunLoop] run];
    
}
return 0;

}
[/code]

BNRLogger.h

[code]#import <Foundation/Foundation.h>

@interface BNRLogger : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
{
// it should be a mutable one, because we are synchronizing data asynchronously.
NSMutableData *_incomingData;
}

@property (nonatomic) NSDate *lastTime;

-(NSString *)lastTimeString;
-(void)updateLastTime:(NSTimer *)t;

@end[/code]

BNRLogger.m

[code]#import “BNRLogger.h”
// add class extension
@interface BNRLogger ()

-(void)zoneChange:(NSNotification *)note;

@end

@implementation BNRLogger

//implementation for the method in the class extension
-(void)zoneChange:(NSNotification *)note
{
NSLog(@“The system time zone has changed!”);
}

//return the time and date of the lastTime property in a nice format
//we don’t manually call this method in main.m, we would call the updateLastTime method, then the method will call lastTimeString to generate the time in a nice format.
-(NSString *)lastTimeString
{
//static in here means all instances of BNRLogger will simple share one single instance of NSDateFormatter, dateFormatter
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter)
{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];

    NSLog(@"created dateFormatter");
}

// - (NSString *)stringFromDate:(NSDate *)date
// Returns a string representation of a given date formatted using the receiver’s current settings.
return [dateFormatter stringFromDate:self.lastTime];

}

// when calling this method to updateLastTime, at the end it will return an NSLog message to indicate the date and time that have been newly set in a nice date and time format, by calling another method, lastTimeString.
-(void)updateLastTime:(NSTimer *)t
{
NSDate *now = [NSDate date];
//using setter method to update the property lastTime to the current time and date
[self setLastTime:now];

NSLog(@"Just set time to %@", self.lastTimeString);

}
//================================================================================================================
// a line to seperate two groups of methods this logger have

// called each time a chunk of data arrives
// - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
// This method provides the only way for an asynchronous delegate to retrieve the loaded data. It is the responsibility of the delegate to retain or copy this data as it is delivered.
// this method is used to handle the incomming data
// it sent as a connection loads data incrementally.
// meaning: what the program should do as soon as a chunk of data is received?
// save all chunks of data into one NSMutableData object
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@“received %lu bytes”, [data length]);

//create a mutable data if it does not already exist
if (!_incomingData) {
    _incomingData = [[NSMutableData alloc] init];
}

// Appends the content of another NSData object to the receiver.
[_incomingData appendData:data];

}

// called when the last chunk has been processed
// meaning: what the program should do as soon as all chunks of data are received?
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@“Got it all!”);
// using the finished receiving data to create an instance of NSString
//Returns an NSString object initialized by converting given data into Unicode characters using a given encoding.
NSString *string = [[NSString alloc] initWithData:_incomingData encoding:NSUTF8StringEncoding];
// we no longer need the _incomingData since we have created a string
// set it to nil to signal ARC to release memory
_incomingData = nil;

NSLog(@"string has %lu characters", [string length]);

// uncomment the next line to see the entire fetched file

// NSLog(@“The whole string is: \n %@”, string);

}

// called if the fetch fails
// in the previous chapter, we already talk about that the parameter NSError object will be created automatically if there is an error. we don’t have to manually define or pass in an NSError object
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

NSLog(@"connection failed: %@", [error localizedDescription]);

// resetting the value to nil, in case the _incommingData recevied incomplete data
// we don't want having half of the data stay there, then it starting from the beginning to retrieve all the data again and put them all in _incommingData, in that case, we will have half of the data(previously saved in) appending a copy of the complete data.
_incomingData = nil;

}

@end
[/code]


#16

[quote] int main (int argc, const char * argv[]) { ... [[NSNotificationCenter defaultCenter] addObserver:logger selector:@selector (zoneChange:) name:NSSystemTimeZoneDidChangeNotification object:nil]; ... } [/quote]
We can do some information hiding, and get the logger object itself to observe the time zone changes.

For example:

int main (int argc, const char * argv[]) {
   ...
   [logger startObservingTimeZoneChanges];
   ...
}    

BNRLogger.h

@interface BNRLogger: NSObject
- (void)startObservingTimeZoneChanges;
...
@end

BNRLogger.m

#import "BNRLogger.h"

@interface BNRLogger ()  <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
-(void)zoneChange:(NSNotification *)note;
@end

@implementation BNRLogger
{
    NSMutableData *_incomingData;
}
...
- (void)startObservingTimeZoneChanges
{
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(zoneChange:) name:NSSystemTimeZoneDidChangeNotification object:nil];
}
...
@end

This is the basic idea. One can even go further…


#17

Thanks @ibex10 !

Wow! I didn’t think of writing a method to make the logger object itself to be the observer. :smiley: