Will dispatch_async inside autoreleasepool cause issues? - objective-c

I'm parsing midi messages inside an autoreleasepool. After determining the message that's being sent, I make calls to the main thread using dispatch_async(dispatch_get_main_queue(). This seems to work well most of the time but occasionally I'll get a crash where it stops on the line
while (semaphore_wait(midiReceivedSemaphore) == KERN_SUCCESS ) {
The method looks something like this:
#autoreleasepool {
unsigned int maxPacketLength = 0x100;
unsigned char* data = malloc(maxPacketLength);
UInt16 length;
while (semaphore_wait(midiReceivedSemaphore) == KERN_SUCCESS ) {
midi_packet_buffer_next_packet_length(midiPacketBuffer, &length);
if( length > 0) {
length = midi_packet_buffer_read(midiPacketBuffer, data, length);
for (unsigned int packetContentsIndex = 0; packetContentsIndex < length; packetContentsIndex++) {
while (packetContentsIndex < length) {
Byte command = data[packetContentsIndex];
switch (command) {
case 0xA0: //aftertouch
commandLength += 2;
int meterChannel = data[packetContentsIndex +1];
int meterValue = data[packetContentsIndex +2];
if (meterValue < 0x10)
{
dispatch_async(dispatch_get_main_queue(), ^{
[delegate sendLevelMeterPT:meterChannel withValue:meterValue ];
});
}
break;
default:
break;
}
}
}
}
}
free(data);
}
Is this a very bad way of handling the MIDI messages? The reason I used dispatch_async is so that it wouldn't slow down the midi processing thread, before using dispatch_async the method was missing some messages if they were coming in fast. Now it catches all the messages but I get the occasional crash. I'm thinking I might have to rewrite the whole midi processing method but want to know the proper way to go about it. Any advice would be great.

Related

How to properly close a FFmpeg stream and AVFormatContext without leaking memory?

I have built an app that uses FFmpeg to connect to remote IP cameras in order to receive video and audio frames via RTSP 2.0.
The app is built using Xcode 10-11 and Objective-C with a custom FFmpeg build config.
The architecture is the following:
MyApp
Document_0
RTSPContainerObject_0
RTSPObject_0
RTSPContainerObject_1
RTSPObject_1
...
Document_1
...
GOAL:
After closing Document_0 no FFmpeg objects should be leaked.
The closing process should stop-frame reading and destroy all objects which use FFmpeg.
PROBLEM:
Somehow Xcode's memory debugger shows two instances of MyApp.
FACTS:
macOS'es Activity Monitor doesn't show two instances of MyApp.
macOS'es Activity Monitor doesn't any instances of FFmpeg or other child processes.
The issue is not related to some leftover memory due to a late memory snapshot since it can be reproduced easily.
Xcode's memory debugger shows that the second instance only having RTSPObject's AVFormatContext and no other objects.
The second instance has an AVFormatContext and the RTPSObject still has a pointer to the AVFormatContext.
FACTS:
Opening and closing the second document Document_1 leads to the same problem and having two objects leaked. This means that there is a bug that creates scalable problems. More and more memory is used and unavailable.
Here is my termination code:
- (void)terminate
{
// * Video and audio frame provisioning termination *
[self stopVideoStream];
[self stopAudioStream];
// *
// * Video codec termination *
avcodec_free_context(&_videoCodecContext); // NULL pointer safe.
self.videoCodecContext = NULL;
// *
// * Audio codec termination *
avcodec_free_context(&_audioCodecContext); // NULL pointer safe.
self.audioCodecContext = NULL;
// *
if (self.packet)
{
// Free the packet that was allocated by av_read_frame.
av_packet_unref(&packet); // The documentation doesn't mention NULL safety.
self.packet = NULL;
}
if (self.currentAudioPacket)
{
av_packet_unref(_currentAudioPacket);
self.currentAudioPacket = NULL;
}
// Free raw frame data.
av_freep(&_rawFrameData); // NULL pointer safe.
// Free the swscaler context swsContext.
self.isFrameConversionContextAllocated = NO;
sws_freeContext(scallingContext); // NULL pointer safe.
[self.audioPacketQueue removeAllObjects];
self.audioPacketQueue = nil;
self.audioPacketQueueLock = nil;
self.packetQueueLock = nil;
self.audioStream = nil;
BXLogInDomain(kLogDomainSources, kLogLevelVerbose, #"%s:%d: All streams have been terminated!", __FUNCTION__, __LINE__);
// * Session context termination *
AVFormatContext *pFormatCtx = self.sessionContext;
BOOL shouldProceedWithInputSessionTermination = self.isInputStreamOpen && self.shouldTerminateStreams && pFormatCtx;
NSLog(#"\nTerminating session context...");
if (shouldProceedWithInputSessionTermination)
{
NSLog(#"\nTerminating...");
//av_write_trailer(pFormatCtx);
// Discard all internally buffered data.
avformat_flush(pFormatCtx); // The documentation doesn't mention NULL safety.
// Close an opened input AVFormatContext and free it and all its contents.
// WARNING: Closing an non-opened stream will cause avformat_close_input to crash.
avformat_close_input(&pFormatCtx); // The documentation doesn't mention NULL safety.
NSLog(#"Logging leftovers - %p, %p %p", self.sessionContext, _sessionContext, pFormatCtx);
avformat_free_context(pFormatCtx);
NSLog(#"Logging content = %c", *self.sessionContext);
//avformat_free_context(pFormatCtx); - Not needed because avformat_close_input is closing it.
self.sessionContext = NULL;
}
// *
}
IMPORTANT: The termination sequence is:
New frame will be read.
-[(RTSPObject)StreamInput currentVideoFrameDurationSec]
-[(RTSPObject)StreamInput frameDuration:]
-[(RTSPObject)StreamInput currentCGImageRef]
-[(RTSPObject)StreamInput convertRawFrameToRGB]
-[(RTSPObject)StreamInput pixelBufferFromImage:]
-[(RTSPObject)StreamInput cleanup]
-[(RTSPObject)StreamInput dealloc]
-[(RTSPObject)StreamInput stopVideoStream]
-[(RTSPObject)StreamInput stopAudioStream]
Terminating session context...
Terminating...
Logging leftovers - 0x109ec6400, 0x109ec6400 0x109ec6400
Logging content = \330
-[Document dealloc]
NOT WORKING SOLUTIONS:
Changing the order of object releases (The AVFormatContext has been freed first but it didn't lead to any change).
Calling RTSPObject's cleanup method much sooner to give FFmpeg more time to handle object releases.
Reading a lot of SO answers and FFmpeg documentation to find a clean cleanup process or newer code which might highlight why the object release doesn't happen properly.
I am currently reading the documentation on AVFormatContext since I believe that I am forgetting to release something. This believe is based on the memory debuggers output that AVFormatContext is still around.
Here is my creation code:
#pragma mark # Helpers - Start
- (NSError *)openInputStreamWithVideoStreamId:(int)videoStreamId
audioStreamId:(int)audioStreamId
useFirst:(BOOL)useFirstStreamAvailable
inInit:(BOOL)isInitProcess
{
// NSLog(#"%s", __PRETTY_FUNCTION__); // RTSP
self.status = StreamProvisioningStatusStarting;
AVCodec *decoderCodec;
NSString *rtspURL = self.streamURL;
NSString *errorMessage = nil;
NSError *error = nil;
self.sessionContext = NULL;
self.sessionContext = avformat_alloc_context();
AVFormatContext *pFormatCtx = self.sessionContext;
if (!pFormatCtx)
{
// Create approp error.
return error;
}
// MUST be called before avformat_open_input().
av_dict_free(&_sessionOptions);
self.sessionOptions = 0;
if (self.usesTcp)
{
// "rtsp_transport" - Set RTSP transport protocols.
// Allowed are: udp_multicast, tcp, udp, http.
av_dict_set(&_sessionOptions, "rtsp_transport", "tcp", 0);
}
av_dict_set(&_sessionOptions, "rtsp_transport", "tcp", 0);
// Open an input stream and read the header with the demuxer options.
// WARNING: The stream must be closed with avformat_close_input()
if (avformat_open_input(&pFormatCtx, rtspURL.UTF8String, NULL, &_sessionOptions) != 0)
{
// WARNING: Note that a user-supplied AVFormatContext (pFormatCtx) will be freed on failure.
self.isInputStreamOpen = NO;
// Create approp error.
return error;
}
self.isInputStreamOpen = YES;
// user-supplied AVFormatContext pFormatCtx might have been modified.
self.sessionContext = pFormatCtx;
// Retrieve stream information.
if (avformat_find_stream_info(pFormatCtx,NULL) < 0)
{
// Create approp error.
return error;
}
// Find the first video stream
int streamCount = pFormatCtx->nb_streams;
if (streamCount == 0)
{
// Create approp error.
return error;
}
int noStreamsAvailable = pFormatCtx->streams == NULL;
if (noStreamsAvailable)
{
// Create approp error.
return error;
}
// Result. An Index can change, an identifier shouldn't.
self.selectedVideoStreamId = STREAM_NOT_FOUND;
self.selectedAudioStreamId = STREAM_NOT_FOUND;
// Fallback.
int firstVideoStreamIndex = STREAM_NOT_FOUND;
int firstAudioStreamIndex = STREAM_NOT_FOUND;
self.selectedVideoStreamIndex = STREAM_NOT_FOUND;
self.selectedAudioStreamIndex = STREAM_NOT_FOUND;
for (int i = 0; i < streamCount; i++)
{
// Looking for video streams.
AVStream *stream = pFormatCtx->streams[i];
if (!stream) { continue; }
AVCodecParameters *codecPar = stream->codecpar;
if (!codecPar) { continue; }
if (codecPar->codec_type==AVMEDIA_TYPE_VIDEO)
{
if (stream->id == videoStreamId)
{
self.selectedVideoStreamId = videoStreamId;
self.selectedVideoStreamIndex = i;
}
if (firstVideoStreamIndex == STREAM_NOT_FOUND)
{
firstVideoStreamIndex = i;
}
}
// Looking for audio streams.
if (codecPar->codec_type==AVMEDIA_TYPE_AUDIO)
{
if (stream->id == audioStreamId)
{
self.selectedAudioStreamId = audioStreamId;
self.selectedAudioStreamIndex = i;
}
if (firstAudioStreamIndex == STREAM_NOT_FOUND)
{
firstAudioStreamIndex = i;
}
}
}
// Use first video and audio stream available (if possible).
if (self.selectedVideoStreamIndex == STREAM_NOT_FOUND && useFirstStreamAvailable && firstVideoStreamIndex != STREAM_NOT_FOUND)
{
self.selectedVideoStreamIndex = firstVideoStreamIndex;
self.selectedVideoStreamId = pFormatCtx->streams[firstVideoStreamIndex]->id;
}
if (self.selectedAudioStreamIndex == STREAM_NOT_FOUND && useFirstStreamAvailable && firstAudioStreamIndex != STREAM_NOT_FOUND)
{
self.selectedAudioStreamIndex = firstAudioStreamIndex;
self.selectedAudioStreamId = pFormatCtx->streams[firstAudioStreamIndex]->id;
}
if (self.selectedVideoStreamIndex == STREAM_NOT_FOUND)
{
// Create approp error.
return error;
}
// See AVCodecID for codec listing.
// * Video codec setup:
// 1. Find the decoder for the video stream with the gived codec id.
AVStream *stream = pFormatCtx->streams[self.selectedVideoStreamIndex];
if (!stream)
{
// Create approp error.
return error;
}
AVCodecParameters *codecPar = stream->codecpar;
if (!codecPar)
{
// Create approp error.
return error;
}
decoderCodec = avcodec_find_decoder(codecPar->codec_id);
if (decoderCodec == NULL)
{
// Create approp error.
return error;
}
// Get a pointer to the codec context for the video stream.
// WARNING: The resulting AVCodecContext should be freed with avcodec_free_context().
// Replaced:
// self.videoCodecContext = pFormatCtx->streams[self.selectedVideoStreamIndex]->codec;
// With:
self.videoCodecContext = avcodec_alloc_context3(decoderCodec);
avcodec_parameters_to_context(self.videoCodecContext,
codecPar);
self.videoCodecContext->thread_count = 4;
NSString *description = [NSString stringWithUTF8String:decoderCodec->long_name];
// 2. Open codec.
if (avcodec_open2(self.videoCodecContext, decoderCodec, NULL) < 0)
{
// Create approp error.
return error;
}
// * Audio codec setup:
if (self.selectedAudioStreamIndex > -1)
{
[self setupAudioDecoder];
}
// Allocate a raw video frame data structure. Contains audio and video data.
self.rawFrameData = av_frame_alloc();
self.outputWidth = self.videoCodecContext->width;
self.outputHeight = self.videoCodecContext->height;
if (!isInitProcess)
{
// Triggering notifications in init process won't change UI since the object is created locally. All
// objects which need data access to this object will not be able to get it. Thats why we don't notifiy anyone about the changes.
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.rtspVideoStreamSelectionChanged
object:nil userInfo: self.selectedVideoStream];
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.rtspAudioStreamSelectionChanged
object:nil userInfo: self.selectedAudioStream];
}
return nil;
}
UPDATE 1
The initial architecture allowed using any given thread. Most of the below code would mostly run on the main thread. This solution was not appropriate since the opening of the stream input can take several seconds for which the main thread is blocked while waiting for a network response inside FFmpeg. To solve this issue I have implemented the following solution:
Creation and the initial setup are only allowed on the background_thread (see code snippet "1" below).
Changes are allowed on the current_thread(Any).
Termination is allowed on the current_thread(Any).
After removing main thread checks and dispatch_asyncs to background threads, leaking has stopped and I can't reproduce the issue anymore:
// Code that produces the issue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1 - Create and do initial setup.
// This block creates the issue.
[self.rtspObject = [[RTSPObject alloc] initWithURL: ... ];
[self.rtspObject openInputStreamWithVideoStreamId: ...
audioStreamId: ...
useFirst: ...
inInit: ...];
});
I still don't understand why Xcode's memory debugger says that this block is retained?
Any advice or idea is welcome.
If you use av_format_open_input to open a file, you must use avformat_close_input to free it. Using free_context will leak all io related allocations.

Const char * as block variable

Some relevant info I am on OSX using GCD in Objective-C. I have a background task that produces a very large const char * this is then re-introduced into a new background task. This cycle repeats essentially until the const char* is empty. Currently I am creating and using NSStrings in the blocks and then going back to char* immediately. As you can imagine this does a ton of unnecessary copying of all that.
I am wondering how __block variables work for non-objects or how I can get away from NSStrings?
Or
How is memory managed for non-object types?
It is currently just blowing up with ~2 gigs of memory all from the strings.
Here is how it currently looks:
-(void)doSomething:(NSString*)input{
__block NSString* blockCopy = input;
void (^blockTask)(void);
blockTask = ^{
const char* input = [blockCopy UTF8String];
//remainder will point to somewhere along input
const char* remainder = NULL;
myCoolCFunc(input,&remainder);
if(remainder != NULL && remainder[0] != '\0'){
//this is whats killing me the NSString creation of remainder
[self doSomething:#(remainder)];
}
}
/*...create background queue if needed */
dispatch_async(backgroundQueue,blockTask);
}
There is no need to use NSString at all and no need to use the __block attribute:
-(void)doSomething:(const char *)input{
void (^blockTask)(void);
blockTask = ^{
const char* remainder = NULL;
myCoolCFunc(input,&remainder);
if(remainder != NULL && remainder[0] != '\0'){
[self doSomething:remainder];
}
}
/*...create background queue if needed */
dispatch_async(backgroundQueue,blockTask);
}
There is also little need to use recursion either, as an iterative approach is also possible.
blockTask = ^{
const char* remainder = NULL;
while (YES) {
myCoolCFunc(input,&remainder);
if(remainder == NULL || remainder[0] == '\0')
break;
input = remainder;
}
}

How to know when a block has been completed

Can someone help,
I have a NSMutablearray which contains approx a 1000 data entries.
What i want to do is go though these entries one by one and send them to a server with HTTP request.
However its important that i only send them one at a time, so i have the following pseudo code
for(int i =0; i < array.count; i++)
{
[ServerLayer ServerUploadRow:[array objectAtIndex:i] : ^void (int ReturnValue, BOOL err){
if(err == true)
{
//Do some local stuff here.
}
}];
}
what i ideally want to do is having something like the following
for(int i =0; i < array.count; i++)
{
//Wait here till i know that we not waiting on a block to complete
[ServerLayer ServerUploadRow:[array objectAtIndex:i] : ^void (int ReturnValue, BOOL err){
if(err == true)
{
//Do some local stuff here.
//Signify that we done
}
}];
}
the function ServerUploadRow just sends HTTP command.
Thanks
Assuming that ServerUploadRow runs asynchronously, you can have the request initiate the next request in the completion block of the previous one:
- (void)initiateRequestNumber:(NSInteger)index
{
[ServerLayer ServerUploadRow:array[index] : ^(int ReturnValue, BOOL err){
if (err == true) {
//Do some local stuff here.
} else {
NSInteger nextIndex = index + 1;
if (nextIndex < array.count) {
[self initiateRequestNumber:nextIndex];
} else {
// do whatever you want now that everything is done
}
}
}];
}
And you'd start this with:
[self initiateRequestNumber:0];
If you can issue these requests concurrently, or better combine all of this in one request, it will be much faster. I'd suggest considering refactoring your web service to enable this, if at all possible. But submitting 1000 network requests sequentially is going to horribly slow (as you'll suffer network latency 1000 times).

In Cocoa app, how to force an event check to abort a calculation

I'm writing a Cocoa app that does sometimes long calculations. I'd like to be able to abort those calculations with a cmd-key sequence. I start the calculations from a command line that is implemented in a subclass of NSTextView. I've over ridden keyDown and get events there, but only when the calculation finishes. I can't figure out how to have the long calculation force an event check periodically that would cause keyDown to be called so I can set a flag to abort the calculation. Seems like there may be an easy way, but I can't seem to find it.
Without worrying about background threads, I was hoping for something like this:
//commandView is a subclass of NSTextView
commandView* theCommands;
extern int continueCalc;
#implementation commandView
- (void)keyDown:(NSEvent *)anEvent{
static int first = 1;
if(first){
theCommands = self;
first = 0;
}
NSString *theKey = [anEvent characters];
[super keyDown:anEvent];
if([theKey isEqualToString:#"s"]){
NSLog(#"start 10 second calc");
continueCalc = 1;
doCalc(10);
} else if ([theKey isEqualToString:#"x"]){
NSLog(#"stop calc");
continueCalc = 0;
}
}
- (void)checkEvent{
NSLog(#"would like to force an event check so that typing x would stop the calc");
}
#end
// and the separate calculation code in another file:
int continueCalc = 1;
extern commandView* theCommands;
void doCalc(int n){
clock_t start;
for (int i=0; i<n && continueCalc; i++) {
start = clock();
while ( (clock()- start)*60/CLOCKS_PER_SEC < 60); //wait a second
// something here to check an event
[theCommands checkEvent];
}
}
One way to do this properly is with NSThread. You can very easily create a new thread which just calls your calculation method. In your calculation method, you check a variable to see if it's been aborted. Meanwhile on the main thread, the UI remains responsive and you can use an event (a key press or button press) to set the variable which is checked on the other thread. I might look something like this:
- (void)startCalculations
{
[NSThread detachNewThreadSelector:#selector (runCalculations)
toTarget:myObject
withObject:nil]; // Or you can send an object if you need to
}
Then in the code for myObject:
- (void)runCalculations
{
for (int i = 0; (i < maxWhatever) && (!stopCalculations); i++)
{
... do one iteration of your calculation
}
}
Then when your UI code gets the proper key or button press, you simply tell myObject to set stopCalculations to YES.
[myObject setStopCalculations:YES];
I should note that there are other ways to do this, like using GCD or pthreads directly, but this is a very simple way to just run a single method on another thread, and all the details of starting and tearing down the thread are taken care of for you.
The answer above works perfectly and showed me how to approach threads, which I hadn't used before. Thanks for that. With that working, I wanted the calculation to be able to report progress back to the command decoder (NSTextView). Doing that in the new thread caused problems, so I looked into using GCD. The implementation below seems to work, but this is my first GCD code and I would welcome any suggestions or caveats.
// CommandView is a subclass of NSTextView
commandView* theCommands;
extern int continueCalc;
#implementation commandView
- (void)keyDown:(NSEvent *)anEvent{
static int first = 1;
if(first){
theCommands = self;
}
NSString *theKey = [anEvent characters];
if([theKey isEqualToString:#"s"]){
[self appendText:#"Start\n"];
[self startCalculations];
return;
} else if ([theKey isEqualToString:#"x"]){
[self appendText:#"\nStop"];
continueCalc = 0;
return;
}
[super keyDown:anEvent];
}
- (void)startCalculations
{
void doCalc(int);
continueCalc = 1;
dispatch_queue_t queue = dispatch_queue_create("oma.oma2.CommandTask",0);
dispatch_async(queue,^{
for (int i=0; i<10 && continueCalc; i++) {
doCalc(0);
NSLog(#"%d",i);
}
});
}
-(void) appendText:(NSString *) string{
[self.textStorage.mutableString appendString:string];
}
#end
// the separate calculation code in a different file
#import "commandView.h"
extern commandView* theCommands;
int continueCalc = 1;
void doCalc(int n){
clock_t start;
start = clock();
while ( (clock()- start)*60/CLOCKS_PER_SEC < 60); //wait a second
// print progress in the main thread
dispatch_sync(dispatch_get_main_queue(),^{[theCommands appendText:#"."];});
}

Can an objective-C NSThread access global variables?

Ok, basically I have a run loop going in my application every second or two, while at the same time I have another thread going that is looping through the listenForPackets method; broadcastMessage is only initiated when another action method takes place. The important part of this question is that when the listener thread is running seperately from the main thread, it never print out any of the print commands and it seems to not allow access to a global variable that I have defined called recvMessage which lies outside of the interface and implementation sections.
In my code, I have it set up so that every time it runs through the main run loop, it updates a UILabel in my GUI. When the app is running, my label stays blank the whole time and never changes. I've double-checked the GUI and everything is linked up there correctly and my label is instantiated correctly too (I use the name "label" as an instance of UILabel in the code below). Does anyone have any ideas why my label is updating? The networking aspect of things is fine I believe, because I just got that done and everything is "talking" ok. Maybe it's a variable scope issue that I don't know about, or are separate threads allowed to access global variables, such as the one I have used below (rcvMessage)? I'm fairly new to multi-threaded applications but I don't believe it's really that hard to implement, using NSThread (only one line of code).
Global Variable
NSString *recvMessage;
Main Runloop - the section that updates the label every time it goes through the runloop
if (label.text != recvMessage)
label.text = recvMessage
Talker Method
-(void)broadcastMessage { // (NSString*)msg {
msg = #"From_Master";
NSLog(#"broadcastMessage - Stage 1");
mc_ttl = 15; // number of node hops the message is allowed to travel across the network
// define the port we will be using
mc_port = MYPORT;
// create a socket for sending to the multicast address
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: broadcastMessage - socket() failed");
return;
}
memset(&mc_addr, 0, sizeof(mc_addr));
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr(GROUP_ADDRESS);
mc_addr.sin_port = htons(MYPORT);
NSLog(#"broadcastMessage - Stage 2");
// set the TTL (time to live/hop count) for the send
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &mc_ttl, sizeof(mc_ttl))) < 0) {
NSLog(#"ERROR: broadcastMessage - setsockopt() failed");
return;
}
NSLog(#"broadcastMessage - Stage 3");
// clear send buffer
memset(send_str, 0, sizeof(send_str));
// convert the message to a C string to send
[msg getCString:send_str maxLength:MAX_LEN encoding:NSASCIIStringEncoding];
//while (fgets(send_str, MAX_LEN, stdin)) {
NSLog(#"broadcastMessage - Stage 4");
NSLog(#"Message =");
printf(send_str);
// send string to multicast address
if ((sendto(sock, send_str, sizeof(send_str), 0, (struct sockaddr *)&mc_addr, sizeof(mc_addr))) < 0) {
NSLog(#"ERROR: broadcastMessage - sendto() sent incorrect number of bytes");
//return;
}
NSLog(#"Sent Message -");
printf(send_str);
NSLog(#"broadcastMessage - Stage 5");
// clear send buffer
memset(send_str, 0, sizeof(send_str));
NSLog(#"broadcastMessage - Stage 6 - Complete");
close(sock);
}
Listener Method
-(void)listenForPackets {
listeningFlag_on = 1; // allows reuse of the same socket
NSLog(#"listenForPackets - Stage 1");
if ((listeningSock = socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: listenForPackets - socket() failed");
return;
// make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 2");
// set reuse port to on to allow multiple binds per host
if ((setsockopt(listeningSock, SOL_SOCKET, SO_REUSEADDR, &listeningFlag_on, sizeof(listeningFlag_on))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() Reuse failed");
return;
// make the method return an int instead of void and use this statement to check for errors
}
// construct a multicast address structure after erasing anything in the listeningmc_addr structure
memset(&listeningmc_addr, 0, sizeof(listeningmc_addr));
listeningmc_addr.sin_family = AF_INET;
listeningmc_addr.sin_addr.s_addr = htonl(INADDR_ANY); // different from sender
listeningmc_addr.sin_port = htons(MYPORT);
// bind multicast address to socket
if ((bind(listeningSock, (struct sockaddr *)&listeningmc_addr, sizeof(listeningmc_addr))) < 0) {
NSLog(#"ERROR: listenForPackets - bind() failed");
perror("Bind() -");
return; // make the method return an int instead of void and use this statement to check for errors
}
//*********************************************************************************
NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];
const char *tmp = [ipAddress UTF8String];
listeningMc_addr_str = tmp;
printf("%s\n", listeningMc_addr_str);
listeningMc_req.imr_multiaddr.s_addr = inet_addr(GROUP_ADDRESS);
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
// send an ADD MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
int err = errno;
NSLog(#"errno - %i", err);
NSLog(#"Error = %s", strerror(err));
perror("ERROR");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 3");
for (;;) { // loop forever
// clear the receive buffers & structs
memset(listeningRecv_str, 0, sizeof(listeningRecv_str));
listeningFrom_len = sizeof(listeningFrom_addr);
memset(&listeningFrom_addr, 0, listeningFrom_len);
NSLog(#"Test #1 Complete");
//msgStatus.text = #"Waiting...";
// block waiting to receive a packet
listeningFrom_len = sizeof(listeningmc_addr);
if ((listeningRecv_len = recvfrom(listeningSock, listeningRecv_str, MAX_LEN, 0, (struct sockaddr*)&listeningmc_addr, &listeningFrom_len)) < 0) {
NSLog(#"ERROR: listenForPackets - recvfrom() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"Test #2 Complete - Received a Message =");
NSLog(#"listenForPackets - Stage 4");
// listeningRecv_str
**tmpString = [[NSString alloc] initWithCString:listeningRecv_str encoding:NSASCIIStringEncoding];
NSLog(#"Message Received =");
NSLog(tmpString);
recvMessage = tmpString;**
//}
// received string
printf("Received %d bytes from %s: ", listeningRecv_len, inet_ntoa(listeningFrom_addr.sin_addr));
printf("%s", listeningRecv_str);
//}
}
// send a DROP MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() drop membership failed");
//return 1; // make the method return an int instead of void and use this statement to check for errors
}
close(listeningSock);
NSLog(#"listenForPackets - Stage 5 - Complete");
}
Yes, all threads can access global variables. There are certainly problems with how you are using the global--you leak the NSString you create every time the variable is updated, and you are accessing the same memory from two threads without any access control--but there's nothing that would prevent the variable from being updated.
If none of your log messages are being printed, the problem is that the code is never being run, which is why the variable isn't changing. You should take a look at the code that is supposed to kick off this new thread.
Also note that updating any UI components, you need to use the method "performSelectorOnMainThread" to do any value setting of label text or any other GUI elements. The value will not update from background threads.