ITLibrary gives me nothing but (null) - objective-c

My program automates a radio station. There is lots of communication back and forth between it and iTunes. I programmed it with scripting bridge. Scripting bridge suffers from memory leaks. Each call to scripting bridge leaks a small amount of memory. Add a lot of calls to a program that runs 24/7 and I've got software that will run for something less than 24 hours, and then quit.
My first attempt at a solution was to minimize my calls to scripting bridge. In researching that end, I came across ItunesLibrary. It isn't working for me.
NSError *error = nil;
ITLibrary *library = [ITLibrary libraryWithAPIVersion:#"1.0" error:&error];
if (library)
{
NSArray *playlists = [[NSArray alloc]init];
playlists = library.allPlaylists;
NSArray *tracks = [[NSArray alloc]init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"mediaKind == %d", ITLibMediaItemMediaKindSong];
tracks = [library.allMediaItems filteredArrayUsingPredicate:predicate];
NSLog(#"Playlists - %#",playlists);
NSLog(#"Tracks - %#",tracks);
}
This code is pretty much right out of Apple's docs. It should work - I think.
Before I added the predicate, I got some info on each of the podcasts in my iTunes library. In the nslog output, each of my playlists produces an entry similar to "". Each of my songs shows nothing more than (null).
All of the info is in iTunes. I can read it with scripting bridge. I can read it with AVAsset
AVAsset *asset = [AVURLAsset URLAssetWithURL:myUrl options:nil];
NSArray *metadata = [asset commonMetadata];
for ( AVMetadataItem* item in metadata )
{
NSString *key = [item commonKey];
NSString *value = [item stringValue];
NSLog(#"key = %#, value = %#", key, value);
}
With AVAsset I only get the song name, album name, and artist name. I need to access the rest of iTune's ID3 tags.
What have I don to break ItunesLibrary?

The secret to getting ItunesLibrary to work seems to be in the entitlements file. You need to add the key "com.apple.security.assets.music.read-only", and set it to YES. I got this by digging through a project on github.com.

Related

Geocoding many addresses - CLGeocoder

I need to geocode a ton of addresses with CLGeocoder, not the Google API.
My problem is that my code is only adding an annotation for the first address in the array below. However, the function gets called an appropriate number of times - the block just runs for the first address after the function has been called for ALL of the addresses:
- (void)mapPoints {
NSString *foo = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"names%d", self.indexOfSchool] ofType:#"plist"];
NSArray *description = [NSArray arrayWithContentsOfFile:foo];
NSString *bar = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%d1", self.indexOfSchool] ofType:#"plist"];
NSArray *details = [NSArray arrayWithContentsOfFile:bar];
NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%d0", self.indexOfSchool] ofType:#"plist"];
NSArray *addresses = [NSArray arrayWithContentsOfFile:path];
self.saved = [NSMutableArray alloc] init];
for (int q = 0; q < addresses.count; q+= 20) {
[self annotate:[addresses objectAtIndex:q] detail:[details objectAtIndex:q] andDesc:[description objectAtIndex:q]];
}
}
see updated annotate: method below...
Here are how my logs appear:
here
here
here
here
.
..
...
....
<MKPointAnnotation: 0x16fc12e0>
Therefore, although the function is being called with the loop, the block is not running for any results other than the first.
I know my addresses are good. This does not work for any number (>1) of calls to the function.
The += 20 in the loop has nothing to do with only one point being plotted: this does not work for ++ either (there are also around 200 results).
I'm fairly new to blocks, so hopefully this is an easy fix. Any help is greatly appreciated!
EDIT
Doing this instead:
- (void)annotate:(NSString *)address detail:(NSString *)detail andDesc:(NSString *)description {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address
completionHandler:^(NSArray* placemarks, NSError* error) {
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = placemark.coordinate;
point.title = description;
point.subtitle = [NSString stringWithFormat:#"%# miles from origin", detail];
MKCoordinateRegion region = self.directionsView.region;
region.center = [(CLCircularRegion *)placemark.region center];
region.span.longitudeDelta /= 8.0;
region.span.latitudeDelta /= 8.0;
[self.directionsView addAnnotation:point];
NSLog(#"%#", point);
[self.directionsView setRegion:region animated:YES];
}
}
];
[self.saved addObject:geocoder];
NSLog(#"%#", geocoder);
}
... still pulls up a blank map with no annotations -- not even the first. The geocoder array has a different memory address upon each call to the function, so that piece appears to be working.
You are calling the geocodeAddressString a few times in a row, but apple's docs say
After initiating a forward-geocoding request, do not attempt to
initiate another forward- or reverse-geocoding request.
So I think what is happening is either the current request is cancelled when you make a new one, or all new requests are ignored if one is already in progress.
As a workaround you could geocode one point, and then geocode the next point in the completion block or create multiple instances of the geocoder
Each CLGeocoder object can only process one request at a time. Try creating a new CLGeocoder object inside your annotate method instead of reusing a single object stored in a property. As long as you have ARC enabled, you should be okay.
Note that Apple may place restrictions on the number of simultaneous requests you send to their servers, so you may need to limit the number of outstanding requests if you're doing a large number of them.
You can have only one geocoding request running at a time regardless of how many instances of CLGeocoder you have. You will have to start another request from withing the completion block of the previous one.
Also, take into account, that geocoding too many addresses in a short time will reach its internal limit. In my experiments, after ~50 requests (one after another) the framework refused to process new requests for next ~60 seconds.
This is a Xamarin pseudo-code but it should work the same in native as I had to convert to C# in the first place:
forloop()
{
var geocoder = new CLGeocoder();
var placemarks = await geocoder.GeocodeAddressAsync(address);
}

iCloud NSMetadata query results are blank

I've been working on adding icloud to my project (which is quite a pain in the buns) and I'm able to save and remove files, but I can't get a list of the files stored in iCloud. I've tried solutions from about 10 different websites (including the Apple documentation). Whenever I call [self.query startQuery]; everything seems to be working: The correct methods get called, the methods execute exactly as they should. Then when I ask for an nsarray of the files in my app's iCloud Documents directory I get two parenthesis with nothing between them (when I view in NSLog): File List: ( ). I know for a fact that there are many different documents of all shapes, extensions, sizes, and names in my app's iCloud Documents directory because I've been using the iCloud Developer site to check if things are working. My first method to setup the query is as follows:
- (void)syncWithCloud {
self.query = [[NSMetadataQuery alloc] init];
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
[query setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope, nil]];
//[query setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE '*'", NSMetadataItemFSNameKey]];
[query setPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey]];
//Pull a list of all the Documents in The Cloud
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidUpdateNotification object:self.query];
[self.query startQuery];
}
The process files method provided by Apple is next:
- (void)processFiles:(NSNotification*)aNotification {
NSMutableArray *discoveredFiles = [NSMutableArray array];
//Always disable updates while processing results.
[query disableUpdates];
//The query reports all files found, every time.
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
NSURL *fileURL = [result valueForAttribute:NSMetadataItemURLKey];
NSNumber *aBool = nil;
// Don't include hidden files.
[fileURL getResourceValue:&aBool forKey:NSURLIsHiddenKey error:nil];
if (aBool && ![aBool boolValue])
[discoveredFiles addObject:fileURL];
}
//Update the list of documents.
[FileList removeAllObjects];
[FileList addObjectsFromArray:discoveredFiles];
//[self.tableView reloadData];
//Reenable query updates.
[query enableUpdates];
NSLog(#"File List: %#", FileList);
}
Why doesn't this give me a list of files or at least some kind of data? Am I defining the NSMetadata query wrong, maybe my predicate isn't formatted right? I know I'm doing something wrong because there's no way iCloud could be this complicated (or could it?).
Thanks for the help in advance!
Edit #1: I am continuing to try different approaches to this problem. Using one of the answers below I have changed the predicate filter as follows:
[query setPredicate:[NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey LIKE '*'"]];
I have also added the following lines before the [query enableUpdates] call:
for (NSMetadataItem *item in query.results) {
[FileList addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
In the processFiles method, I've tried placing all of the code on the background thread, but this makes no difference - as a matter of fact, when the code is not executed on the background thread FileList gives me this (null) instead of this ( ).
Could my problem have to do with thread management or memory allocation? Please note that I am using ARC.
Edit #2: The FileList variable is an NSMutableArray defined in my #interface and initialized in the -(id)init method before calling the processFiles method.
Edit #3: When testing my code with breakpoints I found that the following for-loop never gets run through - not even once. This leads me to believe that:
A. The proper directory isn't being connected with
B. iCloud can't see the files in the directory
C. My NSMetadataQuery isn't being setup properly
D. Something completely different
Here's the code that starts the for-loop which never gets run:
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
Since you already set the search scope correct, there's no need to use special filters in the predicate.
Just use:
query.predicate = [NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey == '*'"];
And to get the array use:
NSMutableArray *array = [NSMutableArray array];
for (NSMetadataItem *item in query.results) {
[array addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
I've solved my problem. Getting the list of files in iCloud was just a matter of correctly defining, allocating, and initializing properties. SAE's answer and this SO posting helped me solve my problem and create this GitHub Repo called iCloud Document Sync. The iCloud Document Sync class simplifies the whole iCloud Document storage process down to a few lines of code. The commit linked here fixes the issues from my question.

Not getting data from Core Data

I am using Core Data to store some information for my app.
I have a .xcdatamodeld file containing 8 entities, and I extract them on different views.
In one of the viewControllers, I call three of them. Like this:
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication]delegate];
managedObjectContext = appDelegate.managedObjectContext;
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entiAll = [NSEntityDescription entityForName:#"AllWeapons" inManagedObjectContext:moc];
NSFetchRequest *frAll = [[NSFetchRequest alloc] init];
[frAll setEntity:entiAll];
NSError *error = nil;
arrAll = [moc executeFetchRequest:frAll error:&error];
displayArray = [[NSMutableArray alloc]initWithArray:arrAll];
NSEntityDescription *entiRange = [NSEntityDescription entityForName:#"WeaponsRanged" inManagedObjectContext:moc];
NSFetchRequest *frRanged = [[NSFetchRequest alloc] init];
[frRanged setEntity:entiRange];
NSError *errorRanged = nil;
arrRange = [moc executeFetchRequest:frRanged error:&errorRanged];
NSLog(#"%i, %i", [arrRange count], [[moc executeFetchRequest:frRanged error:&errorRanged] count]);
NSEntityDescription *entiMelee = [NSEntityDescription entityForName:#"WeaponsMelee" inManagedObjectContext:moc];
NSFetchRequest *frMelee = [[NSFetchRequest alloc] init];
[frMelee setEntity:entiMelee];
NSError *errorMelee = nil;
arrMelee = [moc executeFetchRequest:frMelee error:&errorMelee];
NSLog(#"%i, %i", [arrMelee count], [[moc executeFetchRequest:frMelee error:&errorMelee] count]);
The problem is that the middle one (the one filling the arrRange-array) doesn't work..
arrAll logs out with all correct data, arrMelee logs out with all the correct data (x4 for some reason, don't know if this is related :S), but arrRange logs out as an empty array.
[arrRange count]; gives me 0, even though I know there is lots of data there.
I ran this code on the simulator, and found the .sqlite file, opened it in Firefox's SQLite Manager, and saw the correct data, 40 rows.
I went into the appDelegate, where I fill the CoreData when necessary, and saw that the method which downloads the data in JSON-format successfully sends it to the sqlite aswell.
Here I fill the CoreData with data from the json:
[self deleteAllObjects:#"WeaponsRanged"];
NSManagedObjectContext *context = [self managedObjectContext];
for(NSDictionary *item in jsonWeaponRanged)
{
WeaponsRanged *wr = [NSEntityDescription insertNewObjectForEntityForName:#"WeaponsRanged"
inManagedObjectContext:context];
///***///
wr.recoil = [item objectForKey:#"Recoil"];
///***///
NSError *error;
if(![context save:&error])
NSLog(#"%#", [error localizedDescription]);
}
And if I here do NSLog(#"%# - %#", wr.recoil, [item objectForKey:#"Recoil"]); I get the correct data. (Same data on both)
So. The correct data is obviously in the core. But my NSFetchRequest or something is failing. I am pretty noob at Objective-C, so it might be my bad code-grammar striking again. I realize I should use things again etc, not creating new objects all the time.. But cmon, this is my first app.. And if that is actually the problem, I might learn. But I'm stuck.
SOMETIMES I get data, sometimes I don't. It's weird. I re-launched the app, and got data from it, and now I don't.. I haven't found a pattern yet..
Anyone?
Or is there another way to request data from the entity?
I have some suggestions, too big for a comment.
1) after you create the WeaponsRanged, try reading them back:
for(NSDictionary *item in jsonWeaponRanged)
{
WeaponsRanged *wr = [NSEntityDescription insertNewObjectForEntityForName:#"WeaponsRanged"
inManagedObjectContext:context];
NSLog(#"IS WR Realized? %#", wr ? #"YES" : #"NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO WR");
///***///
wr.recoil = [item objectForKey:#"Recoil"];
///***///
NSError *error;
if(![context save:&error])
NSLog(#"%#", [error localizedDescription]);
}
// Now lets see if we can retrieve them:
{
NSEntityDescription *entiRange = [NSEntityDescription entityForName:#"WeaponsRanged" inManagedObjectContext:context];
NSFetchRequest *frRanged = [[NSFetchRequest alloc] init];
[frRanged setEntity:entiRange];
NSError *errorRanged = nil;
arrRange = [context executeFetchRequest:frRanged error:&errorRanged];
NSLog(#"Wrote %i items, read back %i items", [jsonWeaponRanged count], [arrRange count] );
}
2) In the viewController reading WeaponsRanged, add an assert before the fetch on mod:
NSLog(#"IS moc set? %#", moc ? #"YES" : #"NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MOC");
EDIT:
3) Spread statements everywhere you access the MOC:
assert([NSThread isMainThread]);
[If you haven't used asserts before google and read up on the topic. These are a powerful tool for developers to find out about potential problems way before they manifest themselves in the gui or elsewhere. They are normally compiled out for release/distribution builds.]
This will force an exception if the thread is not the main thread, and then let you track down the reason by following the stack trace.
Nevermind! It was my own damn fault (yet again..).
The problem occured before the code I presented, and it turns out the data was never in the .sqlite-file when the problem was present.
This is what I had:
I collected data from the internet through json-request. I have told the app to check the "version" of the data through the internet, and if the data is outdated, then re-download it.
First, I download all data, then I add them to their own entity in Core Data. After downloading, I clear the current Core Data entity of the downloaded data. So on the top of each add-method it said i.e [self deleteAllObjectsOfEntity:#"WeaponsRanged"];, My whole problem was that in the addMelee-method, it ALSO said [self deleteAllObjectsOfEntity:#"WeaponsRanged"]; instead of #"WeaponsMelee", thus deleting all ranged weapons, and later adding melee to the melee entity. And that also proves that the other problem I mentioned of arrMelee logging out four times as much data as it should was caused by this.
The reason it sometimes worked was that the downloading is not happening in any ordered mode. So the addRanged was sometimes called before the addMelee. If ranged comes first, it clears the arrRanged, and fills it up with correct data, and THEN melee comes, and clears it out. When melee was called first, it cleared arrRanged and filled additional data to arrMelee, and THEN ranged comes and tries to clear an empty entity, and then fills it up with correct data.
The solution was obviously to change the entity deleted when adding it, as it was the wrong one.
Sorry.... :)

Mac OS App crashes when saving too many new NSManagedObjects

I'm trying to write an app that enumerates through folders and items which are dragged onto the main app window and then places a new entry for each PDF it finds into my Core Data database which in turn populates an NSTableView. To cut a long story short[er], I've got to a stage where I can safely drag a few items into the window and the database gets saved, allowing me to restart the app time after time.
The problem I have is that if I were to [say] drag my Documents folder onto the window it all appears to work but when I quit and attempt to restart then it crashes out with a
Thread 1: EXC_BAD_ACCESS (code=2, address-0x7fff6f11dff8
The code I'm currently using (specifically written for debugging this matter) is:
NSError *error;
id firstObject = [draggedStuff objectAtIndex:0];
NSDictionary *fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:firstObject error:&error];
if ([[fileAttribs valueForKey:NSFileType] isEqualTo:NSFileTypeDirectory]) {
NSManagedObject *aNewFolder = [[NSManagedObject alloc]initWithEntity:[NSEntityDescription entityForName:kFOLDERS_ENTITY inManagedObjectContext:[self managedObjectContext]] insertIntoManagedObjectContext:[self managedObjectContext]];
[aNewFolder setValue:firstObject forKey:kFOLDER_PATH];
NSArray *directoryContents = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:firstObject error:&error];
assert(!error);
[directoryContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CFStringRef fileExtension = (__bridge CFStringRef) [obj pathExtension];
CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL);
if (UTTypeConformsTo(fileUTI, kUTTypePDF)) {
NSManagedObject *aDocument = [[NSManagedObject alloc]initWithEntity [NSEntityDescription entityForName:kDOCO_ENTITY inManagedObjectContext:[self managedObjectContext]] insertIntoManagedObjectContext:[self managedObjectContext]];
[aNewPicture setValue:obj forKey:#"docoPath"];
}
CFRelease(fileUTI);
if (idx % 20 == 0) {
NSError *error;
[[self managedObjectContext] save:&error];
assert(!error);
}
}];
}
Initially, I had this in an NSOperation subclass with the relevent checking for user cancellation but I've moved it to the main thread just to make sure that it's not something to do with that, so, yes, it is currently not a user-friendly piece of code.
The crashing is definitely connected to the number of items that directoryContents returns with as it works absolutely fine if I drag a folder with only a matter of 20 or so items inside it. If directoryContents holds a matter of around 200 files or more then Core Data doesn't seem to save it correctly and corrupts the storedata file which needs to be Trashed before I can restart the app.
The if (idx % 20 == 0)... can be changed to save with more or less NSManagedObjects waiting to be saved but with the same results as well.
I've also tried using an NSDirectoryEnumerator with the same results: the corruption is always connected with the number of items in the folder.
I like Core Data, but sometimes I lose my way so any help is much appreciated especially given the length of this post!
Todd.
Turns out that it was much simpler than I thought: I'd managed to wire up my NSTable incorrectly.
I'd connected both the Table View and the Table column in the NIB to the entity.... Oops.

Reading samples via AVAssetReader

How do you read samples via AVAssetReader? I've found examples of duplicating or mixing using AVAssetReader, but those loops are always controlled by the AVAssetWriter loop. Is it possible just to create an AVAssetReader and read through it, getting each sample?
Thanks.
It's unclear from your question whether you are talking about video samples or audio samples. To read video samples, you need to do the following:
Construct an AVAssetReader:
asset_reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
(error checking goes here)
Get the video track(s) from your asset:
NSArray* video_tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack* video_track = [video_tracks objectAtIndex:0];
Set the desired video frame format:
NSMutableDictionary* dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:[NSNumber numberWithInt:<format code from CVPixelBuffer.h>] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
Note that certain video formats just will not work, and if you're doing something real-time, certain video formats perform better than others (BGRA is faster than ARGB, for instance).
Construct the actual track output and add it to the asset reader:
AVAssetReaderTrackOutput* asset_reader_output = [[AVAssetReaderTrackOutput alloc] initWithTrack:video_track outputSettings:dictionary];
[asset_reader addOutput:asset_reader_output];
Kick off the asset reader:
[asset_reader startReading];
Read off the samples:
CMSampleBufferRef buffer;
while ( [asset_reader status]==AVAssetReaderStatusReading )
buffer = [asset_reader_output copyNextSampleBuffer];
damian's answer worked for me with video, and one minor modification: In step 3, I think the dictionary needs to be mutable:
NSMutableDictionary* dictionary = [[NSMutableDictionary alloc] init];