GCDAsyncSocket not receiving all transmitted data, missing last "Chunk" - objective-c

I am trying to send some strings and image data from a python script to an objective C application running on OSX.
I am collecting the transmitted data, using GCDAsyncSocket, and appending it to an NSMutableData until the server disconnects. I am then processing that NSData and splitting it into it's original parts.
The transmitted data consists of the following:
ID string, filled out to 16 bytes.
Image number string, filled out to 16 bytes.
Raw image data.
Termination string, filled out to 16 bytes.
The problem is that i am not receiving/getting the last chunk of data, i end up missing the end of the JPEG image, resulting in a corrupt (though mostly displayed) image, and a missing termination string.
Here is the code i am using with GCDAsyncSocket to get the data, and process it:
Socket connection:
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
// This method is executed on the socketQueue (not the main thread)
#synchronized(connectedSockets)
{
[connectedSockets addObject:newSocket];
}
NSString *host = [newSocket connectedHost];
UInt16 port = [newSocket connectedPort];
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
[self logInfo:FORMAT(#"Accepted client %#:%hu", host, port)];
}
});
[newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
Socket Data Received
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
// This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSLog(#"Thread Data Length is %lu", (unsigned long)[data length]);
if (!imageBuffer){
imageBuffer = [[NSMutableData alloc]init];
}
[imageBuffer appendData:[data subdataWithRange:NSMakeRange(0, [data length])]];
NSLog(#"Total Data Length is %lu", (unsigned long)[imageBuffer length]);
}
});
// Echo message back to client
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
[sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
Socket Disconnected
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (sock != listenSocket)
{
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
[self logInfo:FORMAT(#"Client Disconnected")];
NSData *cameraNumberData;
NSData *imageNumberData;
NSData *imageData;
NSData *endCommandData;
//if ([data length] > 40){
cameraNumberData = [imageBuffer subdataWithRange:NSMakeRange(0, 16)];
imageNumberData = [imageBuffer subdataWithRange:NSMakeRange(16, 16)];
imageData = [imageBuffer subdataWithRange:NSMakeRange(32, [imageBuffer length]-34)];
endCommandData = [imageBuffer subdataWithRange:NSMakeRange([imageBuffer length]-16, 16)];
//}
NSString *cameraNumberString = [[NSString alloc] initWithData:cameraNumberData encoding:NSUTF8StringEncoding];
NSString *imageNumberString = [[NSString alloc] initWithData:imageNumberData encoding:NSUTF8StringEncoding];
NSString *endCommandString = [[NSString alloc] initWithData:endCommandData encoding:NSUTF8StringEncoding];
NSImage* image = [[NSImage alloc]initWithData:imageData];
if (cameraNumberString)
{
NSLog(#"Image recieved from Camera no %#", cameraNumberString);
[self logMessage:cameraNumberString];
}
else
{
[self logError:#"Error converting received data into UTF-8 String"];
}
if (imageNumberString)
{
NSLog(#"Image is number %#", imageNumberString);
[self logMessage:imageNumberString];
}
else
{
[self logError:#"Error converting received data into UTF-8 String"];
}
if (image)
{
NSLog(#"We have an image");
[self.imageView setImage:image];
}
else
{
[self logError:#"Error converting received data into image"];
}
if (endCommandString)
{
NSLog(#"Command String is %#", endCommandString);
[self logMessage:endCommandString];
}
else
{
[self logError:#"No command string"];
}
//self.imageBuffer = nil;
}
});
#synchronized(connectedSockets)
{
[connectedSockets removeObject:sock];
}
}
}
I have used wireshark, and the data is being transmitted, it's just not getting through GCDAsynSocket.
So, i'm obviously missing something. Socket programming and encoding/decoding of data like this is relatively new to me, so i am probably being an idiot.
Help greatly appreciated!
Thanks
Gareth

Ok, so i finally got this working. It involved modifying the transmitting code in Python to send a completion string at the end of the data, and watching for that. The biggest takeaway was that i needed to re-call the readDataToData: method each time the socket read some data, otherwise it would just sit there and wait, and the transmitting socket would also just sit there.
I also had to implement re-calling the second receive with a tag so i could store the received data in the correct NSMutableData object in an NSMutableArray, otherwise i had no way of knowing after the first receive which transmitting socket the data was coming from as the ID was only at the beginning of the first message.
Here is the didReadData code:
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSInteger cameraNumberNumber = 0;
NSString *cameraNumberString = [[NSString alloc]init];
if (tag > 10){
cameraNumberNumber = tag-11;
DDLogVerbose(#"Second data loop, tag is %ld", tag);
} else {
NSData *cameraNumberData;
//if ([data length] > 40){
cameraNumberData = [data subdataWithRange:NSMakeRange(0, 16)];
NSString *cameraNumberString = [[NSString alloc] initWithData:cameraNumberData encoding:NSUTF8StringEncoding];
cameraNumberString = [cameraNumberString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
cameraNumberNumber = [cameraNumberString intValue]-1;
}
if (cameraNumberNumber+1 <= self.images.count){
if ([self.images objectAtIndex:cameraNumberNumber] == [NSNull null]){
image* cameraImage = [[image alloc]init];
[self.images replaceObjectAtIndex: cameraNumberNumber withObject:cameraImage];
}
image* cameraImage = [self.images objectAtIndex:cameraNumberNumber];
[cameraImage.imageData appendData:[data subdataWithRange:NSMakeRange(0, [data length])]];
cameraImage.cameraNumber = cameraNumberString;
if (!imageBuffer){
imageBuffer = [[NSMutableData alloc]init];
}
[imageBuffer appendData:[data subdataWithRange:NSMakeRange(0, [data length])]];
DDLogVerbose(#"Total Data Length is %lu", (unsigned long)[imageBuffer length]);
} else {
DDLogInfo(#"Wrong camera quantity!");
NSAlert *testAlert = [NSAlert alertWithMessageText:#"Wrong camera quantity!"
defaultButton:#"Ok"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:#"We have recieved more images than cameras, please set No.Cameras correctly!"];
[testAlert beginSheetModalForWindow:[self window]
modalDelegate:self
didEndSelector:#selector(stop)
contextInfo:nil];
}
[sock readDataToData:[#"end" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:cameraNumberNumber + 11];
}
});
}
and here is the socketDidDisconnect code, a lot of things in here that don't make sense out of context, but it shows how i handled the received data.
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (sock != listenSocket)
{
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
totalCamerasFetched = [NSNumber numberWithInt:1+[totalCamerasFetched intValue]];
if ([totalCamerasFetched integerValue] >= [numberOfCameras integerValue]){
for (image* cameraImage in self.images){
NSData *cameraNumberData;
NSData *imageNumberData;
NSData *imageData;
NSData *endCommandData;
NSInteger cameraNumberNumber = 0;
cameraNumberData = [cameraImage.imageData subdataWithRange:NSMakeRange(0, 16)];
imageNumberData = [cameraImage.imageData subdataWithRange:NSMakeRange(16, 16)];
imageData = [cameraImage.imageData subdataWithRange:NSMakeRange(32, [cameraImage.imageData length]-32)];
endCommandData = [cameraImage.imageData subdataWithRange:NSMakeRange([cameraImage.imageData length]-16, 16)];
NSString *cameraNumberString = [[NSString alloc] initWithData:cameraNumberData encoding:NSUTF8StringEncoding];
cameraNumberString = [cameraNumberString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *imageNumberString = [[NSString alloc] initWithData:imageNumberData encoding:NSUTF8StringEncoding];
imageNumberString = [imageNumberString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *endCommandString = [[NSString alloc] initWithData:endCommandData encoding:NSUTF8StringEncoding];
NSImage* image = [[NSImage alloc]initWithData:imageData];
cameraNumberNumber = [cameraNumberString intValue]-1;
if (cameraNumberString)
{
DDLogInfo(#"Image recieved from Camera no %#", cameraNumberString);
}
else
{
DDLogError(#"No Camera number in data");
}
if (imageNumberString)
{
DDLogInfo(#"Image is number %#", imageNumberString);
}
else
{
DDLogError(#"No Image number in data");
}
if (image)
{
DDLogVerbose(#"We have an image");
NSString* dataPath = [[NSString alloc]initWithFormat:#"%#/image%#/",self.exportLocation, imageNumberString];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]){
NSError* error;
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error];
if (error)
{
DDLogError(#"[%#] ERROR: attempting to write directory for images", [self class]);
NSAssert( FALSE, #"Failed to create directory maybe out of disk space?");
}
}
NSString* dataPathVideo = [[NSString alloc]initWithFormat:#"%#/video%#/",self.exportLocation, imageNumberString];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPathVideo]){
NSError* error;
[[NSFileManager defaultManager] createDirectoryAtPath:dataPathVideo withIntermediateDirectories:NO attributes:nil error:&error];
if (error)
{
DDLogError(#"[%#] ERROR: attempting to write directory for images", [self class]);
NSAssert( FALSE, #"Failed to create directory maybe out of disk space?");
}
}
NSString * exportLocationFull = [[NSString alloc]initWithFormat:#"%#/image%#/camera_%#.jpg",self.exportLocation, imageNumberString, cameraNumberString];
DDLogInfo(#"Full export URL = %#", exportLocationFull);
[imageData writeToFile:exportLocationFull atomically:YES];
self.currentSet = [NSNumber numberWithInt:[imageNumberString intValue]];
NSImage* imageToStore = [[NSImage alloc]initWithData:imageData];
[self.imagesToMakeVideo replaceObjectAtIndex: cameraNumberNumber withObject:imageToStore];
} else {
DDLogError(#"No image loacted in data");
}
if (endCommandString)
{
DDLogVerbose(#"Command String is %#", endCommandString);
//[self logMessage:endCommandString];
}
else
{
//[self logError:#"No command string"];
}
self.imageBuffer = nil;
}
self.totalCamerasFetched = [NSNumber numberWithInt:0];
[self loadandDisplayLatestImages];
[self createVideowithImages:imagesToMakeVideo toLocation:[[NSString alloc]initWithFormat:#"%#/video%#/image_sequence_%#.mov",self.exportLocation, self.currentSet, self.currentSet]];
processing = false;
}//end of for loop
}
});
#synchronized(connectedSockets)
{
[connectedSockets removeObject:sock];
}
}
}
also here is how i modified the Python code to add the extra "end" tag.
def send_media_to(self, ip, port, media_name, media_number, media_dir):
camera_number = self.camera.current_mode['option'].number
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
try:
sock.send(bytes(str(camera_number).ljust(16), 'utf-8'))
sock.send(bytes(str(media_number).ljust(16), 'utf-8'))
with open(media_dir + media_name, 'rb') as media:
sock.sendall(media.read())
finally:
sock.send(bytes(str("end").ljust(16), 'utf-8'))
sock.close()
Hopefully this helps someone else stuck in the same situation!

Related

ObjectiveC-When I add custom class to userInfo of NSUserNotification, it may cause userInfo to become nil value?

macOS 12.2.1
Xcode 13.3
I'm a beginner in Objective-C. When I try to create a local notification, I save an object of a custom class to the userInfo property of NSUserNotification, but the userInfo seems to be assigned to a nil value.
I will provide a simple code below to demonstrate this.
#import <Cocoa/Cocoa.h>
#interface MyData : NSObject<NSSecureCoding>
#end
#implementation MyData{
double data;
}
-(instancetype)initWithData:(double)data{
[super init];
self->data = data;
return self;
}
+(BOOL)supportsSecureCoding{
return YES;
}
-(void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeDouble:data forKey:#"data"];
}
-(nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]){
self->data = [coder decodeDoubleForKey:#"data"];
}
return self;
}
#end
int main(int argc, const char * argv[]) {
NSUserNotification* n = [NSUserNotification new];
MyData* myData = [[MyData alloc] initWithData: 1.0];
n.userInfo = #{#"MyData":myData};
assert(n.userInfo == nil);// nil userInfo
n.userInfo = #{#"MyData":#"Other"};
assert(n.userInfo != nil);// but not nil here.
return 0;
}
// Why? How can I pass my custom object?
Let's see the documentation of userInfo:
All items must be property list types or an exception is thrown.
Does MyData is one of the property list types? No. The allowed types are listed here.
Now, let's do some test:
NSUserNotification* n = [NSUserNotification new];
NSString *errorDescription = nil;
MyData* myData = [[MyData alloc] initWithData: 1.0];
// String: String
NSDictionary *basicDict = #{#"MyData": #"Other"};
n.userInfo = basicDict;
NSLog(#"UserInfo Basic: %#", n.userInfo);
id plistStrTest = [NSPropertyListSerialization dataFromPropertyList:basicDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
if (errorDescription) {
NSLog(#"Error while serializing: %#", errorDescription);
}
NSLog(#"Plist Basic: %#", [[NSString alloc] initWithData:plistStrTest encoding:NSUTF8StringEncoding]);
// String: MyData
NSDictionary *rawDataDict = #{#"MyData": myData};
n.userInfo = rawDataDict;
NSLog(#"UserInfo Data: %#", n.userInfo);
errorDescription = nil;
id plistRawData = [NSPropertyListSerialization dataFromPropertyList:rawDataDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
if (errorDescription) {
NSLog(#"Error while serializing: %#", errorDescription);
}
NSLog(#"Plist Raw: %#", [[NSString alloc] initWithData:plistRawData encoding:NSUTF8StringEncoding]);
NSDictionary *combinedRawAndBasic = #{#"MyData":#"Other", #"MyData2": myData};
n.userInfo = combinedRawAndBasic;
NSLog(#"UserInfo Combined: %#", n.userInfo);
id plistCombined = [NSPropertyListSerialization dataFromPropertyList:combinedRawAndBasic
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
if (errorDescription) {
NSLog(#"Error while serializing: %#", errorDescription);
}
NSLog(#"Plist Combined: %#", [[NSString alloc] initWithData:plistCombined encoding:NSUTF8StringEncoding]);
// With Encoding, i.e with NSData
NSError *encodingError = nil;
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:myData requiringSecureCoding:YES error:&encodingError];
if (encodingError) {
NSLog(#"Error while encoding: %#", encodingError);
}
NSDictionary *encodedDict = #{#"MyData": #"Other", #"MyData2": encodedData};
n.userInfo = encodedDict;
NSLog(#"UserInfo encoded: %#", n.userInfo);
errorDescription = nil;
id plistEncodedData = [NSPropertyListSerialization dataFromPropertyList:encodedDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
if (errorDescription) {
NSLog(#"Error while serializing: %#", errorDescription);
}
NSLog(#"Plist: %#", [[NSString alloc] initWithData:plistEncodedData encoding:NSUTF8StringEncoding]);
You'll see then, and it concords with the documentation that if you put directly a MyData object, it will reject all the dictionary. But not if you encode it first with NSKeyedArchiver.
Strangely, since the doc talks about an exception thrown, I would have a expect a NSUncaughtException in console and a crash, but there isn't, it's a silent one, that just reject the userInfo set.
You'll need later NSKeyedUnarchiver to decode the encoded data.

Type-check: How to check if NSData is an NSArray?

I'm currently working with the Multipeer Connectivity Framework in XCode for an iPad App.
I want to send messages with the framework (NSStrings, Booleans & NSArrays) and Strings are working fine, but I need some sort of type-check to convert the NSData object into a String, Array, etc.
This is what my didReceiveData methode looks like:
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSMutableArray *recievedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if ([message isEqualToString:#"PeerIsConnected"]) {
NSLog(#"Peer sended: Connected!");
self.connectionIsOn = TRUE;
dispatch_async(dispatch_get_main_queue(),^{
[self changeConnectionButton:TRUE];
});
NSLog(#"Connection is on (data received) : %#", (self.connectionIsOn) ? #"YES" : #"NO");
}
if ([message isEqualToString:#"Disconnect"]) {
NSLog(#"Peer sended: Disconnect!");
self.connectionIsOn = FALSE;
dispatch_async(dispatch_get_main_queue(),^{
[self changeConnectionButton:FALSE];
});
}
if ([message isEqualToString:#"GoWasClicked"]) {
self.muliplayergameIsOn = TRUE;
self.myTurn = TRUE;
self.startDate = [NSDate date];
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
I need some if case so I can cast the NSData object into a NSString OR NSArray OR etc.
How can I solve this problem?
Thanks in advance!
EDIT: This is how the Array is sended:
- (void) sendArray:(NSMutableArray *) arrayToSend{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arrayToSend];
NSError *error;
[self.mySession sendData:data toPeers:[self.mySession connectedPeers] withMode:MCSessionSendDataUnreliable error:&error];
}
You could use NSKeyedArchiver for all kinds of objects that you send
(string, array, ...). Then you can check on the receiving side with
id receivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if ([receivedObject isKindOfClass:[NSArray class]]) {
NSArray *receivedArray = receivedObject;
// handle array ...
} else if ([receivedObject isKindOfClass:[NSString class]]) {
NSString *receivedString = receivedObject;
// handle string ...
}
//Added one more condition here...
id receivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if ([receivedObject isKindOfClass:[NSArray class]]) {
NSArray *receivedArray = receivedObject;
// handle array ...
}
else if ([receivedObject isKindOfClass:[NSDictionary class]]) {
NSString *receivedString = receivedObject;
// handle dictionary ...
}
else if ([receivedObject isKindOfClass:[NSString class]]) {
NSString *receivedString = receivedObject;
// handle string ...
}

Objective-C File Copy Multithreading?

I am trying to write a program using Objective-C/XCode that backs up one directory (source dir) into another (dest dir).
When I test the program on a small directory on my local machine, it works as expected. But when I try a large directory, or anything over a network, the program beachballs. I know that threading is the answer. Given the following code one can tell I have been fiddling with various methods to do this. Can anyone help out? I can't seem to get this working properly.
Here is the code/method in question:
- (void)doSync:(NSString *)sURL {
bStopCopy = NO;
NSString *sSource = [[pcSource URL] path];
NSString *sDestination = [[pcDestination URL] path];
NSString *sSourcePath = [sSource stringByAppendingString:#"/"];
NSString *sDestinationPath = [sDestination stringByAppendingString:#"/"];
NSString *sSourceFile;
NSString *sDestinationFile;
NSString* file;
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:sURL];
while ((file = [enumerator nextObject]) && (bStopCopy == NO)) {
[btMainWindowStopQuitButton setTitle: #"Stop..."];
[btMainWindowStopQuitButton setTag:1];
bCopyInProgress = YES;
__block NSError *eErrorMessage;
sSourceFile = [sSourcePath stringByAppendingString:file];
sDestinationFile = [sDestinationPath stringByAppendingString:file];
// check if it's a directory & exists at destination
BOOL isDirectory = NO;
BOOL isFileExistingAtDestination = NO;
__block BOOL isThereAnError = NO;
[[NSFileManager defaultManager] fileExistsAtPath: [NSString stringWithFormat:#"%#/%#",sURL,file]
isDirectory: &isDirectory];
isFileExistingAtDestination = [[NSFileManager defaultManager] fileExistsAtPath: sDestinationFile];
if (!isDirectory) {
if (!isFileExistingAtDestination) {
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// if (![[NSFileManager defaultManager] copyItemAtPath:sSourceFile toPath:sDestinationFile error: &eErrorMessage]) {
// NSLog(#"File Copy Error: %#", eErrorMessage);
// isThereAnError = YES;
// }
// });
//[oqFileCopy addOperationWithBlock:^{
dispatch_queue_t copyQueue = dispatch_queue_create("Copy File", NULL);
dispatch_async(copyQueue, ^{
if (![[NSFileManager defaultManager] copyItemAtPath:sSourceFile toPath:sDestinationFile error: &eErrorMessage]) {
NSLog(#"File Copy Error: %#", eErrorMessage);
isThereAnError = YES;
}
//[oqMain addOperationWithBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
llFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath: sDestinationFile error: Nil] fileSize];
[[[tvDialogueLabel textStorage] mutableString] setString:
[NSString stringWithFormat:#"%#\nCopied to: %# (%qu bytes)", [[tvDialogueLabel textStorage] string], sDestinationFile, llFileSize]];
NSRange endPoint = NSMakeRange ([[tvDialogueLabel string] length], 0);
[tvDialogueLabel scrollRangeToVisible: endPoint];
llTotalFileSize = llTotalFileSize + llFileSize;
});
});
// NSLog(#"%#", sSourceFile);
// NSLog(#"%#", sDestinationFile);
} else if (isFileExistingAtDestination) {
[[[tvDialogueLabel textStorage] mutableString] setString:
[NSString stringWithFormat:#"%#\nFile: %# | Already Synced.", [[tvDialogueLabel textStorage] string], sDestinationFile]];
NSRange endPoint = NSMakeRange ([[tvDialogueLabel string] length], 0);
[tvDialogueLabel scrollRangeToVisible: endPoint];
}
}
else if (isDirectory) {
if (!isFileExistingAtDestination) {
if (![[NSFileManager defaultManager] createDirectoryAtPath:sDestinationFile withIntermediateDirectories:YES attributes:nil error: &eErrorMessage]){
NSLog(#"Directory Create Failed: %#", eErrorMessage);
isThereAnError = YES;
}
[[[tvDialogueLabel textStorage] mutableString] setString:
[NSString stringWithFormat:#"%#\nCreated Directory: %#", [[tvDialogueLabel textStorage] string], sDestinationFile]];
NSRange endPoint = NSMakeRange ([[tvDialogueLabel string] length], 0);
[tvDialogueLabel scrollRangeToVisible: endPoint];
// NSLog(#"%#", sSourceFile);
// NSLog(#"%#", sDestinationFile);
} else if (isFileExistingAtDestination) {
[[[tvDialogueLabel textStorage] mutableString] setString:
[NSString stringWithFormat:#"%#\nDirectory: %# | Already Exists.", [[tvDialogueLabel textStorage] string], sDestinationFile]];
NSRange endPoint = NSMakeRange ([[tvDialogueLabel string] length], 0);
[tvDialogueLabel scrollRangeToVisible: endPoint];
}
[self doSync: file];
}
if (isThereAnError) {
NSLog(#"There was an error!");
//[_wDialogue setTitle: #"Error while syncing..."];
break;
}
// NSLog(#"%#", #"==================================================");
}
}
The easiest way to do this might be to remove all the block code from your method, and simply make your call to doSync: using performSelectorInBackground:withObject:. For example:
[foo performSelectorInBackground:#selector(doSync:) withObject:myURL];
Another easy way of doing this, if you're using OSX 10.6 and up, would be to throw all this code to Grand Central Dispatch. The while loop is what needs to be on a different thread, that's the one that's holding up the main thread. By wrapping the whole thing into a dispatch_async(), you're moving that while loop on a different thread as well.
- (void)doSync:(NSString *)sURL {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// your doSync code goes here
});
}

AsyncSocket WHOIS connect

I'm trying to connect to WHOIS servers (com.whois-servers.net:43) using GCDAsyncSocket. I can connect and write to the server and the didWriteDataWithTag: gets called but there is no data. The WHOIS server does in fact sends a response but I cant read it.
NSLog(#"Connecting to \"%#\" on port %hu...", host, port);
self.viewController.label.text = #"Connecting...";
NSError *error = nil;
if (![asyncSocket connectToHost:#"com.whois-servers.net" onPort:43 error:&error])
{
DDLogError(#"Error connecting: %#", error);
self.viewController.label.text = #"Oops";
}
NSString *requestStr = [NSString stringWithFormat:#"domain google.com\r\n\r\n"];
NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding];
[asyncSocket writeData:requestData withTimeout:-1 tag:0];
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
Here is the didReadData:
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
DDLogInfo(#"socket:%p didReadData:withTag:%d", sock, tag);
NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Data: %i", [data length]);
}
Help?!
I ran into the same problem using AsyncSocket. I never fully solved it, but found a close solution.
In onSocket:willDisconnectWithError (GapSocketCommand.m) you can get the current buffer when the connection fails by calling unreadData.
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
NSLog(#"onSocket:willDisconnectWithError %#",err);
NSData * unreadData = [sock unreadData]; // ** This gets the current buffer
if(unreadData) {
[self onSocket:sock didReadData:unreadData withTag:NULL]; // ** Return as much data that could be collected
} else {
NSString* jsString = [[NSString alloc] initWithFormat:#"GapSocket.__onError(\"%d\",\"%#\");"
,[sock userData]
,[err localizedDescription] ];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
//[jsString release];
}
}
You'll have to modify it to apply to GCDAsyncSocket, but the solution should be the same.

Handling NSError when reading from file?

I am just curious if I am doing this right.
NSString *fileContents;
NSError *fileError = nil;
fileContents = [[NSString stringWithContentsOfFile:fileOnDisk
encoding:NSMacOSRomanStringEncoding
error:&fileError] retain];
if(fileError != nil) {
NSLog(#"Error : %#", [fileError localizedDescription]);
}
// Other Code ...
[fileContents release];
.
EDIT (to reflect bbums comments)
.
NSString *fileOnDisk = #"/Users/Gary/Documents/Xcode/RnD/Maya.MEL";
NSError *fileError; // Should this be *fileError = nil;
NSString *fileContents;
int status = 0;
fileContents = [[NSString stringWithContentsOfFile:fileOnDisk
encoding:NSMacOSRomanStringEncoding
error:&fileError] retain];
if(fileContents == nil) {
NSLog(#"FileError: %#", [fileError localizedDescription]);
status = 1;
} else {
NSLog(#"Success : %#", fileContents);
}
// Clean up
[fileContents release];
[pool drain];
return status;
gary
NSError *fileError = nil;
....
if(fileError != nil)
....
That is incorrect. You must not assume anything about the return-by-reference value of fileError until you check whether or not fileContents was nil. Not ever. Setting fileError to nil prior to calling the pass-error-by-reference method does nothing useful.
That is, your code should read (fixed now that I'm no longer running from plane to plane and hopping on WiFi in between connections...):
NSString *fileContents;
NSError *fileError;
fileContents = [[NSString stringWithContentsOfFile:fileOnDisk
encoding:NSMacOSRomanStringEncoding
error:&fileError] retain];
if(fileContents == nil) {
NSLog(#"Error : %#", [fileError localizedDescription]);
// ... i.e. handle the error here more
return ...; // often returning after handling the errors, sometimes you might continue
}
// Other Code ...
[fileContents release];