I'm attempting to make an iOS app communicate with a server that uses Bonjour and uses HTTP commands. So far I have been able to find the local domain and locate the particular service I'm looking for. I am able to resolve the address of the service, but I don't know how to get something useful out of the address. The address from the NSNetService is a NSData object and I have no idea what to do with it. I need to send commands like GET and PUT. What cocoa classes handle things like this?
I also tried getting input and output streams from the Service, but they seem to be extremely low level streams and I don't know how to properly deal with buffers and all that.
[service getInputStream:&inputStream outputStream:&outputStream]
the NSOutputStream write method takes in a uint8_t buffer which I have no idea how to create.
the NSInputStream read method returns a uint8_t buffer and I don't know how to interpret it.
I am able to communicate with this server using terminal commands. For instance, sending it the command LIST causes it to print out the list of files I am looking for. How do I send and get information like this in Cocoa?
To write data to the output stream, therefore sending it to the server:
NSString * stringToSend = #"Hello World!\n"; //The "\n" lets the receiving method described below function correctly. I don't know if you need it or not.
NSData * dataToSend = [stringToSend dataUsingEncoding:NSUTF8StringEncoding];
if (outputStream) {
int remainingToWrite = [dataToSend length];
void * marker = (void *)[dataToSend bytes];
while (0 < remainingToWrite) {
int actuallyWritten = 0;
actuallyWritten = [outputStream write:marker maxLength:remainingToWrite];
remainingToWrite -= actuallyWritten;
marker += actuallyWritten;
}
}
You can send any data like this, just put it in a NSData object.
To receive data from the server use this code in the input stream's NSStreamDelegate:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent {
NSInputStream * istream;
NSOutputStream * ostream;
switch(streamEvent) {
case NSStreamEventHasBytesAvailable:;
istream = (NSInputStream *)aStream;
ostream = (NSOutputStream *)CFDictionaryGetValue(connections, istream);
uint8_t buffer[2048];
int actuallyRead = [istream read:(uint8_t *)buffer maxLength:2048];
if (actuallyRead > 0) {
NSData *data;
data = [NSData dataWithBytes:buffer length:actuallyRead];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]autorelease];
string = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
//Do something with the string...
}
break;
case NSStreamEventEndEncountered:;
istream = (NSInputStream *)aStream;
ostream = nil;
if (CFDictionaryGetValueIfPresent(connections, istream, (const void **)&ostream)) {
[self shutdownInputStream:istream outputStream:ostream];
}
break;
case NSStreamEventHasSpaceAvailable:
case NSStreamEventErrorOccurred:
case NSStreamEventOpenCompleted:
case NSStreamEventNone:
default:
break;
}
}
Take a look at Apple's CocoaEcho Sample Code. It should help you.
Related
In my game, one player makes an array of skspritenodes in a random order that I need to send to all other players in the match. I am not sure if this is possible or if I need a workaround that does the same thing, but in examples, I have seen a typedef struct containing pointers like the one below
typedef struct {
Message message;
NSString * piece;
SKSpriteNode * pieces;
} MessagePieceList;
The code above gives me the error "ARC forbids Objective-C objects in structs" when I try creating the NSString or SKSpriteNode. I have also tried
__unsafe_unretained NSString * piece;
but then I believe I get an empty value when it reaches the other players in my didReceiveData since it is not retained. I've been able to send an integer no problem by doing the following
GameScene.m has:
_networkingEngine = [[MultiplayerNetworking alloc] init];
int piece = 5;
[_networkingEngine sendpiecesOrder:piece];
MultiplayerNetworking.h has:
-(void)sendpiecesOrder:(int)piece1;
MultiplayerNetworking.m has:
typedef struct {
Message message;
int piece;
} MessageList;
-(void)sendpiecesOrder:(int)piece {
MessageList messagePiece;
messagePiece.message.messageType = kMessagePieceOrder;
messagePiece.piece = piece;
NSData *data = [NSData dataWithBytes:&messagePiece length:sizeof(MessageList)];
[self sendData:data];
} else if(message->messageType == kMessagePieceOrder) {
MessageList *messagePieceList1 = ( MessageList *)[data bytes];
NSLog(#"Pieces order message received");
NSLog(#"%d", messagePieceList1->piece);
This prints 5 when the other player(s) receives the data in their didReceiveData method. If I cant directly send the array of SKSpriteNodes, is there a way to mold this approach so that it somehow communicates the list of SKSpriteNodes to another player?
I started writing a simple JSON RPC TCP library in Objective C.
I have a method that invokes a RPC Method:
- (void)invokeMethod:(NSString *)method
withParameters:(id)parameters
requestId:(id)requestId
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure
{
NSAssert(NSClassFromString(#"NSJSONSerialization"), #"NSJSONSerialization not found!");
NSDictionary *requestObject = #{#"jsonrpc": #"2.0",
#"method": method,
#"params": parameters,
#"id": requestId};
NSError *error = nil;
NSData *jsondData = [NSJSONSerialization dataWithJSONObject:requestObject options:0 error:&error];
if (error){
return failure(error);
}
[self->callbacks setObject:#{#"success": success ? [success copy] : [NSNull null],
#"failure": failure ? [failure copy] : [NSNull null]}
forKey:requestId];
NSString *str = [[NSString alloc] initWithData:jsondData encoding:NSUTF8StringEncoding];
NSLog(#"Sending: %#", str);
[self.socket writeData:jsondData withTimeout:-1 tag:1];
}
The class basically represents a TCP connection, when calling the above method, the JSON data is sent with an id over TCP to the server which either returns a success or a failure:
- (void) socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSError *error = nil;
[self.socket readDataWithTimeout:-1 tag:2];
// … rpc response parsing code here, removed for simplicity …
// detect if error or success
NSDictionary *cbs = [self->callbacks objectForKey:JSONRPCObjectId];
void(^success)(id resultObject) = [cbs objectForKey:#"success"];
success ? success(JSONRPCObjectResult) : nil;
return;
}
Now, I am unsure how to keep track of the success and failure blocks, currently I am storing them in an NSMutableDict, using the requestId as key. Is it fine to do this or is there a better approach that I should use?
Blocks in objective-c are objects and you can treat the same way as other object, so storing them in NSDictionarys, NSArrays etc is perfectly fine. The only catch is that blocks when initially created exist in the same memory scope as local variable do and so they are no longer valid when the method that the block is defined in returns, just like all other local variables so you have to copy them first, just copy them and put the copy in the collection. There is a block copy function but you can just send them a copy message [myBlock copy];
Quick answer, seeing as you don't have anything workable yet...
This is more than you asked for; so, you'll probably have to pair it down to meet your specific need. Basically, it stores as many blocks as you specify at contiguous memory addresses. Paste this into a header file or somewhere global to the method from which you will call these:
typedef const typeof(id(^)(void)) retained_object;
static id (^retainable_object)(id(^)(void)) = ^ id (id(^object)(void)) {
return ^{
return object();
};
};
typeof (retained_object) *(^(^retain_object)(id (^__strong)(void)))(void) = ^ (id(^retainable_object)(void)) {
typeof(retained_object) * object_address;
object_address = &retainable_object;
typeof(retained_object) * persistent_object = (typeof(retained_object) *)CFBridgingRetain(retainable_object);
return ^ typeof(retained_object) * {
return persistent_object;
};
};
static void (^(^iterator)(const unsigned long))(id(^)(void)) = ^ (const unsigned long object_count) {
id const * retained_objects_ref[object_count];
return ^ (id const * retained_objects_t[]) {
return ^ (id(^object)(void)) {
object();
int index = 0UL;
int * index_t = &index;
for (; (*index_t) < object_count; ((*index_t) = (*index_t) + 1UL)) printf("retained_object: %p\n", (*((id * const)retained_objects_t + (object_count - index)) = retain_object(retainable_object(object()))));
};
}(retained_objects_ref);
};
From some method, add:
iterator(1000)(^ id { return (^{ printf("stored block\n"); }); });
This should store 1,000 blocks at as many unique memory addresses.
I am having some trouble using the data that I receive from a remote server. This is how I take in the data from my nsinputstream:
case NSStreamEventHasBytesAvailable:
{
if(!_data)
{
_data = [NSMutableData data];
}
uint8_t buffer = malloc(1024);
NSInteger *len = [inputStream read:buffer maxLength:1024];
if(len)
{
_data = [[NSData alloc]initWithBytesNoCopy:buffer length:1024];
[self closeThread];
}
shouldClose = YES;
break;
}
In the same class I have this function to return the data in order to use it in different classes:
-(NSData *)returnData {
return self.data;
}
In the view controller that I want to use the data in I have this code to retrieve the data for use:
_schools = [_server returnData];
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];//exc_bad_access
From what I understand about EXC_BAD_ACCESS exceptions they usually mean that you are trying to access data that either doesn't exist or is not allocated. The _schools variable shows a size of 1024 bytes so I know there is memory correctly allocated for it. Is there something else going wrong that I am missing?
You appear to have mixed up the types of the variables on these two lines:
uint8_t buffer = malloc(1024);
NSInteger *len = [inputStream read:buffer maxLength:1024];
In its current form, you will malloc'ate 1024 bytes of memory, and attempt to store the pointer to said memory in a uint8_t (which CLANG will rightly scream at you for), thus truncating the pointer and not providing a buffer, but rather a single unsigned 8-bit byte for the stream to attempt to read into. Also, -[NSInputStream read:maxLength:] does not return NSInteger *, just plain NSInteger, so all you need to do is swap the pointers on the two variables:
uint8_t *buffer = malloc(1024);
NSInteger len = [inputStream read:buffer maxLength:1024];
and it should work just fine.
I am writing a text-editor and I would need to store a few pieces of information (generally just a few strings; the storage needn't be particularly durable) with each file the app saves (without that being part of the text-file as other apps might read it and the info is only specific to my app).
How would I go about this?
More info: I have a NSDocument set up and I would like to simply store a NSString instance variable as a per file meta-datum. Based on the answers below I've come up with this, which is currently buggy and causes the program to crash on startup:
#import <sys/xattr.h>
#interface MyDocument : NSDocument {
NSString *metadatum;
}
#implementation MyDocument
- (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)type error:(NSError **)err
{
BOOL output = [super writeToURL:url ofType:type error:err];
if(!setxattr([[url path] cStringUsingEncoding:NSUTF8StringEncoding],
"eu.gampleman.xattrs.style",
[metadatum cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(char) * [styleName length], 0, 0))
{
NSLog(#"Write failure");
}
return output;
}
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)type error:(NSError **)err {
char *output;
ssize_t bytes = getxattr([[url path] cStringUsingEncoding:NSUTF8StringEncoding],
"eu.gampleman.xattrs.style", &output, 1024, 0, 0);
if (bytes > 0) {
metadatum = [[NSString alloc] initWithBytes:output length:bytes
encoding:NSUTF8StringEncoding]; // <- crashes here with "EXC_BAD_ACCESS"
}
return [super readFromURL:url ofType:type error: err];
}
// ...
// fairly standard -dataOfType:error: and
// -readFromData:ofType:error: implementations
PS: If your answer is really good (with sample code, etc.), I will award a 100rep bounty.
Use extended attributes. See setxattr().
Here's a sample call to write a string:
NSData* encodedString = [theString dataUsingEncoding:NSUTF8StringEncoding];
int rc = setxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", [encodedString bytes], [encodedString length], 0, 0);
if (rc)
/* handle error */;
To read a string:
ssize_t len = getxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", NULL, 0, 0, 0);
if (len < 0)
/* handle error */;
NSMutableData* data = [NSMutableData dataWithLength:len];
len = getxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", [data mutableBytes], len, 0, 0);
NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
PS: Don't you have to set the bounty on the question before it's answered?
There are several places to store this kind of information on Mac. The most simple, of course, is to store it in your own separate metadata database. Of course there are challenges if the file moves. Since 10.6, however, you can use Bookmarks to address this problem. A Bookmark (see NSURL) allows you to keep a reference to a file even if it is moved (even across application restarts). Prior to 10.6 there was the Alias Manager, but it couldn't create new aliases; just read ones that Finder created.
The next common solution is to create metadata files. So if I have foo.txt, then you create a sibling .foo.txt.metadata to hold the extra info. Several trade-offs there if the files can be moved around.
Next is Spotlight, which can be used to attach arbitrary information to files. The actual tool here is xattr (see the man pages for setxattr and its relatives). These basically absorb several things that used to be done with Resource Forks (except xattr is supposed to just be metadata).
I am new to iOS development. I am attempting to create a read and write stream. I am using the CFNetworking programming guide's examples to try and get something working.
I am trying to schedule the read stream on the run loop to work around the issue of the streams blocking. Right away I have run into issues. How can I create a CFHost object using CFHhostCreateWithAddress? Here is what I have so far:
NSString *address = #"irc.ubuntu.net";
CFDataRef addressDataRef = (CFDataRef)[address dataUsingEncoding:NSASCIIStringEncoding];
CFHostRef host = CFHostCreateWithAddress(kCFAllocatorDefault, addressDataRef);
//Create Read and Write Stream
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, 8008, &readStream, &writeStream);
The second line bombs. Can someone please tell me how to create a CFHostRef?
Thanks a lot!
The documentation states that the second argument to CFHostCreateWithAddress() must be "A CFDataRef object containing a sockaddr structure for the address of the host. This value must not be NULL."
You're passing a CFDataRef representing "irc.ubuntu.net", which is by no means a sockaddr struct.
Use CFHostCreateWithName:
CFHostRef CFHostCreateWithName (
CFAllocatorRef allocator,
CFStringRef hostname
);
As you probably know, you can cast an NSString * to CFStringRef, or create a constant CFStringRef with the macro CFSTR().
+ (NSData *)dataForIPAddress:(NSString *)address {
struct addrinfo hints;
struct addrinfo *result = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
int errorStatus = getaddrinfo([address cStringUsingEncoding:NSASCIIStringEncoding], NULL, &hints, &result);
if (errorStatus != 0) return nil;
CFDataRef addressRef = CFDataCreate(NULL, (UInt8 *)result->ai_addr, result->ai_addrlen);
if (addressRef == nil) return nil;
freeaddrinfo(result);
return [(NSData *)addressRef autorelease];
}