Looking through the documentation for CFDataRef I can't see anything that will compress a CFDataRef after it has been created. For example, in my code I do something like this:
CFIndex byteSize = GetExportByteSize();
const UInt8 *exportData = GetExportDataPtr();
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, exportData, byteSize);
CFDictionarySetValue(dict, kAS_ExportDataKey, dataRef);
Basically, I'm creating a CFDataRef from a chunk of memory that is about 2MB in size. Doesn't sound like much but in practice for this application it is too much data.
Is it possible to compress the CFDataRef object after creation? Or might it be better to compress the raw data it points to first?
Is it possible to compress the CFDataRef object after creation?
zlib.h is one option.
Or might it be better to compress the raw data it points to first?
How much time do you want to spend developing this? How well can the data structure's size be reduced using what you know of the input data? Is the implementation in zlib (or another lib) unacceptable?
I should note that OS X now has SecTransform https://developer.apple.com/library/mac/#documentation/System/Reference/SecTransform_header_reference/Reference/reference.html which you can use to do ZLib compression on CFData. I have an example of using it here https://github.com/Machx/Zangetsu/blob/master/Source/CWZLib.m
-(NSData *)cw_zLibCompress {
SecTransformRef encoder;
CFDataRef data = NULL;
CFErrorRef error = NULL;
CFDataRef inputData = CFDataCreate(kCFAllocatorDefault, [self bytes], [self length]);
if (inputData == NULL) { return nil; }
encoder = SecEncodeTransformCreate(kSecZLibEncoding, &error);
if(error) { CWZLIBCLEANUP(); return nil; }
SecTransformSetAttribute(encoder, kSecTransformInputAttributeName, inputData, &error);
if (error) { CWZLIBCLEANUP(); return nil; }
data = SecTransformExecute(encoder, &error);
if (error) { CWZLIBCLEANUP(); return nil; }
NSData *compressedData = [[NSData alloc] initWithData:(__bridge NSData *)data];
CFRelease(encoder);
CFRelease(inputData);
return compressedData;
}
Related
I am using the tensorlfow C++ API and I have linked the tensor flow framework to perform a prediction using an inference.pb file.
The inference works but I have a Memory leak which (according to instruments) is linked to the session->Close().
My function is:
- (double) MLPredictionObjC: (float[24]) inputFeatures {
double results[]={0.0};
NSString *path = [[NSBundle mainBundle] pathForResource:#"inference84" ofType:#"pb"];
if ([self loadGraphFromPath:path] && [self createSession]) {
double resultsP = [self predictML:inputFeatures];
session->Close();
return resultsP;
}
return *results;
}
// the other functions associated with loading and creating the session are:
- (BOOL)loadGraphFromPath:(NSString *)path
{
ReadBinaryProto(tensorflow::Env::Default(), path.fileSystemRepresentation, &graph);
return YES;
}
- (BOOL)createSession
{
tensorflow::SessionOptions options;
tensorflow::NewSession(options, &session);
session->Create(graph);
return YES;
}
I am not sure how to resolve this? should I be using std::unique_ptr session?
I'm converting our app over to use the Photos Framework of iOS8, the ALAsset framework is clearly a second class citizen under iOS8.
I'm having a problem is that our architecture really wants an NSURL that represents the location of the media on "disk." We use this to upload the media to our servers for further processing.
This was easy with ALAsset:
ALAssetRepresentation *rep = [asset defaultRepresentation];
self.originalVideo = rep.url;
But I'm just not seeing this ability in PHAsset. I guess I can call:
imageManager.requestImageDataForAsset
and then write it out to a temp spot in the file system but that seems awfully heavyweight and wasteful, not to mention potentially slow.
Is there a way to get this or am I going to have refactor more of my app to only use NSURLs for iOS7 and some other method for iOS8?
If you use [imageManager requestAVAssetForVideo...], it'll return an AVAsset. That AVAsset is actually an AVURLAsset, so if you cast it, you can access it's -url property.
I'm not sure if you can create a new asset out of this, but it does give you the location.
SWIFT 2.0 version
This function returns NSURL from PHAsset (both image and video)
func getAssetUrl(mPhasset : PHAsset, completionHandler : ((responseURL : NSURL?) -> Void)){
if mPhasset.mediaType == .Image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInputWithOptions(options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [NSObject : AnyObject]) -> Void in
completionHandler(responseURL : contentEditingInput!.fullSizeImageURL)
})
} else if mPhasset.mediaType == .Video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .Original
PHImageManager.defaultManager().requestAVAssetForVideo(mPhasset, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) -> Void in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl : NSURL = urlAsset.URL
completionHandler(responseURL : localVideoUrl)
} else {
completionHandler(responseURL : nil)
}
})
}
}
If you have a PHAsset, you can get the url for said asset like this:
[asset requestContentEditingInputWithOptions:editOptions
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
}];
Use the new localIdentifier property of PHObject. (PHAsset inherits from this).
It provides similar functionality to an ALAsset URL, namely that you can load assets by calling the method
+[PHAsset fetchAssetsWithLocalIdentifiers:identifiers options:options]
All the above solutions won't work for slow-motion videos. A solution that I found handles all video asset types is this:
func createFileURLFromVideoPHAsset(asset: PHAsset, destinationURL: NSURL) {
PHCachingImageManager().requestAVAssetForVideo(self, options: nil) { avAsset, _, _ in
let exportSession = AVAssetExportSession(asset: avAsset!, presetName: AVAssetExportPresetHighestQuality)!
exportSession.outputFileType = AVFileTypeMPEG4
exportSession.outputURL = destinationURL
exportSession.exportAsynchronouslyWithCompletionHandler {
guard exportSession.error == nil else {
log.error("Error exporting video asset: \(exportSession.error)")
return
}
// It worked! You can find your file at: destinationURL
}
}
}
See this answer here.
And this one here.
In my experience you'll need to first export the asset to disk in order to get a fully accessible / reliable URL.
The answers linked to above describe how to do this.
Just want to post the hidden gem from a comment from #jlw
#rishu1992 For slo-mo videos, grab the AVComposition's
AVCompositionTrack (of mediaType AVMediaTypeVideo), grab its first
segment (of type AVCompositionTrackSegment), and then access its
sourceURL property. – jlw Aug 25 '15 at 11:52
In speking of url from PHAsset, I had once prepared a util func on Swift 2 (although only for playing videos from PHAsset). Sharing it in this answer, might help someone.
static func playVideo (view:UIViewController, asset:PHAsset)
Please check this Answer
Here's a handy PHAsset category:
#implementation PHAsset (Utils)
- (NSURL *)fileURL {
__block NSURL *url = nil;
switch (self.mediaType) {
case PHAssetMediaTypeImage: {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;
[PHImageManager.defaultManager requestImageDataForAsset:self
options:options
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
url = info[#"PHImageFileURLKey"];
}];
break;
}
case PHAssetMediaTypeVideo: {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[PHImageManager.defaultManager requestAVAssetForVideo:self
options:nil
resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:AVURLAsset.class]) {
url = [(AVURLAsset *)asset URL];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
break;
}
default:
break;
}
return url;
}
#end
I had similiar problem with video files, what worked for me was:
NSString* assetID = [asset.localIdentifier substringToIndex:(asset.localIdentifier.length - 7)];
NSURL* videoURL = [NSURL URLWithString:[NSString stringWithFormat:#"assets-library://asset/asset.mov?id=%#&ext=mov", assetID]];
Where asset is PHAsset.
Every Mac has a model identifier, for example "Macmini5,1". (These are shown in the System Information app.)
How can I programatically obtain this model identifier string?
Swift 4+ using IOKit
import IOKit
func getModelIdentifier() -> String? {
let service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"))
var modelIdentifier: String?
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
modelIdentifier = String(data: modelData, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters)
}
IOObjectRelease(service)
return modelIdentifier
}
You can use sysctl
#import <Foundation/Foundation.h>
#import <sys/sysctl.h>
NSString *ModelIdentifier()
{
NSString *result=#"Unknown Mac";
size_t len=0;
sysctlbyname("hw.model", NULL, &len, NULL, 0);
if (len) {
NSMutableData *data=[NSMutableData dataWithLength:len];
sysctlbyname("hw.model", [data mutableBytes], &len, NULL, 0);
result=[NSString stringWithUTF8String:[data bytes]];
}
return result;
}
You can also use IOKit.framework. I think it's best choice.
This simple code example shows how to read model identifier from I/O Kit registry to NSString:
- (NSString *)modelIdentifier {
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"));
CFStringRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0);
NSString *modelIdentifier = [[NSString alloc] initWithData:(__bridge NSData *)model
encoding:NSUTF8StringEncoding];
CFRelease(model);
IOObjectRelease(service);
return modelIdentifier;
}
Strings "IOPlatformExpertDevice" and "model" in code above is used to read model identifier from I/O Kit registry. ioreg command line tool is your friend, when you want to find information from I/O Kit registry. This image shows those strings in ioreg output:
I hope this helps to use IOKit.framework.
Answer from Ryan H is correct except improper conversion from null-terminated string to Swift String, giving result with \0 symbol in the end, which you may not expect, performing full match. This is corrected version:
static private func modelIdentifier() -> String? {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
defer { IOObjectRelease(service) }
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
return modelData.withUnsafeBytes { (cString: UnsafePointer<UInt8>) -> String in
return String(cString: cString)
}
}
return nil
}
You can get the same output from the system_profiler command. It has an -xml option that you can use. NSTask can run the command for you and you can parse the result.
Sample code:
#import <Foundation/Foundation.h>
NSString *ModelIdentifier() {
NSPipe *pipe=[NSPipe pipe];
NSTask *task=[[NSTask alloc] init];
[task setLaunchPath:#"/usr/sbin/system_profiler"];
[task setArguments:#[#"-xml", #"SPHardwareDataType"]];
[task setStandardOutput:pipe];
[task launch];
NSData *outData=[[pipe fileHandleForReading] readDataToEndOfFile];
NSString *outString=[[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
return [outString propertyList][0][#"_items"][0][#"machine_model"];
}
CFStringRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0); ? type is ok ?
I think code maybe like this:
CFSDataRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0);
With iOS 16 apps that also build to Mac Catalyst any solution here that uses kIOMasterPortDefault (such as Ryan H's) will generate a build error:
'kIOMasterPortDefault' is unavailable in Mac Catalyst
Attempting to switch kIOMasterPortDefault to kIOMainPortDefault may also give a build error depending on which versions of Catalyst you are targetting – and I found trying to use #available to get around that didn't work either.
If you run into that situation then try the following, which is a reformulation of Parag Bafna's answer into Swift:
var modelIdentifier: String {
#if targetEnvironment(macCatalyst)
var size = 0
sysctlbyname("hw.model", nil, &size, nil, 0)
var modelIdentifier: [CChar] = Array(repeating: 0, count: size)
sysctlbyname("hw.model", &modelIdentifier, &size, nil, 0)
return String(cString: modelIdentifier)
#else
// Handle iOS
#endif
}
Swift version of Parag Bafna excellent answer
var deviceName: String {
var str = "Unknown Device"
var len = 0
sysctlbyname("hw.model", nil, &len, nil, 0)
if len > 0 {
var data = Data(count: len)
sysctlbyname("hw.model", &data, &len, nil, 0)
if let s = String(bytes: data, encoding: .utf8) {
str = s
}
}
return str
}
I am working on an app that requires me to save audio recordings in the database and retrieve them later. However, while I'm somewhat aware of how to use sqlite, I'm not sure if I'm using the right method when it comes to saving audio recordings, as I have a hard time getting it to work. The current code I'm using to save recordings is listed below and I would appreciate any feedback I receive regarding my code.
self.renameString =[NSString stringWithFormat:#"%#/%#.caf",DOCUMENTS_FOLDER,self.soundName.text];
NSLog(#"%#",self.renameString);
self.soundFileURL2 = [NSURL fileURLWithPath:self.renameString];
NSLog(#"%#",self.soundFileURL2);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *renameError;
[fm moveItemAtURL:self.soundFileURL toURL:self.soundFileURL2 error:&renameError];
[self checkAndCreateDatabase];
self.soundName.text = #"";
BOOL checkQuery;
NSData *blob;
blob = self.voiceData;
sqlite3_stmt *statement;
const char *insert = [self.renameString UTF8String];
if(sqlite3_prepare_v2(soundDB, insert, -1, &statement, NULL)!= SQLITE_OK) {
checkQuery=NO;
}
else {
sqlite3_bind_blob(statement, 1, [blob bytes], [blob length], NULL);
if(SQLITE_DONE != sqlite3_step(statement)) {
checkQuery=NO;
}
else {
// NSLog(#"Query Working nicely");
checkQuery=YES;
sqlite3_reset(statement);
}
}
//}
sqlite3_finalize(statement);
sqlite3_close(soundDB);
I'm also aware that in order to retrieve the saved file, you have to declare a new NSData variable and assign the reading to that variable. Afterwards, you have to extract the NSData and put it in another variable itself. The problem is, while I'm aware of how to extract NSString and UIImage data, I'm not sure about the rules for extracting AVAudioRecorder data. Can someone please help me on this? Thanks.
So the OS X Keychain has three pieces of information:
ServiceName (the name of my app)
Username
Password
I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)
I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;
How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?
SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.
If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
OSStatus status;
// Construct a query.
const char *utf8Service = [service UTF8String];
SecKeychainAttribute attr = { .tag = kSecServiceItemAttr,
.length = strlen(utf8Service),
.data = (void *)utf8Service };
SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
SecKeychainSearchRef *search = NULL;
status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
if (status) {
report(status);
return nil;
}
// Enumerate results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
while (1) {
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search, &item);
if (status)
break;
// Find 'account' attribute and password value.
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
SecKeychainAttributeList *attrList = NULL;
UInt32 length = 0;
void *data = NULL;
status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
if (status) {
CFRelease(item);
continue;
}
NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, #"SecKeychainItemCopyAttributesAndData is messing with us");
NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
[result setObject:password forKey:account];
SecKeychainItemFreeAttributesAndData(attrList, data);
CFRelease(item);
}
CFRelease(search);
return result;
}
For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
(id)kCFBooleanTrue, kSecReturnData,
(id)kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitAll, kSecMatchLimit,
service, kSecAttrService,
nil];
NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
if (status) {
report(status);
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSDictionary *itemDict in itemDicts) {
NSData *data = [itemDict objectForKey:kSecValueData];
NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *account = [itemDict objectForKey:kSecAttrAccount];
[result setObject:password forKey:account];
}
[itemDicts release];
return result;
}
For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (LKKCGenericPassword *item in [keychain genericPasswords]) {
if ([service isEqualToString:item.service]) {
[result setObject:item.password forKey:item.account];
}
}
return result;
}
(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)
You don't need the username. You do with EMKeychain, but that's an artificial distinction that that class imposes; the underlying Keychain Services function does not require a username to find a keychain item.
When using SecKeychainFindGenericPassword directly, pass 0 and NULL for the username parameters. It will return a keychain item that exists on that service.
However, that will return only one item. If the user has multiple keychain items on the same service, you won't know that, or which one you got (the documentation says it returns the “first” matching item, with no specification of what it considers “first”). If you want any and all items for that service, you should create a search and use that.
Generic passwords have a unique key of the service name and the username. Thus, to fetch a single generic keychain entry, you will need to provide both. However, you can iterate over all generic keychain entries for your given service using the SecKeychainFindGenericPassword function.
(Disclaimer: I don't know anything about doing this in EMKeychain.)