Playing video from NSData stored in Core Data - ios

I am trying to play a video stored in Core data. After fetch it shows, there is an object and objects.video returns a value but the dataString prints out to be null. I am not sure what I maybe doing wrong. Is it a right way to play video or is there something I could have done better?
I have single object in Core Data.
I have stored as video as NSData in Core data. I want to get that stored video and play. Is there any other way I can do it?
_context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Activity" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
// Specify how the fetched objects should be sorted
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"level"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchResults = [_context executeFetchRequest : fetchRequest error : &error];
if(fetchRequest == nil){
NSLog(#"Nothing fetched");
}
for (Activity *objects in fetchResults){
NSLog(#"%#",objects.video);
prints-> External Data Reference: <self = 0x7bf48750 ; path = FF54B18E-10B3-4B04-81D4-55AC5E2141B9 ; length = 504426>
NSString *dataString = [[NSString alloc] initWithData:objects.video encoding:NSUTF8StringEncoding];
NSLog(#"%#",dataString);
NSURL *movieURL = [NSURL URLWithString:dataString];
NSLog(#"%#",movieURL);
_moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
[_moviePlayer.view setFrame:CGRectMake (20, 20, 200 , self.view.bounds.size.height/2)];
[self.view addSubview:_moviePlayer.view];
[_moviePlayer play];
}
NSLog(#"%i",fetchResults.count);

It will just be a binary property on the managed object. You call the property and it returns NSData. From there you can do whatever you want with the NSData while it is memory. Your problem is that you can't convert the NSData into a NSURL. A NSURL is a reference to data where NSData is the actual data.
What you need to do is store the video file on disk, outside of the SQLite file. Then store a reference to it (aka a url) in Core Data. That will allow you to use the video file with the movie player.
As others have said, storing the video in the SQLite file is a bad idea. It will wreck the performance of Core Data.
Update 1
Thanks. I have not saved the video directly to core data instead just copied the video url and saved it as string in core data. Should I create a different folder for the app to store the videos whenever I create a copy of the videos the users use so even if they delete the original video that was uploaded to core data, the copy remains intact and thus maintaining integrity of the Objects in Core Data.
Your comment is unclear. According to your question, you are storing the actual video in Core Data. Based on the output you showed, you stored the file in Core Data. Have you changed that? If so, you should store the video file in a known location and store the relative URL to that location in core data as a string. Then you build the full URL from that when you are ready to use it. Since the sandbox can change you can't store the entire URL as it will go stale.

Related

Encrypted Core Data doesn't encrypt the .sqlite file and extra "ecd" tables doesn't have any records

I am using Xcode 10.3 for an iOS 9.0 project. I'm trying to use Encrypted Core Data to encrypted both the Core Data and the .sqlite file but the problem is that it doesn't encrypt the .sqlite file.
I verified this when i hexdump the .sqlite file, it still shows the SQL Format and other plain text instead of encrypted text like it shown in the library's github repo.
Initially, the .sqlite file contains 172 tables but the number of tables increases to 342 when using the library. I checked it on the SQLiteStudio and the database is full of tables with "ecd" appended to the table names (example: ecdStocks), i assumed that ecd is the acronym of the library, not to mentioned that it still includes the old tables with "Z" appended on it (example: ZSTOCKS). Is this normal for the library to do this?
Another problem is that the "ecd" tables doesn't contain any records while the old tables before it was converted to "ecd" contain full of records.
Here's the code that i use with the library.
- (NSManagedObjectContext *)masterManagedObjectContext {
if (_masterManagedObjectContext != nil) {
return _masterManagedObjectContext;
}
NSURL *storeURL = [[self getApplicationDocumentsDirectory] URLByAppendingPathComponent:SQLITE_FILENAME];
// This is the result of the URL /Users/user1/Library/Developer/CoreSimulator/Devices/BE0082D4-2D71-42A2-959D-F1AC2BD947D9/data/Containers/Data/Application/CC4BA3B8-9143-47F3-BF2D-5DABB6EDB137/Documents/dbTest.sqlite
NSString *path = [storeURL path]; //get the URL string
NSDictionary *options = #{ EncryptedStorePassphraseKey: #"",
EncryptedStoreDatabaseLocation: path,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
#"journal_mode" : #"DELETE"
};
NSError *error = nil;
NSManagedObjectModel *objectModel = [self managedObjectModel];
NSPersistentStoreCoordinator *coordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:objectModel error:&error];
//NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSLog(#"%#", error.localizedDescription); //result is nil
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_masterManagedObjectContext performBlockAndWait:^{
[_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];
}
return _masterManagedObjectContext;
}
I tried to validate the path by using this condition
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(#"%#", #"Exist");
}
It outputs the "Exist" on the console which means that the library has access to the .sqlite file. I also validate this by using an incorrect passcode which the library outputs an error since my .sqlite file doesn't have a password.
Deleting and rebuilding the app still doesn't work.
The library doesn't include any error messages which means it doesn't have a problem accessing it.

App crashes parsing JSON only when installed via Testflight

So my app begins on first run by parsing all the information in a json file (included in the project) and saves that into its core data model.
This runs fine via debugging deployment with XCode however when I install the app via Testflight, it crashes immediately.
I have identified it to be the parsing of the json file that is the problem as I have created a build that does not do this & instead grabs the data straight from a web api, the only problem with this is that from the web api it will take about an hour to download all the data, from the json file, it takes about a minute.
I have tried disabling BITCODE & ensuring that the scheme is set to Release, which seems to cover most of these start up problems via Testflight. Neither has worked.
My suspicion is that the json file has not correctly been packaged with the app when distributed via Testflight, but i have no idea how to remedy this. Does anyone have any suggestions?
I am including my import function below incase the error is contained within this and not the configuration.
The json file is roughly 26mb which is very large compared to most other files in the project.
My device is an iPhone X running iOS 11.2.6 and XCode 9.2,
the app also successfully runs on all simulators.
I am using Objective C
-(void)ImportInitialCards:(NSManagedObjectContext *) managedObjectContext
{
_privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// Configure Managed Object Context
[_privateManagedObjectContext setParentContext:managedObjectContext];
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"cardDatabase1" ofType:#"json"];
NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:filePath];
[inputStream open];
NSArray *cards = [NSJSONSerialization JSONObjectWithStream:inputStream options:kNilOptions error:nil];
float percentage = 1.0 / (float) cards.count;
for (NSDictionary *card in cards)
{
NSString *cardParticularsJson = [card valueForKey:#"cardContent"];
NSData *cardParticularsData = [cardParticularsJson dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *cardParticulars = [NSJSONSerialization JSONObjectWithData:cardParticularsData options:NSJSONReadingMutableContainers error:nil];
NSString *cardName = [cardParticulars valueForKey:#"name"];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Card"];
[request setPredicate:[NSPredicate predicateWithFormat:#"name == %#", cardName]];
NSError *error = nil;
NSUInteger count = [_privateManagedObjectContext countForFetchRequest:request error:&error];
if (count > 0)
{
NSLog(#"CardMatched: %#", cardName);
//card matched
_progress += percentage;
_currentProgressDescription = #"Scanning ...";
}
else
{
_currentProgressDescription = [NSString stringWithFormat:#"Importing: %#", cardName];
NSManagedObject *newCard = [NSEntityDescription insertNewObjectForEntityForName:#"Card" inManagedObjectContext:_privateManagedObjectContext];
[self SaveCardFrom:cardParticulars to:newCard saveThumb:NO inContext:_privateManagedObjectContext];
_progress += percentage;
dispatch_async(dispatch_get_main_queue(),^ {
NSError *error = nil;
[managedObjectContext save:&error];
} );
}
}
}
I've decided to post the solution to my problem in the hope that it helps someone else that struggled like me.
So while importing the JSON file I was updating a progress bar to indicate how long it would take, this was in a while loop that was maxing out the CPU, that along with the JSON import sent the CPU to approx 200% which caused the app to close.
The solution seems quite obvious really, don't make the cpu go above 100%
I now plan to only update the progress bar when the value is changed rather than constantly updating via a while loop.

Automatically drop and re-create Persistent Data Store with Core Data + RestKit 0.2.x

While in the early stages of developing my app that uses Core Data and RestKit I often modify the data model quite heavily and find little or no use to even think about migrations.
I'd like the logic to be:
IF (there is a foolproof, automatic migration path) THEN
TRY { foolproof path; }
CATCH { brute path; }
ELSE
brute path;
brute path:
IF (SqlLiteDatabase exists) THEN
DELETE IT;
CREATE SqlLiteDatabase;
What I have now, I honestly do not understand...
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:[[RestKitClientConfigurator webServiceConfiguration] baseURL]]];
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
[managedObjectStore createPersistentStoreCoordinator];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"CTISDB.sqlite"];
NSString *seedPath = [[NSBundle mainBundle] pathForResource:#"RKSeedDatabase" ofType:#"sqlite"];
NSDictionary *optionsDictionary =
[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:NSMigratePersistentStoresAutomaticallyOption];
NSError *error;
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
But basically, what I am really wondering is - isn't this by now a common enough problem (having to constantly reset virtual device) that there is a way to tell Core Data just to blow it up?
And if not, what am I missing about the complexity of this operation that prevents the above algorithm from being implemented?
Don't know if this would affect RestKit, but here is my usual development setup when developing an evolving Core Data application:
Set a flag to wipe the store on each run.
In the persistentStoreCoordinator method, delete the store file just before calling addPersistentStore... if the flag is set.
Add a DataPopulator class that conveniently populates the database with imported or random generated dummy data, best also based on a flag.
Once you are ready or want to test data you have created in the app before, just change the flags. It's very convenient.
While this would correspond to your "brute force" method, I have found it to be the simplest and most efficient for rapid development cycles. Also, I cannot see any disadvantage compared to a "less brutish" migration process unless your in-app generated data is really critical for development, which tends to be rare.
BTW, the data populator can come in very handy later if you need to generate seed data, or if you want to stress-test the system with large amounts of records.
Adding some code for deleting the store:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = // get the store URL
if (kWipeDB) {
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
}
// add persistent store
return _persistentStoreCoordinator;
}
You would find this method in the AppDelegate if you used XCode's standard Core Data template.

Core data issue when storing larger images

When i am storing larger size images in core data using for loop i am receiving memory warning message by the didReceiveMemoryWarning method when
iteration count is 300. Now based on memory warning i can display the user with the alert that "memory is fully please sync your images". But my problem is i am unable to get memory warning greater that 300. i.e i am getting memory warning exactly for 300th iteration.above 300 and below 300 i am not getting memory warning.
this is code which i used
for (int i=0;i<=300;i++)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *persistentStorePath = [documentsDirectory stringByAppendingPathComponent:#"DetailsRegister.sqlite"];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EMpDetails" inManagedObjectContext:context];
NSManagedObject *newDevice=[[NSManagedObject alloc]initWithEntity:entity insertIntoManagedObjectContext:context];
UIImage *image = [UIImage imageNamed:#"image.png"];
imageview.image=image;
[self SaveImage:image];
dataImage = UIImageJPEGRepresentation(image, 0.0);
[newDevice setValue:dataImage forKey:#"image"]; // obj refers to NSManagedObject
error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
CoreData isn't really an ideal place to store image data.
I tend to just store the imageData (or just actual images if they are not sensitive) in the documents folder and then just store an imageURL against the persisted object.
That way you can just return the Image for a URL that way, much better performance.
Again drawing on Marcus S. Zarra's Core Data: Data Storage and Management for iOS, OS X, and iCloud (2nd edition), one recommendation is as follows:
Small Binary Data [...] anything smaller than 100 kilobytes [...] When working with something this small, it is most
efficient to store it directly as a property value in its
corresponding table.
Medium Binary Data [...] anything larger than 100
kilobytes and smaller than 1 megabyte in size [...] Data
of this size can also be stored directly in the repository. However,
the data should be stored in its own table on the other end of a
relationship with the primary tables.
Large Binary Data [...] greater than 1
megabyte in size [...] Any binary data of this size
should be stored on disk as opposed to in the repository. When working
with data of this size, it is best to store its path information
directly in the primary entity [...] and store the binary data in a known location on disk (such as in the Application Support subdirectory for your application).
For more details, you should get the book (disclosure: not affiliated).

iOS: Get file's metadata

I have an mp3 file on a server. I want to get this file's information like what's the size of this file, what's the artists name, what's the album name, when was the file created, when was it modified, etc. I want all this information.
Is it possible to get this information without actually downloading the whole file? Using NSURLConnection or otherwise?
EDIT:
The following code doesn't give me the required information, i.e. file created by, artist name, etc
NSError *rerror = nil;
NSURLResponse *response = nil;
NSURL *url = [NSURL URLWithString:#"http://link.to.mp3"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"HEAD"];
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&rerror];
NSString *resultString = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"URL: %#", url);
NSLog(#"Request: %#", request);
NSLog(#"Result (NSData): %#", result);
NSLog(#"Result (NSString): %#", resultString);
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", rerror);
if ([response isMemberOfClass:[NSHTTPURLResponse class]]) {
NSLog(#"AllHeaderFields: %#", [((NSHTTPURLResponse *)response) allHeaderFields]);
}
The "AllHeaderFields" is:
AllHeaderFields: {
"Cache-Control" = "max-age=0";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Type" = "text/plain; charset=ascii";
Date = "Fri, 17 Feb 2012 12:44:59 GMT";
Etag = 19202n;
Pragma = public;
Server = dbws;
"x-robots-tag" = "noindex,nofollow";
}
It is quite possible to get the ID3 information embedded in an MP3 file (artist name, track title) without downloading the whole file or using low-level APIs. The functionality is part of the AVFoundation framework.
The class to look at is AVAsset and specifically it's network friendly subclass AVURLAsset. AVAsset has an NSArray property named commonMetadata. This commonMetadata property will contain instances of AVMetadataItem, assuming of course that the reference URL contains metadata. You will usually use the AVMetadataItem's commonKey property to reference the item. I find this method of iterating through an array checking commonKeys irritating so I create an NSDictionary using the commonKey property as the key and the value property as the object. Like so:
-(NSDictionary *)dictionaryOfMetadataFromAsset:(AVAsset *)asset{
NSMutableDictionary *metaData = [[NSMutableDictionary alloc] init];
for (AVMetadataItem *item in asset.commonMetadata) {
if (item.value && item.commonKey){
[metaData setObject:item.value forKey:item.commonKey];
}
}
return [metaData copy];
}
With the addition of this simple method the AVAsset's metadata becomes quite easy to use. Here is an example of getting an MP3's metadata through a URL:
NSURL *mp3URL = [NSURL URLWithString:#"http://'AddressOfMP3File'"];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:mp3URL options:nil];
NSDictionary *metaDict = [self dictionaryOfMetadataFromAsset:asset];
NSLog(#"Available Metadata :%#",metaDict.allKeys);
NSLog(#"title:%#",[metaDict objectForKey:#"title"]);
I have found that this code seems to load just the first few seconds of your MP3 file. Also note that this code is synchronous; So use with caution. But AVURLAsset does have some async functionality described in the docs.
Once you have the AVAsset you can create a AVPlayerItem with it and feed that to an AVPlayer and play it, or not.
Yes and no. Things like the file size and modification date often come as part of the HEAD response. But not always: with a lot of dynamic URLs, you won't get all of the information.
As for the artist and album name, they're part of the MP3's ID3, which is contained inside the file, and so you won't be able to get them with a HEAD request. Since the ID3 tag is typically at the beginning of a file, you could try to grab just that part and then read the ID3 tag. But you won't be able to do it with NSURLConnection since it doesn't support just fetching part of a file, so you'll need to find a more low-level way of getting data by HTTP.
Yep, you're right on target with NSURLConnection.
I think you want to send a HEAD request for the resource you want information about and then check the information you receive in connection:didReceiveResponse: and connection:didReceiveData:
Edit
Admittedly I didn't read your question in its entirety. It won't be possible to get ID3 information, but you should be able to get size of file and maybe creation date etc.
This answer does give some good information about how to get the ID3 information. You'd need to set up a php page to examine the mp3 file server-side and return just that information you require instead of the entire mp3.

Resources