2

I have the excellent GCDAsyncSocket running perfectly on an iOS app I have developed.

I was just playing around with setting up a Mac OSX command line program that uses the library in a similar way to log to SQLite DB but can't get it to even attempt to connect to host. No errors get generated. The program doesn't crash or anything so don't have an idea about why it's not working. Does anyone have an idea why this won't work?

The console only prints out the following (with no connect/disconnect/read/write logging i.e. the socket delegate methods are not being called):

Attempting to connect to host: 192.168.1.2 on port: 1234 to refresh

Here is quite a bit of the code I am using:

main.m

#import <Foundation/Foundation.h>
#import "LoggerClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LoggerClass *logger = [[LoggerClass alloc] init];
        [logger startLogging];
        while (logger.status == 0) {
            sleep(1);
            continue;
        }
        return 0;
    }
}

LoggerClass.h

#import <Foundation/Foundation.h>
#import "Device.h"

@interface LoggerClass : NSObject <DeviceProtocol>

@property (nonatomic, strong) FMDatabase *database;
@property (nonatomic, strong) NSArray *devices;
@property (nonatomic) int status;
- (void)startLogging;

@end

LoggerClass.m

#import "LoggerClass.h"
#import "FMDatabase.h"

#define kLoggingInProgress 0
#define kLoggingCompleted 1

@implementation LoggerClass

@synthesize database = _database;
@synthesize devices = _devices;
@synthesize status = _status;

- (id)init {
    if (self = [super init]) {
        self.status = kLoggingInProgress;

        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *docsPath = [paths objectAtIndex:0];
        NSString *path = [docsPath stringByAppendingPathComponent:@"database.sqlite"];  
        self.database = [FMDatabase databaseWithPath:path];

        Device *d1 = [Device deviceWithName:@"Device 1" address:@"192.168.1.2" delegate:self];      
        self.devices = [NSArray arrayWithObjects:d1, nil];
    }   
    return self;
}

- (void)startLogging {
    for (Device *d in self.devices) {
        [d refresh];
    }
}

- (void)didUpdateDevice:(Device *)device {

    // Insert DB entry code

    NSLog(@"%@ has finished Logging", device.name);
    self.status = kLoggingCompleted; // This would obviously register completed if only 1 device returned but for sake of this test that fine
}

@end

Device.h

#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"

@protocol DeviceProtocol;

@interface Device : NSObject

@property (nonatomic, weak) id<DeviceProtocol> delegate;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) GCDAsyncSocket *socket;

+ (Device *)deviceWithName:(NSString *)n address:(NSString *)a delegate:(id<DeviceProtocol>)d;
- (void)refresh;

@end

@protocol DeviceProtocol <NSObject>
@required
- (void)didUpdateDevice:(Device *)device;
@end

Device.m

#import "Device.h"

#define DEVICE_PORT 1234

@implementation Device

@synthesize delegate = _delegate;
@synthesize name = _name;
@synthesize address = _address;
@synthesize socket = _socket;

- (id)initWithName:(NSString *)name andAddress:(NSString *)address andDelegate:(id<DeviceProtocol>)delegate { // Designated Initialiser
    if (self = [super init]) {

        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

        self.name = name;
        self.address = address;
        self.delegate = delegate;
    }
    return self;
}

+ (Device *)deviceWithName:(NSString *)n address:(NSString *)a delegate:(id<DeviceProtocol>)d {
    return [[Device alloc] initWithName:n andAddress:a andDelegate:d];
}

#pragma mark - GCD Async Socket Delegate Methods

- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port {
    NSLog(@"Connected to: %@", self.address);
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error {
    NSLog(@"Socket for %@ disconnected %@.", self.address, error);

    if (self.delegate) [self.delegate didUpdateDevice:self];
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    NSLog(@"socket:didWriteDataWithTag:");
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    NSLog(@"socket:didReadData:withTag:");

    [self.socket disconnect];   
}

- (void)refresh {
    if ([self.address length] == 0) { [self.delegate didUpdateDevice:self]; return; }

    NSLog(@"Attempting to connect to host: %@ on port: %i to refresh", self.address, DEVICE_PORT);
    NSError *error = nil;
    if (![self.socket connectToHost:self.address onPort:DEVICE_PORT withTimeout:15 error:&error]) NSLog(@"ERROR: %@", error);

    NSData *dataToSend;
    // Build byte data here to send to device (exact same data works on iOS)

    [self.socket writeData:dataToSend withTimeout:10 tag:0];
    [self.socket readDataWithTimeout:-1 tag:0];
}

@end
Andy
  • 717
  • 1
  • 9
  • 24
  • You've got a good question here. However I removed the Linux part of your question, which really is a different question entirely. Would you do some separate searches and post new questions about using Objective-C, FMDB, and GCDAsyncSocket on Linux? – paulmelnikow Mar 11 '13 at 04:22
  • Thanks noa. Point taken - was hoping to piggy-back the Linux question onto this as quick yes/no whether GCDAsyncSocket works on Linux! A note about my question: the other 2 similar questions on SO about this seem to suggest that ARC has removed the object (in this case would translate to the object Device *d1), hence no delegate messages. However, when I breakpoint the @continue in the main.m while loop the LoggerClass *logger obj still exists with d1 so it's not out of scope. Thanks. – Andy Mar 11 '13 at 09:13
  • Any chance the Mac firewall is blocking this? Have you tried a server running on localhost? Also, have you tried a known-working host and port, like an HTTP or SMTP server? – paulmelnikow Mar 11 '13 at 15:35
  • Erps, nevermind. See below. – paulmelnikow Mar 11 '13 at 15:45

1 Answers1

2

I just re-read your comment and realized what your main() looks like. I think that's where your problem lies. The callbacks are probably sitting in the main dispatch queue but it never gets a chance to execute them. In a Cocoa application, the main queue normally runs as part of the main run loop, but you aren't starting a run loop.

See the dispatch_get_main_queue() documentation.

I think the simplest initial fix is to replace your spin loop with this:

dispatch_main();
paulmelnikow
  • 16,895
  • 8
  • 63
  • 114
  • Great that worked by putting dispatch_main(); just before the while loop, although I haven't replaced any code...should I be? Is this the best place to put it or should it be in the Device Class? Thanks for your help. – Andy Mar 11 '13 at 21:36
  • Please do read the docs for dispatch_get_main_queue() and dispatch_main(). As I said, you don't need the while loop – it won't be executed. dispatch_main() never returns. – paulmelnikow Mar 11 '13 at 23:03