using notificationcenter with performselector - macos

I want to get notifications on non-main thread from notificationcenter.
is there any way I can use performselector onThread when adding observer to NotificationCenter?

You have to set up a NSOperationQueue using the dispatch_queue_t you want to process notifications on. Here's an example of registering for current locale changed notification:
- (instancetype)init
{
self = [super init];
if (self)
{
//You need to set this variable to the queue you want the blocks to run on if not on default background queue
dispatch_queue_t queueToPostTo = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//Properties being used
//#property (nonatomic, strong) NSObject * localeChangeObserver;
//#property (nonatomic, strong) NSOperationQueue * localChangeObserverQueue;
self.localChangeObserverQueue = [[NSOperationQueue alloc] init];
[self.localChangeObserverQueue setUnderlyingQueue:queueToPostTo];
NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
self.localeChangeObserver = [notificationCenter addObserverForName:NSCurrentLocaleDidChangeNotification
object:nil
queue:self.localChangeObserverQueue
usingBlock:^(NSNotification *note) {
//Your code here for processing notification.
}];
}
return self;
}
- (void)dealloc
{
NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self.localeChangeObserver];
}

Related

How to observe ABPersonView changes for ABPerson

I know that ABPersonView is not KVO complaint. My issue is that despite declared property of ABPersonView being retained every time I access the property I get different object. Am I doing something wrong or is this correct that every time there was a change in ABPersonView I have to update model with new ABPerson object? Using El Capitan GM.
ABPersonView:
#property (readwrite, retain) ABPerson *person;
// An ABPerson record for display.
// Raises if person originates from ABAddressBook's +sharedAddressBook.
// Person must be exist in an ABAddressBook created and manipulated on the main thread only.
// When person is nil, displays an empty selection state.
Code:
#import "AppDelegate.h"
#import AddressBook;
static void * ABPersonVCContext = &ABPersonVCContext;
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) ABPerson *person;
#property (strong) ABPersonView *personView;
#property (strong) ABAddressBook *book;
#property (assign, getter=isEditing) BOOL editing;
#property NSTimer *timer;
#end
#implementation AppDelegate
- (instancetype)init {
self = [super init];
if (self) {
_book = [[ABAddressBook alloc] init];
NSString *vCardRepresentation = #"BEGIN:VCARD\r\nVERSION:3.0\r\nN:AA;BB;;;\r\nFN:\r\nEND:VCARD\r\n";
NSData *vCardData = [vCardRepresentation dataUsingEncoding:NSUTF8StringEncoding];
_person = [[ABPerson alloc] initWithVCardRepresentation:vCardData];
[_book addRecord:_person];
[self addObserver:self forKeyPath:#"editing"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:ABPersonVCContext];
#ifdef DEBUG
NSLog(#"%s %d %s", __FILE__, __LINE__, __PRETTY_FUNCTION__);
NSLog(#"%#",_person);
#endif
}
return self;
}
- (void)awakeFromNib
{
self.personView = [[ABPersonView alloc] initWithFrame:self.window.contentView.frame];
self.personView.person = self.person;
[self.window.contentView addSubview:self.personView];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(reverseEditing) userInfo:NULL repeats:YES];
[self.timer fire];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == ABPersonVCContext) {
if ([keyPath isEqualTo:#"editing"]) {
#ifdef DEBUG
NSLog(#"%s %d %s", __FILE__, __LINE__, __PRETTY_FUNCTION__);
NSLog(#"%#",self.personView.person);
#endif
}
} else {
#try {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
#catch (NSException *exception) {
;
}
#finally {
;
}
}
}
- (void)reverseEditing
{
self.editing = !self.editing;
}
#end
EDIT:
The new object comes from different addressBook instance:
(lldb) po [newPerson addressBook]
<ABAddressBook: 0x6080000d50e0>
(lldb) po self.book
<ABAddressBook: 0x6080000c4130>
(lldb) po [self.person addressBook]
<ABAddressBook: 0x6080000c4130>
EDIT2:
Even registering for notifications does not help because different object is being modified.
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(changeOccured:) name:kABDatabaseChangedNotification object:nil];
[nc addObserver:self selector:#selector(changeOccured:) name:kABDatabaseChangedExternallyNotification object:nil];
Unfortunately every call to person property of personView triggers ABPersonViewAPIAdapter that converts CNContact to ABPerson. So if one doesn't want to use CNContact on El Capitan he has to propagate edited ABPerson back to the model object.
One can try following code (hope this will save some time to someone)
NSLog(#"%#",[self.personView performSelector:#selector(addressBook) withObject:nil]);
NSLog(#"%#",[self.personView performSelector:#selector(_APIAdapter) withObject:nil]);
NSLog(#"%#",[self.personView performSelector:#selector(_contact) withObject:nil]);

NSManagedObjectContext save notification on background thread

I have NSManagedObjectContext in background thread. When context in main thread save context on background thread don't know about it.
I try to use NSManagedObjectContextDidSaveNotification on main context but then i can't run marge methode in background thread. bellow is my code.
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
if (![[currentThread threadDictionary] objectForKey:#"managedObjectContext"]) {
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
// Configure Managed Object Context
[managedObjectContext setPersistentStoreCoordinator:_mOCMainSetting.persistentStoreCoordinator];
[[currentThread threadDictionary] setObject:managedObjectContext forKey:#"managedObjectContext"];
if (!_contextArray) {
_contextArray = [NSArray array];
}
NSMutableArray *mutableContextArray = [_contextArray mutableCopy];
[mutableContextArray addObject:managedObjectContext];
_contextArray = mutableContextArray;
}
NSManagedObjectContext *context = [[currentThread threadDictionary] objectForKey:#"managedObjectContext"];
options = (Options*)[context objectWithID:_optionsID];
return options;
}
- (void)managedObjectContextDidSave:(NSNotification *)notification {
// dispatch_async(dispatch_get_main_queue(), ^{
for (NSManagedObjectContext *context in _contextArray) {
[context mergeChangesFromContextDidSaveNotification:notification];
}
// });
}
can you ask me how to run merge method in correct thread.

How can I run a NSTask in the background and show the results in a modal NSWindow while running

I would like to execute a command with NSTask, and be able to see the progress in a modal window. For example if I execute 'ls -R /' i would like to see the chunks appearing in a NSTextView.
I came up with the following, and everything works fine, except the update part. The task get executed (with the spinning beachbal) and when it is finished i see the result appear in the textview.
#interface ICA_RunWindowController ()
#property (strong) IBOutlet NSTextView* textResult;
#property (strong) IBOutlet NSButton* buttonAbort;
#property (strong) IBOutlet NSButton* buttonOK;
- (IBAction) doOK:(id) sender;
- (IBAction) doAbort:(id) sender;
#end
#implementation ICA_RunWindowController {
NSTask * executionTask;
id taskObserver;
NSFileHandle * errorFile;
id errorObserver;
NSFileHandle * outputFile;
id outputObserver;
}
#synthesize textResult,buttonAbort,buttonOK;
- (IBAction)doOK:(id)sender {
[[self window] close];
[NSApp stopModal];
}
- (IBAction)doAbort:(id)sender {
[executionTask terminate];
}
- (void) taskCompleted {
NSLog(#"Task completed");
[[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
[[NSNotificationCenter defaultCenter] removeObserver:errorObserver];
[[NSNotificationCenter defaultCenter] removeObserver:outputObserver];
[self outputAvailable];
[self errorAvailable];
executionTask = nil;
[buttonAbort setEnabled:NO];
[buttonOK setEnabled:YES];
}
- (void) appendText:(NSString *) text inColor:(NSColor *) textColor {
NSDictionary * makeUp = [NSDictionary dictionaryWithObject:textColor forKey:NSForegroundColorAttributeName];
NSAttributedString * extraText = [[NSAttributedString alloc] initWithString:text attributes:makeUp];
[textResult setEditable:YES];
[textResult setSelectedRange:NSMakeRange([[textResult textStorage] length], 0)];
[textResult insertText:extraText];
[textResult setEditable:NO];
[textResult display];
}
- (void) outputAvailable {
NSData * someData = [outputFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"output Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor blackColor]];
}
}
- (void) errorAvailable {
NSData * someData = [errorFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"Error Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor redColor]];
}
}
- (void) runCommand:(NSString *) command {
// make sure all views are initialized
[self showWindow:[self window]];
// some convience vars
NSArray * runLoopModes = #[NSDefaultRunLoopMode, NSRunLoopCommonModes];
NSNotificationCenter * defCenter = [NSNotificationCenter defaultCenter];
// create an task
executionTask = [[NSTask alloc] init];
// fill the parameters for the task
[executionTask setLaunchPath:#"/bin/sh"];
[executionTask setArguments:#[#"-c",command]];
// create an observer for Termination
taskObserver = [defCenter addObserverForName:NSTaskDidTerminateNotification
object:executionTask
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self taskCompleted];
}
];
// Create a pipe and a filehandle for reading errors
NSPipe * error = [[NSPipe alloc] init];
[executionTask setStandardError:error];
errorFile = [error fileHandleForReading];
errorObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:errorFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self errorAvailable];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// Create a pipe and a filehandle for reading output
NSPipe * output = [[NSPipe alloc] init];
[executionTask setStandardOutput:output];
outputFile = [output fileHandleForReading];
outputObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:outputFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self outputAvailable];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// start task
[executionTask launch];
// show our window as modal
[NSApp runModalForWindow:[self window]];
}
My question: Is it possible to update the output while the task is running? And, if yes, how could I achieve that?
A modal window runs the run loop in NSModalPanelRunLoopMode, so you need to add that to your runLoopModes.
You should not be getting the spinning beach ball. The cause is that you're calling -readDataToEndOfFile in your -outputAvailable and -errorAvailable methods. Given that you're using -waitForDataInBackgroundAndNotifyForModes:, you would use the -availableData method to get what data is available without blocking.
Alternatively, you could use -readInBackgroundAndNotifyForModes:, monitor the NSFileHandleReadCompletionNotification notification, and, in your handler, obtain the data from the notification object using [[note userInfo] objectForKey:NSFileHandleNotificationDataItem]. In other words, let NSFileHandle do the work of reading the data for you.
Either way, though, once you get the end-of-file indicator (an empty NSData), you should not re-issue the ...InBackgroundAndNotifyForModes: call. If you do, you'll busy-spin as it keeps feeding you the same end-of-file indicator over and over.
It shouldn't be necessary to manually -display your text view. Once you fix the blocking calls that were causing the spinning color wheel cursor, that will also allow the normal window updating to happen automatically.

Why do NSFilePresenter protocol methods never get called?

I am trying to monitor file changes in local and iCloud directories and have implemented the NSFilePresenter protocol methods but the only method that gets called is presentedItemAtURL.
Am I correct in assuming that I should be able to monitor a local or an iCloud directory and get notified any time any process adds, modifies or deletes a file in the directory.
Here is the basic code for the OS X App:
- (void)awakeFromNib {
_presentedItemURL = myDocumentsDirectoryURL;
_presentedItemOperationQueue = [[NSOperationQueue alloc] init];
[_presentedItemOperationQueue setMaxConcurrentOperationCount: 1];
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
}
- (NSURL*) presentedItemURL {
FLOG(#" called %#", _presentedItemURL);
return _presentedItemURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
FLOG(#" called");
return _presentedItemOperationQueue;
}
- (void)presentedItemDidChange {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
-(void)accommodatePresentedItemDeletionWithCompletionHandler:(void (^)(NSError *errorOrNil))completionHandler
{ FLOG(#" called");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self reloadData];
}];
completionHandler(nil);
}
-(void)presentedSubitemDidChangeAtURL:(NSURL *)url {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
-(void)presentedSubitemDidAppearAtURL:(NSURL *)url {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
Long time ago, I know, but perhaps this will still help. NSFilePresenter will only notify you about changes made by another process that makes changes to a directory or file USING AN NSFileCoordinator. If another process (eg: iTunes file sharing) makes changes without an NSFileCoordinator, you won't be notified.
This is in no way my final implementation and I will edit/update as I improve. But since there is nil examples on how to do this, i figured i'd share something that works!!! That's right, it works. I am able to read the file in my app, and at the same time make a change in textedit and the changes propagate to my app. Hope this helps bud.
PBDocument.h
#interface PBDocument : NSObject <NSFilePresenter>
#property (nonatomic, strong) NSTextView *textView;
#pragma mark - NSFilePresenter properties
#property (readonly) NSURL *presentedItemURL;
#property (readonly) NSOperationQueue *presentedItemOperationQueue;
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView;
#end
PBDocument.m
#interface PBDocument ()
#property (readwrite) NSURL *presentedItemURL;
#property (readwrite) NSOperationQueue *presentedItemOperationQueue;
#property (readwrite) NSFileCoordinator *fileCoordinator;
#end
#implementation PBDocument
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView {
self = [super init];
if (self) {
_textView = textView;
_presentedItemURL = url;
_presentedItemOperationQueue = [NSOperationQueue mainQueue];
[NSFileCoordinator addFilePresenter:self];
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
[self readWithCoordination];
}
return self;
}
- (void)readWithCoordination {
NSError *error = nil;
[self.fileCoordinator coordinateReadingItemAtURL:_presentedItemURL options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *newURL) {
NSLog(#"Coordinating Read");
NSError *error = nil;
NSFileWrapper *wrapper = [[NSFileWrapper alloc] initWithURL:newURL options:0 error:&error];
if (!error) {
[self readFromFileWrapper:wrapper ofType:[self.presentedItemURL pathExtension] error:&error];
}
if (error) #throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:#"%#", error] userInfo:nil];
}];
if (error) #throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:#"%#", error] userInfo:nil];
}
- (void)presentedItemDidChange {
[self readWithCoordination];
}
#end
If it's any help to anyone this is the approach (FSEvents) I ended up using recently for a file sync solution and it seems to work for any file system. I have not done any research recently on NSFileCoordinator to see whether this is better worse or what the use cases are as a comparison.
I also did not test every use case so your mileage may vary.
https://github.com/eonil/FSEvents

Setting up NSMetadataQueryDidUpdateNotification for a simple response

I'm trying to get up and running with an NSMetadataQueryDidUpdateNotification on an OS X app, to alert me when a file in my iCloud ubiquity container is updated. I've been doing a lot of research (including reading other Stack answers like this, this, this, and this), but I still don't have it quite right, it seems.
I've got a "CloudDocument" object subclassed from NSDocument, which includes this code in the H:
#property (nonatomic, strong) NSMetadataQuery *alertQuery;
and this is the M file:
#synthesize alertQuery;
-(id)init {
self = [super init];
if (self) {
if (alertQuery) {
[alertQuery stopQuery];
} else {
alertQuery = [[NSMetadataQuery alloc]init];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
NSLog(#"Notification created");
[alertQuery startQuery];
}
return self;
}
-(void)queryDidUpdate:(NSNotification *)notification {
NSLog(#"Something changed!!!");
}
According to my best understanding, that should stop a pre-existing query if one is running, set up a notification for changes to the ubiquity container, and then start the query so it will monitor changes from here on out.
Except, clearly that's not the case because I get Notification created in the log on launch but never Something changed!!! when I change the iCloud document.
Can anyone tell me what I'm missing? And if you're extra-super-sauce awesome, you'll help me out with some code samples and/or tutorials?
Edit: If it matters/helps, there is only one file in my ubiquity container being synced around. It's called "notes", so I access it using the URL result from:
+(NSURL *)notesURL {
NSURL *url = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
return [url URLByAppendingPathComponent:kAllNotes];
}
where "kAllNotes" is set with #define kAllNotes #"notes".
EDIT #2: There have been a lot of updates to my code through my conversation with Daij-Djan, so here is my updated code:
#synthesize alertQuery;
-(id)init {
self = [super init];
if (self) {
alertQuery = [[NSMetadataQuery alloc] init];
if (alertQuery) {
[alertQuery setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSString *STEDocFilenameExtension = #"*";
NSString* filePattern = [NSString stringWithFormat:#"*.%#", STEDocFilenameExtension];
[alertQuery setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE %#", NSMetadataItemFSNameKey, filePattern]];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidFinishGatheringNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
NSLog(#"Notification created");
[alertQuery startQuery];
}
return self;
}
-(void)queryDidUpdate:(NSNotification *)notification {
[alertQuery disableUpdates];
NSLog(#"Something changed!!!");
[alertQuery enableUpdates];
}
How do you save your document - what url do you give it? Unless you give it an extension yourself, it won't automatically be given one - so your *.* pattern will never match a file that does not have an extension. Try * as the pattern and see what happens.
Also, it helps to log what is happening within queryDidUpdate, until you've worked out exactly what's going on :
Try something like:
-(void)queryDidUpdate:(NSNotification *)notification {
[alertQuery disableUpdates];
NSLog(#"Something changed!!!");
// Look at each element returned by the search
// - note it returns the entire list each time this method is called, NOT just the changes
int resultCount = [alertQuery resultCount];
for (int i = 0; i < resultCount; i++) {
NSMetadataItem *item = [alertQuery resultAtIndex:i];
[self logAllCloudStorageKeysForMetadataItem:item];
}
[alertQuery enableUpdates];
}
- (void)logAllCloudStorageKeysForMetadataItem:(NSMetadataItem *)item
{
NSNumber *isUbiquitous = [item valueForAttribute:NSMetadataItemIsUbiquitousKey];
NSNumber *hasUnresolvedConflicts = [item valueForAttribute:NSMetadataUbiquitousItemHasUnresolvedConflictsKey];
NSNumber *isDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey];
NSNumber *isDownloading = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadingKey];
NSNumber *isUploaded = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadedKey];
NSNumber *isUploading = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadingKey];
NSNumber *percentDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentDownloadedKey];
NSNumber *percentUploaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentUploadedKey];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
BOOL documentExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
NSLog(#"isUbiquitous:%# hasUnresolvedConflicts:%# isDownloaded:%# isDownloading:%# isUploaded:%# isUploading:%# %%downloaded:%# %%uploaded:%# documentExists:%i - %#", isUbiquitous, hasUnresolvedConflicts, isDownloaded, isDownloading, isUploaded, isUploading, percentDownloaded, percentUploaded, documentExists, url);
}
you never allocate your alertQuery....
somewhere you need to alloc,init a NSMetaDataQuery for it
example
NSMetadataQuery* aQuery = [[NSMetadataQuery alloc] init];
if (aQuery) {
// Search the Documents subdirectory only.
[aQuery setSearchScopes:[NSArray
arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
// Add a predicate for finding the documents.
NSString* filePattern = [NSString stringWithFormat:#"*"];
[aQuery setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE %#",
NSMetadataItemFSNameKey, filePattern]];
// Register for the metadata query notifications.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processFiles:)
name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processFiles:)
name:NSMetadataQueryDidUpdateNotification
object:nil];
// Start the query and let it run.
[aQuery startQuery];
}
processFiles method
== your queryDidUpdate
- processFiles(NSNotification*)note {
[aQuery disableUpdates];
.....
[aQuery enableUpdates];
}
NOTE: disable and reenable search there!
see:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloud101/SearchingforiCloudDocuments/SearchingforiCloudDocuments.html

Resources