This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)
I can get the last element from an NSMutableArray with "lastObject". If the array was not initialized, it will return nil. Is there a similiar method or way to get this effect for the first object? This would be very helpful for me.
New update in ios 7
NSArray *array = #[#1, #2, #3];
NSLog(#"First Object: %#", [array firstObject]); // 1
NSLog(#"Last Object: %#", [array lastObject]); // 3
Something like this?
id myFirstObject = myArray.count>0 ? myArray[0] : nil;
If you particularly want this to be part of an array, make it a category :
#interface NSArray (firstObject)
- (id)firstObject;
#end
#implementation NSArray (firstObject)
- (id)firstObject {
return myArray.count>0 ? myArray[0] : nil;
}
#end
Then you get a firstObject that behaves the same as lastObject.
Why don't you just do
if ([array count] > 0) {
id x = [array objectAtIndex:0];
}
I want to see objects classes of my dictionary in console log. As for standard NSObject subclasses, I override -(NSString*) description in category:
-(NSString*) description
{
NSMutableString* desc = [NSMutableString stringWithFormat: #"<%# 0x%08x>\nobjects count: %ld", [self class], (uint)self, [self count]];
for (id key in [self allKeys])
[desc appendFormat: #"\n%# = %# (%#)", key, [self objectForKey: key], [[self objectForKey: key] class]];
return desc;
}
It works, but only for top-level NSDictionary object (if the object has dictionaries in children they are logged bypassing description method). So NSDictionary prints its children objects in some way without calling description on them...
Is there an approach to log these children dictionaries through my description method?
PS: In practical situation I want to find an object in dictionary that can't be saved to plist. Maybe there is another solution, I would be thankful for that too.
You can write a recursive description method:
// Private Methods
#interface MyClass ()
- (NSString *)_description:(id)object;
#end
...
- (NSString *)_description:(id)object
{
if ([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *dict = (NSDictionary *)object;
NSMutableString *desc = [NSMutableString stringWithFormat: #"<%# %p>\nobjects count: %ld", [dict class], dict, [dict count]];
for (id key in [dict allKeys])
{
[desc appendFormat: #"\n%# = %# (%#)", key, [self _description:[objectForKey: key]], [[self objectForKey: key] class]];
return desc;
}
}
else
{
return [(NSObject *)object description];
}
}
- (NSString *)description
{
return [self _description:self];
}
You'll probably want to pass an incrementing indentation counter so you can format the child objects better, but you should get the idea.
I need to read 3 values from a database and return them in a method. I'm having trouble to understand how to return values using NSInteger types. This is the code:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger verbal_time, NSInteger verbal_person)
{
NSMutableString *ret = [[NSMutableString alloc] initWithString:#""];
FMResultSet *rs = [mydb executeQuery:#"select verb_text, conjugation_verbal_time, conjugation_verbal_person from verbs where verb_conjugation = ?",[[myConjugation lowercaseString] precomposedStringWithCanonicalMapping]];
if ([rs next])
{
if (![rs columnIndexIsNull:0])
{
[ret setString:[rs stringForColumn:#"verb_text"]];
verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
}
else
{
NSLog(#"GetVerbInfinitive: verb '%#' has no infinitive defined", myConjugation);
}
}
[rs close];
return [ret autorelease];
}
The NSInteger values work only inside the method when I return to the calling method they are lost. I believe I should pass these NSInteger by reference, but I don't know how. I don't want to create a type structure for this.
Thanks,
Miguel
You have to dereference them as pointers. Here's an example (this goes in the code when you're calling the method):
NSInteger verbal_time, verbal_person;
GetVerbInfinitive(....., &verbal_time, &verbal_person);
Now in your getverbinfinitivemethod:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger *verbal_time, NSInteger *verbal_person)
{
...
...
*verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
*verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
...
...
}
Notice the change in the signature line to make those two NSIntegers pointers.
With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.
Currently I'm manually appending "?" followed by name value pairs joined by "&", but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn't fail entirely when it tries to connect to the URL.
This feels like something I should be able to use a pre-baked API for.... is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?
Introduced in iOS8 and OS X 10.10 is NSURLQueryItem, which can be used to build queries. From the docs on NSURLQueryItem:
An NSURLQueryItem object represents a single name/value pair for an item in the query portion of a URL. You use query items with the queryItems property of an NSURLComponents object.
To create one use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:
NSURLComponents *components = [NSURLComponents componentsWithString:#"http://stackoverflow.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:#"q" value:#"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:#"count" value:#"10"];
components.queryItems = #[ search, count ];
NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10
Notice that the question mark and ampersand are automatically handled. Creating an NSURL from a dictionary of parameters is as simple as:
NSDictionary *queryDictionary = #{ #"q": #"ios", #"count": #"10" };
NSMutableArray *queryItems = [NSMutableArray array];
for (NSString *key in queryDictionary) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
}
components.queryItems = queryItems;
I've also written a blog post on how to build URLs with NSURLComponents and NSURLQueryItems.
You can create a category for NSDictionary to do this -- there isn't a standard way in the Cocoa library that I could find either. The code that I use looks like this:
// file "NSDictionary+UrlEncoding.h"
#import <cocoa/cocoa.h>
#interface NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString;
#end
with this implementation:
// file "NSDictionary+UrlEncoding.m"
#import "NSDictionary+UrlEncoding.h"
// helper function: get the string form of any object
static NSString *toString(id object) {
return [NSString stringWithFormat: #"%#", object];
}
// helper function: get the url encoded string form of any object
static NSString *urlEncode(id object) {
NSString *string = toString(object);
return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}
#implementation NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString {
NSMutableArray *parts = [NSMutableArray array];
for (id key in self) {
id value = [self objectForKey: key];
NSString *part = [NSString stringWithFormat: #"%#=%#", urlEncode(key), urlEncode(value)];
[parts addObject: part];
}
return [parts componentsJoinedByString: #"&"];
}
#end
I think the code's pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.
I wanted to use Chris's answer, but it wasn't written for Automatic Reference Counting (ARC) so I updated it. I thought I'd paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.
+(NSString*)urlEscapeString:(NSString *)unencodedString
{
CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ", kCFStringEncodingUTF8);
CFRelease(originalStringRef);
return s;
}
+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];
for (id key in dictionary) {
NSString *keyString = [key description];
NSString *valueString = [[dictionary objectForKey:key] description];
if ([urlWithQuerystring rangeOfString:#"?"].location == NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
}
}
return urlWithQuerystring;
}
The other answers work great if the values are strings, however if the values are dictionaries or arrays then this code will handle that.
Its important to note that there is no standard way of passing an array/dictionary via the query string but PHP handles this output just fine
-(NSString *)serializeParams:(NSDictionary *)params {
/*
Convert an NSDictionary to a query string
*/
NSMutableArray* pairs = [NSMutableArray array];
for (NSString* key in [params keyEnumerator]) {
id value = [params objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
for (NSString *subKey in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[value objectForKey:subKey],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, escaped_value]];
}
} else if ([value isKindOfClass:[NSArray class]]) {
for (NSString *subValue in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)subValue,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, escaped_value]];
}
} else {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[params objectForKey:key],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, escaped_value]];
[escaped_value release];
}
}
return [pairs componentsJoinedByString:#"&"];
}
Examples
[foo] => bar
[translations] =>
{
[one] => uno
[two] => dos
[three] => tres
}
foo=bar&translations[one]=uno&translations[two]=dos&translations[three]=tres
[foo] => bar
[translations] =>
{
uno
dos
tres
}
foo=bar&translations[]=uno&translations[]=dos&translations[]=tres
I refactored and converted to ARC answer by AlBeebe
- (NSString *)serializeParams:(NSDictionary *)params {
NSMutableArray *pairs = NSMutableArray.array;
for (NSString *key in params.keyEnumerator) {
id value = params[key];
if ([value isKindOfClass:[NSDictionary class]])
for (NSString *subKey in value)
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];
else if ([value isKindOfClass:[NSArray class]])
for (NSString *subValue in value)
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, [self escapeValueForURLParameter:subValue]]];
else
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, [self escapeValueForURLParameter:value]]];
}
return [pairs componentsJoinedByString:#"&"];
}
- (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
NULL, (CFStringRef) #"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
}
If you are already using AFNetworking (as was the case with me), you can use it's class AFHTTPRequestSerializer to create the required NSURLRequest.
[[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:#"YOUR_URL" parameters:#{PARAMS} error:nil];
In case you only require the URL for your work, use NSURLRequest.URL.
Here is a simple example in Swift (iOS8+):
private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"
private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
return components.URL
}
return nil
}
I took Joel's recommendation of using URLQueryItems and turned into a Swift Extension (Swift 3)
extension URL
{
/// Creates an NSURL with url-encoded parameters.
init?(string : String, parameters : [String : String])
{
guard var components = URLComponents(string: string) else { return nil }
components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }
guard let url = components.url else { return nil }
// Kinda redundant, but we need to call init.
self.init(string: url.absoluteString)
}
}
(The self.init method is kinda cheesy, but there was no NSURL init with components)
Can be used as
URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
I've got another solution:
http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict
+(NSString*)urlEscape:(NSString *)unencodedString {
NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)unencodedString,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
kCFStringEncodingUTF8);
return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
}
// Put a query string onto the end of a url
+(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
// Convert the params into a query string
if (params) {
for(id key in params) {
NSString *sKey = [key description];
NSString *sVal = [[params objectForKey:key] description];
// Do we need to add ?k=v or &k=v ?
if ([urlWithQuerystring rangeOfString:#"?"].location==NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
}
}
}
return urlWithQuerystring;
}
You can then use it like so:
NSDictionary *params = #{#"username":#"jim", #"password":#"abc123"};
NSString *urlWithQuerystring = [self addQueryStringToUrl:#"https://myapp.com/login" params:params];
-(NSString*)encodeDictionary:(NSDictionary*)dictionary{
NSMutableString *bodyData = [[NSMutableString alloc]init];
int i = 0;
for (NSString *key in dictionary.allKeys) {
i++;
[bodyData appendFormat:#"%#=",key];
NSString *value = [dictionary valueForKey:key];
NSString *newString = [value stringByReplacingOccurrencesOfString:#" " withString:#"+"];
[bodyData appendString:newString];
if (i < dictionary.allKeys.count) {
[bodyData appendString:#"&"];
}
}
return bodyData;
}
Yet another solution, if you use RestKit there's a function in RKURLEncodedSerialization called RKURLEncodedStringFromDictionaryWithEncoding that does exactly what you want.
Simple way of converting NSDictionary to url query string in Objective-c
Ex: first_name=Steve&middle_name=Gates&last_name=Jobs&address=Palo Alto, California
NSDictionary *sampleDictionary = #{#"first_name" : #"Steve",
#"middle_name" : #"Gates",
#"last_name" : #"Jobs",
#"address" : #"Palo Alto, California"};
NSMutableString *resultString = [NSMutableString string];
for (NSString* key in [sampleDictionary allKeys]){
if ([resultString length]>0)
[resultString appendString:#"&"];
[resultString appendFormat:#"%#=%#", key, [sampleDictionary objectForKey:key]];
}
NSLog(#"QueryString: %#", resultString);
Hope will help :)
If you are already using AFNetwork, you can use their built in serializer to to produce an encoded URL;
NSString *baseURL = #"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"true",#"option1", data, #"option2", token, #"token", #"3.0", #"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(#"%#", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....
Note; this is an extension to an above answer. The edit queue is full so cannot be added to the existing answer.