Memory Leak Using IOKit (Adapted From USBPrivateDataSample) - objective-c

Well. I'm back with another memory leak question. I recently asked a similar question here, but it turns out that code doesn't entirely do what I am trying to accomplish (which is to perform an action whenever any USB device connects/disconnects, and not just when an HID devices does).
I've started over from scratch, and have adapted Apple's USBPrivateDataSample and some of GitHub project ORSSerialPort into something that seems to work just like I want it to -- except --there are memory leaks. :(
I've tried applying the knowledge gained from my previous posts about dealing with memory leaks, but I'm stuck again. What am I missing? Thanks very much in advance.
Here's the code:
**AppDelegate.h:**
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong) NSMutableDictionary *deviceDictionary;
#end
**AppDelegate.m:**
#import "AppDelegate.h"
#include <IOKit/usb/IOUSBLib.h>
typedef struct DeviceData
{
io_object_t notification;
CFStringRef deviceName;
CFStringRef serialNum;
} DeviceData;
static IONotificationPortRef gNotifyPort;
static io_iterator_t gAddedIter;
static CFRunLoopRef gRunLoop;
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.deviceDictionary = [[NSMutableDictionary alloc] init];
CFMutableDictionaryRef matchingDict;
CFRunLoopSourceRef runLoopSource;
kern_return_t kr;
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL)
{
fprintf(stderr, "IOServiceMatching returned NULL.\n");
}
// Create a notification port and add its run loop event source to our run loop
// This is how async notifications get set up.
gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
gRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);
// Now set up a notification to be called when a device is first matched by I/O Kit.
kr = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort
kIOFirstMatchNotification, // notificationType
matchingDict, // matching
DeviceAdded, // callback
NULL, // refCon
&gAddedIter // notification
);
// Iterate once to get already-present devices and arm the notification
DeviceAdded(NULL, gAddedIter);
// Start the run loop. Now we'll receive notifications.
CFRunLoopRun();
// can't do these, causes EXC_BAD_ACCESS crash
//CFRelease(matchingDict);
//CFRelease(gNotifyPort);
//CFRelease(kr);
CFRelease(runLoopSource);
CFRelease(gRunLoop);
}
void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
{
kern_return_t kr;
DeviceData *deviceDataRef = (DeviceData *) refCon;
if (messageType == kIOMessageServiceIsTerminated)
{
kr = IOObjectRelease(deviceDataRef->notification);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
if ((deviceDataRef->serialNum) && (deviceDataRef->deviceName))
{
// remove device from dict
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
NO];
}
free(deviceDataRef);
}
}
void DeviceAdded(void *refCon, io_iterator_t iterator)
{
kern_return_t kr;
io_service_t usbDevice;
while ((usbDevice = IOIteratorNext(iterator)))
{
io_name_t deviceName;
CFStringRef deviceNameCF;
DeviceData *deviceDataRef = NULL;
// Add some app-specific information about this device.
// Create a buffer to hold the data.
deviceDataRef = malloc(sizeof(DeviceData));
bzero(deviceDataRef, sizeof(DeviceData));
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
if (KERN_SUCCESS != kr)
{
deviceName[0] = '\0';
}
CFMutableDictionaryRef usbProperties = 0;
if (IORegistryEntryCreateCFProperties(usbDevice, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
{
IOObjectRelease(usbDevice);
continue;
}
NSDictionary *properties = CFBridgingRelease(usbProperties);
NSString *serialNum = properties[(__bridge NSString *)CFSTR("USB Serial Number")];
NSNumber *builtIn = properties[(__bridge NSString *)CFSTR("Built-In")];
deviceNameCF = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII);
if (builtIn.integerValue != 1)
{
// Save the device's name to our private data.
deviceDataRef->deviceName = deviceNameCF;
deviceDataRef->serialNum = (__bridge CFStringRef)(serialNum);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
YES];
}
// Register for an interest notification of this device being removed. Use a reference to our
// private data as the refCon which will be passed to the notification callback.
kr = IOServiceAddInterestNotification(gNotifyPort, // notifyPort
usbDevice, // service
kIOGeneralInterest, // interestType
DeviceNotification, // callback
deviceDataRef, // refCon
&(deviceDataRef->notification) // notification
);
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
- (void) updateDeviceDict : (NSString *) deviceSerial : (NSString *) deviceName : (bool) keepDevice
{
if (deviceName)
{
if ((!deviceSerial) || ([deviceSerial isEqualToString:#""])) deviceSerial = deviceName;
#try
{
//#throw [[NSException alloc] initWithName:#"test" reason:#"test" userInfo:nil];
// if the device needs to be added
if ((keepDevice) && (deviceSerial))
{
if (![self.deviceDictionary objectForKey:deviceSerial])
{
[self.deviceDictionary setObject:[NSDictionary dictionaryWithObjectsAndKeys:deviceName, #"name", nil] forKey:deviceSerial];
NSLog(#"Device added: %#", deviceName);
}
}
else // if the device needs to be removed
{
[self.deviceDictionary removeObjectForKey:deviceSerial];
NSLog(#"Device removed: %#", deviceName);
}
}
#catch (NSException *exception)
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
#finally
{
NSLog(#"\n%#", self.deviceDictionary);
}
}
else // name is nil
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
}

Related

How to share saved games across devices?

I implemented GameKit into my iOS game including the saved game feature.
Here an example how I save and load a game:
MobSvcSavedGameData.h
#ifndef MOBSVC_SAVEDGAMEDATA_H
#define MOBSVC_SAVEDGAMEDATA_H
#import <Foundation/Foundation.h>
#interface MobSvcSavedGameData : NSObject <NSCoding>
#property (readwrite, retain) NSString *data;
+(instancetype)sharedGameData;
-(void)reset;
#end
#endif /* MOBSVC_SAVEDGAMEDATA_H */
MobSvcSavedGameData.m
#import "MobSvcSavedGameData.h"
#import <Foundation/Foundation.h>
#interface MobSvcSavedGameData () <NSObject, NSCoding>
#end
#implementation MobSvcSavedGameData
#pragma mark MobSvcSavedGameData implementation
static NSString * const sgDataKey = #"data";
+ (instancetype)sharedGameData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)reset
{
self.data = nil;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.data forKey: sgDataKey];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)decoder {
self = [self init];
if (self) {
self.data = [decoder decodeObjectForKey:sgDataKey];
}
return self;
}
#end
For simplicity my saved game object above has only a NSString which will be serialised and uploaded like so:
void MobSvc::uploadSavedGameDataAwait(const char *name, const char *data)
{
GKLocalPlayer *mobSvcAccount = [GKLocalPlayer localPlayer];
if(mobSvcAccount.isAuthenticated)
{
MobSvcSavedGameData *savedGameData = [[MobSvcSavedGameData alloc] init];
savedGameData.data = [NSString stringWithUTF8String:data];
[mobSvcAccount saveGameData:[NSKeyedArchiver archivedDataWithRootObject:savedGameData] withName:[[NSString alloc] initWithUTF8String:name] completionHandler:^(GKSavedGame * _Nullable savedGame __unused, NSError * _Nullable error) {
if(error == nil)
{
NSLog(#"Successfully uploaded saved game data");
}
else
{
NSLog(#"Failed to upload saved game data: %#", error.description);
}
}];
}
}
And this is how I download the most recent saved game on the next play session again:
void MobSvc::downloadSavedGameDataAwait(const char *name)
{
GKLocalPlayer *mobSvcAccount = [GKLocalPlayer localPlayer];
if(mobSvcAccount.isAuthenticated)
{
[mobSvcAccount fetchSavedGamesWithCompletionHandler:^(NSArray<GKSavedGame *> * _Nullable savedGames, NSError * _Nullable error) {
if(error == nil)
{
GKSavedGame *savedGameToLoad = nil;
for(GKSavedGame *savedGame in savedGames) {
const char *sname = savedGame.name.UTF8String;
if(std::strcmp(sname, name) == 0)
{
if (savedGameToLoad == nil || savedGameToLoad.modificationDate < savedGame.modificationDate) {
savedGameToLoad = savedGame;
}
}
}
if(savedGameToLoad != nil) {
[savedGameToLoad loadDataWithCompletionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
if(error == nil)
{
MobSvcSavedGameData *savedGameData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(#"Successfully downloaded saved game data: %#", [savedGameData.data cStringUsingEncoding:NSUTF8StringEncoding]);
}
else
{
NSLog(#"Failed to download saved game data: %#", error.description);
}
}];
}
}
else
{
NSLog(#"Failed to prepare saved game data: %#", error.description);
}
}];
}
}
I tested this by uploading a random string and receiving it on the next session by using the same name. It works! However, as soon as I try to download the saved game from my second iPhone it does not work. On both phones I'm logged into the same Game-Center account, I could confirm this by comparing the playerId in the GKLocalPlayer instance.
I've set up the proper iCloud container and linked my game to it, but the logs in the iCloud container backend remain empty.
What is going on? How can I share the saved game across Apple devices?
The above sample in the question works just fine. It's mandatory that the user logs into iCloud and uses the same apple ID on the Game Center login, because the saved games will be stored in the iCloud.
Unfortunately, I was testing the whole without iCloud, so it couldn't work.

Getting 'unrecognised selector' from NSNotificationCenter

I'm trying to implement an observer in the NSNotificationCenter. Instead of using self as the observer I want to create a little object that does it:
typedef void (^ErrorCallback)(NSError*);
typedef void (^SuccessCallback)();
typedef void (^ReplicationChanged) (NSNotification*);
#interface SyncParams : NSObject
#property (copy) ErrorCallback errorCallback;
#property (copy) SuccessCallback successCallback;
#property (copy) ReplicationChanged replicationChanged;//this used to observe
- (void)replicationChanged:(NSNotification*)notification;
#end
#implementation SyncParams
#end
Then later I create an instance of the observer:
SyncParams* params = [SyncParams alloc];
params.replicationChanged = ^(NSNotification* notification) {
//do stuff here
};
And finally add it to the NSNotificationCenter:
[[NSNotificationCenter defaultCenter] addObserver: params
selector: #selector(replicationChanged:)
name: kCBLReplicationChangeNotification
object: replicationObject];
But I get this error: Exception '-[SyncParams replicationChanged:]: unrecognized selector sent to instance 0x7ff945c049a0' was thrown
I'm very new to objective-c! Any pointers?
Yes and no, I ended up with the following:
[[NSNotificationCenter defaultCenter] addObserverForName:kCBLReplicationChangeNotification object:repl queue:nil usingBlock:^(NSNotification *notification) {
NSString *status;
if (repl.status == kCBLReplicationActive) {
NSLog(#"Repication in progress");
status = #"in-progrss";
} else if (repl.status == kCBLReplicationOffline) {
NSLog(#"Sync in offline");
status = #"offline";
} else if (repl.status == kCBLReplicationStopped) {
NSLog(#"Sync in stopped");
status = #"stopped";
} else if (repl.status == kCBLReplicationIdle) {
NSLog(#"Sync in idle");
status = #"idle";
}
NSError *error = repl.lastError;
if(error) {
status = #"error";
NSLog(#"replication error %#", error.code);
}
NSDictionary *dictionary = #{
#"type": type,
#"changesCount": #(repl.changesCount),
#"completedChangesCount": #(repl.completedChangesCount),
#"running": #(repl.running),
#"status": status,
#"suspended": #(repl.suspended),
};
[self sendEventWithName:#"replicationChanged" body:dictionary];
}];
I only ever get one callback though, when it starts up.

Using NSProgress with nested NSOperations

I've been investigating NSProgress but have found the existing documentation, class reference and tutorials to be lacking. I'm mainly wondering if my NSProgress is applicable to my use case. The class reference documentation alternatively refers to suboperations or subtasks, I may be mistaken but I interpreted suboperations to mean a case where an NSOperation manages a group of other NSOperations. An example of my use case is as follows:
Create an Upload All Items in Group operation for each group that exists.
Add each of these operations to an NSOperationQueue.
Each Upload All Items in Group operation will create an Upload Item operation for each item in their group. These all get added to an NSOperationQueue managed by the operation.
I would have expected NSProgress to support this, and allow me to propagate progress from the nested operations (Upload Item operation) to the parent operation, and then finally to the main thread and the UI. But I've had difficulty implementing this, it seems as though NSProgress is meant more for long operations that execute all their code on one background thread, but have separate "sections" that make it easy to determine when progress has been made, if this is the case then the use of the term suboperation is a bit misleading as it brings to mind the use of nested NSOperations.
Thank you for any help you can provide, and let me know if additional details are needed.
NSProgress knows nothing about NSOperations -- the two things are orthogonal -- but that doesn't mean it can't be used with them. The idea behind nesting NSProgress "tasks" is that the inner task doesn't know anything about the outer task, and the outer task doesn't need direct access to the inner task's NSProgress to pull in updates for it. I cooked up a little example:
// Outer grouping
NSProgress* DownloadGroupsOfFiles(NSUInteger numGroups, NSUInteger filesPerGroup)
{
// This is the top level NSProgress object
NSProgress* p = [NSProgress progressWithTotalUnitCount: numGroups];
for (NSUInteger i = 0; i < numGroups; ++i)
{
// Whatever DownloadFiles does, it's worth "1 unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
DownloadFiles(filesPerGroup);
[p resignCurrent];
}
return p;
}
// Inner grouping
void DownloadFiles(NSUInteger numberOfFiles)
{
NSProgress* p = [NSProgress progressWithTotalUnitCount: numberOfFiles];
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
// Make the op queue last as long as the NSProgress
objc_setAssociatedObject(p, NULL, opQueue, OBJC_ASSOCIATION_RETAIN);
// For each file...
for (NSUInteger i = 0; i < numberOfFiles; ++i)
{
// Whatever this DownloadOperation does is worth 1 "unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
// Make the new operation
MyDownloadOperation* op = [[MyDownloadOperation alloc] initWithName: [NSString stringWithFormat: #"File #%#", #(i+1)]];
[opQueue addOperation: op];
[p resignCurrent];
}
}
// And then the DownloadOperation might look like this...
#interface MyDownloadOperation : NSOperation
#property (nonatomic, readonly, copy) NSString* name;
- (id)initWithName: (NSString*)name;
#end
#implementation MyDownloadOperation
{
NSProgress* _progress;
NSString* _name;
}
- (id)initWithName:(NSString *)name
{
if (self = [super init])
{
_name = [name copy];
// Do this in init, so that our NSProgress instance is parented to the current one in the thread that created the operation
_progress = [NSProgress progressWithTotalUnitCount: 1];
}
return self;
}
- (void)dealloc
{
_name = nil;
_progress = nil;
}
- (void)main
{
// Fake like we're doing something that takes some time
// Determine fake size -- call it 768K +- 256K
const NSUInteger size = 512 * 1024 + arc4random_uniform(512*1024);
const NSUInteger avgBytesPerSec = 1024 * 1024;
const NSTimeInterval updatePeriod = 1.0/60.0;
// Make sure all the updates to the NSProgress happen on the main thread
// in case someone is bound to it.
dispatch_async(dispatch_get_main_queue(), ^{
_progress.totalUnitCount = size;
_progress.completedUnitCount = 0;
});
NSUInteger bytesRxd = 0;
do
{
// Sleep for a bit...
usleep(USEC_PER_SEC * updatePeriod);
// "Receive some data"
NSUInteger rxdThisTime = updatePeriod * avgBytesPerSec;
// Never report more than all the bytes
bytesRxd = MIN(bytesRxd + rxdThisTime, size);
// Update on the main thread...
dispatch_async(dispatch_get_main_queue(), ^{
[_progress setCompletedUnitCount: bytesRxd];
});
} while (bytesRxd < size);
}
#end
One thing to note is that if NSProgress is being used to convey status to the UI, then you will want to make sure that every time you update the NSProgress object, you do so from the main thread, otherwise you'll get lots of weird crashes.
Alternately you could just use NSURLConnection to download files, and then have a delegate like this:
#interface MyURLConnectionProgressReporter : NSObject <NSURLConnectionDownloadDelegate>
#property (nonatomic, readwrite, assign) id<NSURLConnectionDownloadDelegate> delegate;
#end
NSProgress* DownloadABunchOfFiles(NSArray* arrayOfURLs)
{
arrayOfURLs = arrayOfURLs.count ? arrayOfURLs : #[ [NSURL URLWithString: #"http://www.google.com"] ];
NSProgress* p = [NSProgress progressWithTotalUnitCount: arrayOfURLs.count];
for (NSURL* url in arrayOfURLs)
{
[p becomeCurrentWithPendingUnitCount: 1];
MyURLConnectionProgressReporter* delegate = [[MyURLConnectionProgressReporter alloc] init];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: delegate];
[conn start];
[p resignCurrent];
}
return p;
}
#implementation MyURLConnectionProgressReporter
{
NSProgress* _progress;
}
static void EnsureMainThread(dispatch_block_t block);
- (id)init
{
if (self = [super init])
{
_progress = [NSProgress progressWithTotalUnitCount: 1];
EnsureMainThread(^{
_progress.kind = NSProgressKindFile;
[_progress setUserInfoObject:NSProgressFileOperationKindDownloading forKey:NSProgressFileOperationKindKey];
});
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
id retVal = [super forwardingTargetForSelector:aSelector];
if (!retVal && [self.delegate respondsToSelector: _cmd])
{
retVal = self.delegate;
}
return retVal;
}
- (void)p_updateWithTotalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress on the main thread...
EnsureMainThread(^{
if (!expectedTotalBytes)
_progress.totalUnitCount = -1;
else
_progress.totalUnitCount = MAX(_progress.totalUnitCount, expectedTotalBytes);
_progress.completedUnitCount = totalBytesWritten;
});
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connection:connection didWriteData:bytesWritten totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidResumeDownloading:connection totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL
{
// We're done, so we want (_progress.completedUnitCount == _progress.totalUnitCount)
EnsureMainThread(^{
_progress.completedUnitCount = _progress.totalUnitCount;
});
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidFinishDownloading:connection destinationURL:destinationURL];
}
}
static void EnsureMainThread(dispatch_block_t block)
{
if (!block)
return;
else if ([NSThread isMainThread])
block();
else
dispatch_async(dispatch_get_main_queue(), block);
}
#end
Hope that helps.

Monitor a specific folder for changes and detect which files changed in Cocoa

I'm trying to make an app that simply monitors a specific folder for changes and outputs the path of the file(s) changed. It will later do some processing of those changed files. How would I go about doing this in native cocoa? I have tried somethings listed at:
http://developer.apple.com/library/mac/#featuredarticles/FileSystemEvents/_index.html
but I can't figure out how to effectively accomplish the tasks.
Code samples would be much appreciated.
Have a look at fs-notifier by Peter Hosey
#interface Notifier : NSObject {
NSArray *paths; //Actually just one.
FSEventStreamRef stream;
struct FSEventStreamContext context;
}
+ (id) notifierWithCallback:(FSEventStreamCallback)newCallback path:(NSString *)newPath;
- (id) initWithCallback:(FSEventStreamCallback)newCallback path:(NSString *)newPath;
- (void) start;
- (void) stop;
#end
#import "Notifier.h"
#implementation Notifier
+ (id) notifierWithCallback:(FSEventStreamCallback)newCallback path:(NSString *)newPath {
return [[[self alloc] initWithCallback:newCallback path:newPath] autorelease];
}
- (id) initWithCallback:(FSEventStreamCallback)newCallback path:(NSString *)newPath {
if((self = [super init])) {
paths = [[NSArray arrayWithObject:newPath] retain];
context.version = 0L;
context.info = newPath;
context.retain = (CFAllocatorRetainCallBack)CFRetain;
context.release = (CFAllocatorReleaseCallBack)CFRelease;
context.copyDescription = (CFAllocatorCopyDescriptionCallBack)CFCopyDescription;
stream = FSEventStreamCreate(kCFAllocatorDefault, newCallback, &context, (CFArrayRef)paths, kFSEventStreamEventIdSinceNow, /*latency*/ 1.0, kFSEventStreamCreateFlagUseCFTypes);
if (!stream) {
NSLog(#"Could not create event stream for path %#", newPath);
[self release];
return nil;
}
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
return self;
}
- (void) dealloc {
[self stop];
FSEventStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(stream);
[super dealloc];
}
- (void) start {
FSEventStreamStart(stream);
}
- (void) stop {
FSEventStreamStop(stream);
}
#end
#import "Notifier.h"
static void gotEvent(ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]);
int main (int argc, char **argv) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = [[NSProcessInfo processInfo] arguments];
NSMutableArray *streams = [NSMutableArray arrayWithCapacity:[paths count]];
for (NSString *path in paths) {
[streams addObject:[Notifier notifierWithCallback:gotEvent path:path]];
}
[streams makeObjectsPerformSelector:#selector(start)];
CFRunLoopRun();
[pool drain];
return EXIT_SUCCESS;
}
static void gotEvent(ConstFSEventStreamRef stream,
void *clientCallBackInfo,
size_t numEvents,
void *eventPathsVoidPointer,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]
) {
NSArray *eventPaths = eventPathsVoidPointer;
NSString *streamName = clientCallBackInfo;
NSLog(#"%#: %#", streamName, [eventPaths objectAtIndex:0UL]);
}
The Example is the basic way of doing what you want.
Take a look at Using the FSEvents Framework documenation. It tells you all you need to get up and running. The Code samples I would give here are the same ones as listed in the documentation.

Memory Management with NSStatusItem : setToolTip

I have a Singleton class with static NSStatusItem and a NSStream client. Whenever I receive a message in Stream I pass it to another thread to change NSStatusItem toolTip.
case NSStreamEventHasBytesAvailable:
{
if(stream == inputStream)
{
//InputStream ready
uint8_t buf[1024];
unsigned int len = 0;
len = [inputStream read:buf maxLength:1024];
if(len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buf length:len];
NSString *msgRcvd = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"Message Recieved in StreamUtil ::: %#",msgRcvd);
[self ProcessMessage:msgRcvd];
[data release];
[msgRcvd release];
}
}
break;
}
#implementation SBNSStatusItem
static SBNSStatusItem *sbNSStatusItem = NULL;
+(SBNSStatusItem *) GetSBNSStatusItem
{
#synchronized(self)
{
if (sbNSStatusItem== NULL)
{
sbNSStatusItem= [[self alloc] init];
}
}
return(sbNSStatusItem);
}
-(void) CreateNSStatusItem
{
// Initalization of NSStatusItem with NSMenu and Image setting
}
-(void) SetToolTip:(NSString *) toolTip
{
NSLog(#"%#",toolTip); // Shows Correct Message
[statusItem setToolTip:toolTip]; // But the ToolTip has some junk at the end of ori msg
//[toolTip release]; // EXC_BAD_ACCESS
}
#end
Now the Threadfrom StreanUtil calls the setToolTip with the message to be set. But after 2 or 3 calls, the tooltip starts showing some junk text at the end of the original tool tip message.
What am I doing wrong here?
Since the toolTip item you pass into SetToolTip: is just a pointer its probably getting set or released somewhere else.
try something like this
- (void)SetToolTip:(NSString *) toolTip
{
NSString *toolTipCopy = [toolTip copy];
[statusItem setToolTip:toolTipCopy];
[toolTipCopy release];
}
Also if your targeting iOS 4 or OS X 10.6 or later I would consider moving to ARC.
Then the code would then become this.
- (void)SetToolTip:(NSString *) toolTip
{
[statusItem setToolTip:[toolTip copy]];
}