Search code examples
iosxcodemultithreadingnsoperationnsoperationqueue

NSOperationQueue with loop and class with delegates


I am new to NSOperationQueue and I am trying to create one which will ping all hosts in my network. First of all I am using Apple's class SimplePing.h which pings only one host at a time. This class has some delegates to notify the main class that the ping was successful or not. Now in my case I want to ping all hosts from 192.168.1.1 to 192.168.1.254 so this is my code:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // Create a new NSOperationQueue instance.
    operationQueue = [NSOperationQueue new];



    for (int i=1; i<254; i++) {

        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                                selector:@selector(pingHost:)
                                                                                  object:[NSString stringWithFormat:@"192.168.1.%d",i]];
        // Add the operation to the queue and let it to be executed.
        [operationQueue addOperation:operation];


    }


}

-(void)pingHost:(NSString*)ip{

    ping = [SimplePing simplePingWithHostName:ip];  
    self.ping.delegate=self;
    [ping start];


};
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

// When the pinger starts, send the ping immediately
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address {


    [pinger sendPingWithData:nil];
}

- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet{


    [self performSelector:@selector(didNotReceivedAnswer) withObject:nil afterDelay:2];

    NSLog(@"didsendpacket");

}
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error {
    NSLog(@"didFail");

    [self didNotReceivedAnswer];

}

- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet error:(NSError *)error {
    NSLog(@"didfailtosendpacket");

    [self didNotReceivedAnswer];

}

- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet {

    NSLog(@"didreceivesresponse");

}

- (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet{

    [self didNotReceivedAnswer];
    NSLog(@"didreceiveunexpected");

}

//Helper for delegate
-(void)didNotReceivedAnswer{
}

And then I get this error:

Assertion failed: (self->_host == NULL), function -[SimplePing start], file /Users/Mike/Documents/Xcode/Unused/PingTest/PingTest/SimplePing.m, line 574. Assertion failed: (self->_host == NULL), function -[SimplePing start], file /Users/Mike/Documents/Xcode/Unused/PingTest/PingTest/SimplePing.m, line 574. Assertion failed: (self->_host == NULL), function -[SimplePing start], file /Users/Mike/Documents/Xcode/Unused/PingTest/PingTest/SimplePing.m, line 574. Assertion failed: (self->_host == NULL), function -[SimplePing start], file /Users/Mike/Documents/Xcode/Unused/PingTest/PingTest/SimplePing.m, line 574. Assertion failed: (self->_host == NULL), function -[SimplePing start], file /Users/Mike/Documents/Xcode/Unused/PingTest/PingTest/SimplePing.m, line 574.

Any ideas what's going wrong here?


Solution

  • Your problem is that you have to keep a reference to each ping object. As of now you have ping declared as a class variable so each time you call pingHost it is being re initialized and the previous one is being lost. Which is why you are getting the (self->_host == NULL) assertion failure.

    You will need to use an NSOperationQueue and a subclassed NSOperation to handle this. Having the NSOperation will allow each instantiation of that NSOperation to retain it's unique ping object.

    I've quickly done this so you can work off of it. You would ideally want to create a delegate for the PingOperation class so you can get call backs to your main thread from it.

    PingOperation.h

    #import <Foundation/Foundation.h>
    
    @interface PingOpertion : NSOperation
    
    -(id)initWithHostName:(NSString*)hostName;
    
    @end
    

    PingOperation.m

    #import "PingOpertion.h"
    #include "SimplePing.h"
    
    #include <sys/socket.h>
    #include <netdb.h>
    
    static NSString * DisplayAddressForAddress(NSData * address)
    // Returns a dotted decimal string for the specified address (a (struct sockaddr)
    // within the address NSData).
    {
        int         err;
        NSString *  result;
        char        hostStr[NI_MAXHOST];
    
        result = nil;
    
        if (address != nil) {
            err = getnameinfo([address bytes], (socklen_t) [address length], hostStr, sizeof(hostStr), NULL, 0, NI_NUMERICHOST);
            if (err == 0) {
                result = [NSString stringWithCString:hostStr encoding:NSASCIIStringEncoding];
                assert(result != nil);
            }
        }
    
        return result;
    }
    
    @interface PingOpertion () <SimplePingDelegate>
    
    @property NSString *hostName;
    
    @property (nonatomic, strong, readwrite) SimplePing *   pinger;
    @property (nonatomic, strong, readwrite) NSTimer *      sendTimer;
    @end
    
    
    @implementation PingOpertion
    
    @synthesize pinger    = _pinger;
    @synthesize sendTimer = _sendTimer;
    
    
    -(id)initWithHostName:(NSString*)hostName {
        if (self = [super init]) {
            self.hostName = hostName;
        }
        return self;
    }
    
    - (void)main {
        // a lengthy operation
        @autoreleasepool {
            assert(self.pinger == nil);
    
            self.pinger = [SimplePing simplePingWithHostName:self.hostName];
            assert(self.pinger != nil);
    
            self.pinger.delegate = self;
            [self.pinger start];
    
            do {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            } while (self.pinger != nil);
        }
    }
    
    
    - (NSString *)shortErrorFromError:(NSError *)error
    // Given an NSError, returns a short error string that we can print, handling
    // some special cases along the way.
    {
        NSString *      result;
        NSNumber *      failureNum;
        int             failure;
        const char *    failureStr;
    
        assert(error != nil);
    
        result = nil;
    
        // Handle DNS errors as a special case.
    
        if ( [[error domain] isEqual:(NSString *)kCFErrorDomainCFNetwork] && ([error code] == kCFHostErrorUnknown) ) {
            failureNum = [[error userInfo] objectForKey:(id)kCFGetAddrInfoFailureKey];
            if ( [failureNum isKindOfClass:[NSNumber class]] ) {
                failure = [failureNum intValue];
                if (failure != 0) {
                    failureStr = gai_strerror(failure);
                    if (failureStr != NULL) {
                        result = [NSString stringWithUTF8String:failureStr];
                        assert(result != nil);
                    }
                }
            }
        }
    
        // Otherwise try various properties of the error object.
    
        if (result == nil) {
            result = [error localizedFailureReason];
        }
        if (result == nil) {
            result = [error localizedDescription];
        }
        if (result == nil) {
            result = [error description];
        }
        assert(result != nil);
        return result;
    }
    
    - (void)runWithHostName:(NSString *)hostName
    // The Objective-C 'main' for this program.  It creates a SimplePing object
    // and runs the runloop sending pings and printing the results.
    {
        assert(self.pinger == nil);
    
        self.pinger = [SimplePing simplePingWithHostName:hostName];
        assert(self.pinger != nil);
    
        self.pinger.delegate = self;
        [self.pinger start];
    
        do {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        } while (self.pinger != nil);
    }
    
    - (void)sendPing
    // Called to send a ping, both directly (as soon as the SimplePing object starts up)
    // and via a timer (to continue sending pings periodically).
    {
        assert(self.pinger != nil);
        [self.pinger sendPingWithData:nil];
    }
    
    - (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address
    // A SimplePing delegate callback method.  We respond to the startup by sending a
    // ping immediately and starting a timer to continue sending them every second.
    {
    #pragma unused(pinger)
        assert(pinger == self.pinger);
        assert(address != nil);
    
        NSLog(@"pinging %@", DisplayAddressForAddress(address));
    
        // Send the first ping straight away.
    
        [self sendPing];
    
        // And start a timer to send the subsequent pings.
    
        assert(self.sendTimer == nil);
        self.sendTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendPing) userInfo:nil repeats:YES];
    }
    
    - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error
    // A SimplePing delegate callback method.  We shut down our timer and the
    // SimplePing object itself, which causes the runloop code to exit.
    {
    #pragma unused(pinger)
        assert(pinger == self.pinger);
    #pragma unused(error)
        NSLog(@"failed: %@", [self shortErrorFromError:error]);
    
        [self.sendTimer invalidate];
        self.sendTimer = nil;
    
        // No need to call -stop.  The pinger will stop itself in this case.
        // We do however want to nil out pinger so that the runloop stops.
    
        self.pinger = nil;
    }
    
    - (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet
    // A SimplePing delegate callback method.  We just log the send.
    {
    #pragma unused(pinger)
        assert(pinger == self.pinger);
    #pragma unused(packet)
        NSLog(@"#%u sent", (unsigned int) OSSwapBigToHostInt16(((const ICMPHeader *) [packet bytes])->sequenceNumber) );
    }
    
    - (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet error:(NSError *)error
    // A SimplePing delegate callback method.  We just log the failure.
    {
    #pragma unused(pinger)
        assert(pinger == self.pinger);
    #pragma unused(packet)
    #pragma unused(error)
        NSLog(@"#%u send failed: %@", (unsigned int) OSSwapBigToHostInt16(((const ICMPHeader *) [packet bytes])->sequenceNumber), [self shortErrorFromError:error]);
    }
    
    - (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet
    // A SimplePing delegate callback method.  We just log the reception of a ping response.
    {
    #pragma unused(pinger)
        assert(pinger == self.pinger);
    #pragma unused(packet)
        NSLog(@"#%u received", (unsigned int) OSSwapBigToHostInt16([SimplePing icmpInPacket:packet]->sequenceNumber) );
    }
    
    - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet
    // A SimplePing delegate callback method.  We just log the receive.
    {
        const ICMPHeader *  icmpPtr;
    
    #pragma unused(pinger)
        assert(pinger == self.pinger);
    #pragma unused(packet)
    
        icmpPtr = [SimplePing icmpInPacket:packet];
        if (icmpPtr != NULL) {
            NSLog(@"#%u unexpected ICMP type=%u, code=%u, identifier=%u", (unsigned int) OSSwapBigToHostInt16(icmpPtr->sequenceNumber), (unsigned int) icmpPtr->type, (unsigned int) icmpPtr->code, (unsigned int) OSSwapBigToHostInt16(icmpPtr->identifier) );
        } else {
            NSLog(@"unexpected packet size=%zu", (size_t) [packet length]);
        }
    }
    
    @end
    

    Now when you want to start pinging some people you just manage it through an NSOperationQueue.

    example

    self.pingQueue = [[NSOperationQueue alloc] init];
    
    
        NSArray *host = [NSArray arrayWithObjects:@"http://www.google.com", @"http://www.stackoverflow.com", @"http://www.woot.com", nil];
    
        for (int i = 0; i < host.count; i++) {
            PingOpertion *pingOperation = [[PingOpertion alloc] initWithHostName:host[i]];
            [self.pingQueue addOperation:pingOperation];
        }