Is it possible to use NSMetadataQuery when iCloud is disabled? - iphone

I can't seem to get NSMetadataQuery to work when I disable iCloud. I put in a valid search URL, but it never registers as finished:
//Check for iCloud
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"iCloud access at %#", ubiq);
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDataScope]];
_isiCloudEnabled = YES;
} else {
NSLog(#"No iCloud access");
//Get the doc directory
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObjects:
[NSURL fileURLWithPath:path],nil]];
_isiCloudEnabled = NO;
}
NSPredicate *pred = [NSPredicate predicateWithFormat:
#"%K like %#", NSMetadataItemFSNameKey, #"*.adoc"];
[self.query setPredicate:pred];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:self.query];
[self.query startQuery];
queryDidFinishGathering: never gets called. When iCloud is enabled, it always gets called. Any idea why?

I was facing the same problem but I am using the ARC in project. This is solved by setting the ivar to self.query variable.
#property (nonatomic, strong) NSMetadataQuery *query;
I think for your problem as you are not using the ARC, you may need to do following things:
remove the autorelease and manually release when finish the work.
you need to set the property
#property (nonatomic, retain) NSMetadataQuery *query;

As of iOS5, NSMetadataQuery 's search scope can only be set to ubiquitous things (NSMetadataQueryUbiquitousDocumentsScope and NSMetadataQueryUbiquitousDataScope) so using it with iCloud disabled would be unuseful.
As you are probably guessing the reason queryDidFinishGathering is never called is because of your query scope, local directories are not supported yet (but suspiciously not throwing exceptions or errors :) )
In my opinion NSMetadataQuery class is not fully ported to iOS, in OSX more scopes can be set , more kinds of NSPredicate can be set, NSSortDescriptors work, etc.

Related

How to force iCloud with core data to do sync?

I'm using iCloud with core data to sync data of my App.
I tried everything and finally It works perfectly.
but I have one question.
Is there any way that saying to iCloud to sync?
It sync when app begin, but the other time. It seems like that it sync randomly. I can't find the way to handle it myself.
any help?
Thanks.
I figured out how to force a core data store to sync to iCloud. You just need to "touch" any receipt files in the Data folder in the Ubiquity filesystem. The code below will do this. To force a sync, call the syncCloud method. For every "receipt" file it finds, it will update the files modification time stamp to the current time, essentially doing a "touch" on the file. As soon as this happens, iCloud will check and sync any core data files that need synchronization in iCloud. Near as I can tell, this is how the simulator or Xcode does it when you select the "Trigger iCloud Sync" option.
#property (strong, nonatomic) NSMetadataQuery *query;
-(void) init
{
self.query = [[NSMetadataQuery alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(didFinishMetadataQuery)
name: NSMetadataQueryDidFinishGatheringNotification
object: self.query];
}
-(void) didFinishMetadataQuery
{
// Query completed so mark it as completed
[self.query stopQuery];
// For each receipt, set the modification date to now to force an iCloud sync
for(NSMetadataItem *item in self.query.results)
{
[[NSFileManager defaultManager] setAttributes: #{NSFileModificationDate:[NSDate date]}
ofItemAtPath: [[item valueForAttribute: NSMetadataItemURLKey] path]
error: nil];
}
}
-(void) syncCloud
{
// Look for files in the ubiquity container data folders
[self.query setSearchScopes:#[NSMetadataQueryUbiquitousDataScope]];
// Look for any files named "receipt*"
[self.query setPredicate:[NSPredicate predicateWithFormat:#"%K like 'receipt*'", NSMetadataItemFSNameKey]];
// Start the query on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
BOOL startedQuery = [self.query startQuery];
if (!startedQuery)
{
NSLog(#"Failed to start query.\n");
return;
}
});
}
You have to force any NSURL item to be cached in iCloud with this method:
- (BOOL)addBackupAttributeToItemAtURL:(NSURL *)URL
{
NSLog(#"URL: %#", URL);
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: NO]
forKey: NSURLIsExcludedFromBackupKey error: &error];
return success;
}
You're need to launch this method on every directory and file you're downloading/creating in Documents or Library directory.
I don't believe that you have any control over when the sync will occur when using iCloud and core-data. With UIDocument and iCloud I have used startDownloadingUbiquitousItemAtURL with varying degrees of success but for core-data I don't think there is any equivalent -- I would love to hear someone tell me otherwise however...

NSMetadataQuery doesn't finish gathering (no notification)

I'm making a backup managrer for my App (via iCloud). I did some tests and the basics worked. But few days later it stopped. I'm using NSMetadataQuery for searching if backup file exists. My backup files are named e.g. Backup29112011154133.xml where numbers represent date of the backup (formatted as ddMMyyyyHHmmss). I check for it in -viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[self checkForRemoteFile];
}
- (void)checkForRemoteFile {
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K like 'Backup*'",NSMetadataItemFSNameKey];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud is unavailable at the moment" message:nil delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert setTag:TAG_ALERT_NOICLOUD];
[alert show];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notif {
NSMetadataQuery *query = [notif object];
[query disableUpdates];
[query stopQuery];
[self loadRemoteFile:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
}
- (void)loadRemoteFile:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
canRestore = YES;
NSMetadataItem *item = [query resultAtIndex:0];
// parse the backup file
[self.tableView reloadData];
} else {
canRestore = NO;
modifDate = #"never";
backupInfoLoaded = YES;
[self.tableView reloadData];
}
}
The problem is that - (void)queryDidFinishGathering:(NSNotification *)notif is never executed. I put breakpints and NSLogs ion there but nothing happend.
I also tried to check for other notifications e.g. 'query did start gathering' and 'query process'. Only 'query did start' notification is posted.
I also have AppID with iCloud registered and entitlements file attached.
Can you help me out what's going on? Maybe I missed something?
First of all NSMetadataQuery doesn't works if startQuery was called not from the MaintThread.
There is possibility that predicate fails for every path also.
Following code works for me.
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
...
query.predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey];
[query startQuery];
FIXED by creating ivar for NSMetadataQuery.
I don't know why the application can't read data without NSMetadataquery ivar.
Unfortunately there have been many problems with iCloud and using NSMetaDataQuery. To be honest with you the best source as of now for all your iCloud related questions is the Apple Developer Forums. Today Apple released iOS 5.1 beta, and the release notes STILL say that NSMetaDataQuery isn't functioning properly. It's extremely frustrating that iCloud still isn't working properly, but sadly there's nothing we can do.
This problem still persists. I have been able to trace it to the following divergence:
If you limit your search predicate on the query to the name key,
for example
[NSPredicate predicateWithFormat:#"%K like[cd] %#", NSMetadataItemFSNameKey, #"*"]
then it will work as expected (posting all four query lifecycle notifications).
If, however, you try either a compound predicate or try to work with the path,
as in
[NSPredicate predicateWithFormat:#"%K BEGINSWITH %#", NSMetadataItemPathKey, [self cloudDocumentsURL].path]
OR
[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:namePred, pathPred, nil]];
Then only the initial notification will be posted.
I have tried literally hundreds of combinations of these configurable variables in multiple test and intended-for-production apps over the last year and have yet to find a counterexample to this hypothesis.
Unfortunately, NSMetadataQuery just doesn't work for ubiquitous stores (as of 10.8).
My workaround is to get the raw results from the query and work mostly on a bound NSArrayController which can have its results filtered. This will mean refactoring away from query.results for most existing code and there is a performance hit (presumably) but it is the only way I have found. I would love an alternative.

How to save a NSMutableArray (containing other arrays) to file

This has been asked before and people have given very good instructions on how to do this, e.g. here.
However, I was wondering if I really need to work with NSCoder if I simply wanted to save one NSMutableArray (containing various instances of another NSMutableArray) to a file? I tried this but only got an error message:
-(void)saveLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
NSError *error;
[myLibrary writeToFile:filePath atomically:YES];
if (error) {
NSLog(#"There was an error saving myLibrary.dat: %#", error);
}
}
My error message:
2011-05-13 22:00:47.840 MoleNotes[15437:207] There was an error saving myLibrary.dat: (
1,
2
)
So I guess I have to work with NSCoder, right? If so, I was wondering how to go about this. People have explained how to do this with a class, but in my case, I have a NSMutableArray (myLibrary) which contains various instances of a class. Will I have to implement the NSCoder in this class and the NSMutableArray?
I alloc my library like this:
myLibrary = [[NSMutableArray alloc] init];
And then add instances of a class called NoteBook.m like this:
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes"; // etc.
[myLibrary addObject:newNoteBook];
So where exactly do I put the NSCoder commands? Only into my NoteBook.m class? Will this automatically take care of myLibrary?
Thanks for any suggestions.
EDIT:
So I've updated my code, but I guess the big problem is that my NSMutableArray myLibrary contains several instances of a custom class I've set up (called notebook). I have set up NSCoding for this class (and all its variables) so that I can save it and load it.
Now my app works totally fine if I create the NSMutableArray in the app (i.e. when the app is started for the very first time, no file exists), instead of loading it from disk:
-(void) setupLibrary {
myLibrary = [[NSMutableArray alloc] init];
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes";
/...
If I load it from disk, it works fine as well:
-(void)loadLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
myLibrary = [[NSMutableArray alloc] init];
myLibrary = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (!myLibrary) {
// if it couldn't be loaded from disk create a new one
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
}
If I log everything which is contained in my library after loading it, everything is still fine. E.g. the following works totally fine:
NSLog(#"%#", [[self.myLibrary objectAtIndex:0] titleName]);
The big problem is, however, if any other method tries to access myLibrary. For instance, if I call the very same log command from another method, the app will crash and I get this error message:
[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510
2011-05-14 14:09:10.490 Notes[17091:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510'
This sounds to me as if myLibrary has become deallocated somehow, but I can't see why. How could this have happened? I have the feeling that I did something wrong in my NSCoding set up... because if I simply create myLibrary in code, everything works like wonderfully. It's only if I load it from the disk, that the app will crash.
Here is the class setup:
#import <Foundation/Foundation.h>
#interface NoteBook : NSObject <NSCoding> {
NSString *titleName;
NSString *fileName;
NSMutableArray *tabTitles;
NSMutableArray *tabColours;
NSMutableArray *tabReference;
}
#property (nonatomic, retain) NSString *titleName;
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSMutableArray *tabTitles;
#property (nonatomic, retain) NSMutableArray *tabColours;
#property (nonatomic, retain) NSMutableArray *tabReference;
-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
#end
//
// NoteBook.m
#import "NoteBook.h"
#implementation NoteBook
#synthesize titleName, fileName, tabTitles, tabColours, tabReference;
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.titleName = [aDecoder decodeObjectForKey:#"titleName"];
self.fileName = [aDecoder decodeObjectForKey:#"fileName"];
self.tabTitles = [aDecoder decodeObjectForKey:#"tabTitles"];
self.tabColours = [aDecoder decodeObjectForKey:#"tabColours"];
self.tabReference = [aDecoder decodeObjectForKey:#"tabReference"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:titleName forKey:#"titleName"];
[aCoder encodeObject:fileName forKey:#"fileName"];
[aCoder encodeObject:tabTitles forKey:#"tabTitles"];
[aCoder encodeObject:tabColours forKey:#"tabColours"];
[aCoder encodeObject:tabReference forKey:#"tabReference"];
}
#end
EDIT:
I think I've solved it... I forgot a little 'self'... which messed it all up and deallocated myLibrary:
self.myLibrary = [NSKeyedUnarchiver
unarchiveObjectWithFile:filePath];
if (self.myLibrary == nil) {
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
Your code is busted. The "error" variable is uninitialized and never set, so when you check it, you're just seeing random garbage data. If you want to know whether the write was successful, check the return value of writeToFile:atomically:. It will be YES if the write succeeded and NO if it didn't.
However, NSArray's writeTo… methods are for creating plists. If non-property-list objects are in your array, that method isn't appropriate, and an archiver is what you want. Just do something like [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePath atomically:YES].
To make your objects conform to NSCoding correctly, just have them implement initWithCoder: and encodeWithCoder:, and in those methods, use NSCoder's storage methods to store the object's instance variables (and the retrieval methods to get them back out).
NSCoder is a protocol that your class must conform to in order to be archived to data/file. Works something like Serealizabe in Java.
Add conformance to the class header like this:
#interface NoteBook : NSObject <NSCoder> { // …
And then you must implement two methods:
-(id)initWithCoder:(NSCoder)decoder;
{
self = [super initWithCoder:decoder];
if (self) {
_someIvar = [decoder decodeObjectForKey:#"someKey"];
// And more init as needed…
}
return self;
}
-(void)encodeWithCoder:(NSCoder)coder;
{
[super encodeWithCoder:coder];
[coder encodeObject:_someIvar forKey#"someKey"];
/// Etc…
}
I would also advice against using -[NSArray writeToFile:atomically:] since in work with property list compliant objects only, not coding compliant classes. The property list object are NSString, NSData, NSArray, or NSDictionary, NSDate, and NSNumber. The list can not be extended.
Instead use NSKeyedArchiver/NSKeyedUnarchiver. Almost as simple to use:
if (![NSKeyedArchive archiveRootObject:yourArrat toFile:path]) {
// It failed.
}

Memory Leak the second time the view loads

i been cracking my head over this memory leak..
my datasource is mutabledictionary..that i load in the viewdidload. if i dont retain it. i dont have access it it in cellforrowatindexpath. but when i retain it.. it shows up as a memory leak in instruments. i have tried so many different variations.. doesnt seem to get it right.
here is the code the leak is in "dict" and "plistPath"
`
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBarHidden = NO;
self.title = #"Messages & Lists";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
[plistPath release];
plistPath = [documentsDirectory stringByAppendingPathComponent:#"general.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
[dict release];
if ( [fileManager fileExistsAtPath:plistPath] ) {
dict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath] ;
} else {
dict = [NSMutableDictionary dictionaryWithCapacity:1];
[dict setObject:#"NO" forKey:#"busyStatus"];
[dict setObject:#"NO" forKey:#"replyToAll"];
[dict setObject:#"NO" forKey:#"replyToList"];
[dict setObject:#"NO" forKey:#"dontReplyToList"];
[dict writeToFile:plistPath atomically:YES];
}
[tableData release];
tableData = [[NSMutableDictionary alloc] init];
[tableData setObject:[NSArray arrayWithObjects:#"Help",#"Set Default Message",#"Reply To All",[dict objectForKey:#"replyToAll"],nil] forKey:#"1"];
[tableData setObject:[NSArray arrayWithObjects:#"Reply to a List",[dict objectForKey:#"replyToList"],#"List of Contacts",nil] forKey:#"2"];
[tableData setObject:[NSArray arrayWithObjects:#"Don't reply to List",[dict objectForKey:#"dontReplyToList"],#"List of Contacts",nil] forKey:#"3"];
[dict retain];
[plistPath retain];
}
`
there is no leak the first time the view loads. but if i got back. and then load the view again it leaks.
thanks in advance for anyone who can help me out.
You have to call [dict release] in your view controller's dealloc method.
I think you should release your dictionary in the dealloc method of your view controller so that the retain count of your datasource is decreased when the ViewController is deallocated.
Retaining without releasing after use is the main source of memory leak.
It seems right.
The only thing that confuses me is the tableData. Is that var released or allocated somewhere else too?
Say you do allocate it somewhere else, and not releases it in dealloc, shouldn't these be the steps then:
tableData is allocated somewhere else.
tableData is released in viewDidLoad
tableData is allocated again (counter 1).
Exiting view, entering view again, tableData allocated again, released and allocated in function (counter two?)
Seems like a longshot, but. Can you display what the instruments say?

Objective c, Memory Leak, reading from sqlite and assigning values to a NSDictionary and NSAarray

I have a list of shops in a ListController file.
I've setted up a sqlite db, in which i've stored 60 shops.
On the top of the list i have a search bar.
I've made a class called DataController, that is responsible to load and store db datas.
#interface DataController : NSObject {
sqlite3 *database;
NSArray *shops;
NSDictionary* dictionaryOfShops;
}
#property (nonatomic, retain) NSDictionary *dictionaryOfShops;
#property (nonatomic, retain) NSArray* shops;
-(void)initializeShops;
initializeShops method loads data from the db, and stores results into the 2 props in this way:
-(void)initializeShops{
[dictionaryOfShops release];
[shops release];
NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
if (sqlite3_open(....))
NSString *query = ....
if (sqlite3_prepare_v2(database, [query UTF8String],-1, &statement, nil) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW) {
int rId = sqlite3_column_int(statement, 0);
char *rName = (char *)sqlite3_column_text(statement, 1);
Shop* s = [[Shop alloc] init];
s.ID = rId;
if(sName != nil) s.Name = [NSString stringWithUTF8String:rName];
NSString *shopID = [[NSString alloc] initWithFormat:#"%d",s.ID];
[dictionary setObject:s forKey:shopID];
[shopID release];
[s release];
}
sqlite3_finalize(statement);
}
[query release];
dictionaryOfShops = [[NSDictionary alloc] initWithDictionary:dictionary];
shops = [[NSArray alloc] initWithArray:[dictionary allValues]];
dictionary = nil;
[dictionary release];
//Sorting
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
NSArray *sortedList =[self.shops sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
self.shops = sortedList;
[sort release];
}
The problem is that when user enters some text into the search
bar, I change the value of the query (adding LIKE....) and then call the initializeShops method again. This second time makes
so many leaks, (related to the Shop class properties) and
leaks also a NSDictionary and a NSArray.
Before posting this to you I've tried different solutions, but
at least this doesn't leaks anything the first time I call
initilizeShops.
I accept any suggestion, since I'm really stuck
on it.
MORE:
The really strange thing is memory management of my var dictionary and the 2 props shops and dictionaryOfShops. With this code
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
//add data to dictionary
dictionaryOfShops = [[NSDictionary alloc] initWithDictionary:dictionary];
shops = [[NSArray alloc] initWithArray:[dictionary allValues]];
[dictionary release]
Considering that dictionaryOfShops and shops are two properties (nonatomic,retain) synthesized, how can I change value to them without leaks?
The very first time I pass through this method nothing gets leaked, from the second time it starts to leak so many objects (the contents of the collections).
The first question is Why not just use Core Data? It is very likely going to be faster, will require less code, and will be significantly easier to maintain over time. To be blunt; SQLite is deceptively hard. Easy to get started, exceptionally difficult to get right.
In any case, the memory management of dictionary is wrong. It only isn't crashing because you swapped the order of the nil assignment and release as kennyTM suggested. I would suggest not creating an autoreleased dictionary.
Otherwise, the code as written seems pretty leakless at first glance. So:
Can you provide some more code?
Anything interesting memory wise
going on elsewhere?
Are you using threading at all (or
NSOperationQueue)?
Have you run under the Leaks
instrument and retrieved the
backtraces of allocation of the
specific objects being leaked?
dictionary = nil;
[dictionary release];
Please swap these 2 statements. In this form it means [nil release] which is a no-op.
Ok, I've found the error.
In my class Shop, i realize i didn't implement the method
-(void)dealloc
So when I release the old dictionary (to prepare for a new assignment), all the fields inside of it didn't get released.