How to make this Parse.com cloud code call? - ios

Note:
Here's some thoughts from CA at Parse about this:
https://www.parse.com/questions/ios-when-will-swift-for-parse-be-ready
(notice how popular that question is - hot topic). Hope it helps someone
Here's an iOS7 Parse cloud code call ...
how to do this in SWIFT ? cheers
To be clear ... can you use "callFunctionInBackground" in SWIFT, or do you have to just call to an objc class?
-(void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
int thisRow = indexPath.row;
PFUser *delFriend = [self.theFriends objectAtIndex:thisRow];
NSLog(#"you wish to delete .. %#", [delFriend fullName] );
// note, this cloud call is happily is set and forget
// there's no return either way. life's like that sometimes
[PFCloud callFunctionInBackground:#"clientRequestFriendRemove"
withParameters:#{
#"removeThisFriendId":delFriend.objectId
}
block:^(NSString *serverResult, NSError *error)
{
if (!error)
{
NSLog(#"ok, Return (string) %#", serverResult);
}
}];
[self back]; // that simple
}
Note, I've noticed this is a google landing page for trying to figure out "how the heck to do cloud code calls" (unrelated to Swift). Here is a full, complete set of example code for both iOS and Android of custom cloud code functions, in the Parse.com universe https://stackoverflow.com/a/24010828/294884 Hope it helps someone

Add an Objective-C .m file to your project. Xcode will ask about creating a Bridge Header file. Say yes.
Delete the .m file. In the bridge header file, add your import statement:
#import <Parse/Parse.h>
Another answer, which has a way better walkthrough than mine: https://stackoverflow.com/a/24005242/353988
Now you can call native Parse code in Swift, i.e.:
import UIKit
import Foundation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
Parse.setApplicationId("appid", clientKey: "clientkey")
var obj = PFObject(className:"TestObject")
obj.setObject("bar", forKey: "foo")
obj.saveInBackgroundWithBlock ({
(succeeded: Bool!, err: NSError!) -> Void in
NSLog("Hi")
})
}
}

It is not possible to call this exact function (callFunctionInBackground) because it is an obj-C function.
Please refer to this question how to call obj-c functions.
With time Parse will also introduce Swift implementation.

Related

How to call Swift from an Objective C project App Delegate?

This code is from a Swift project App delegate. It is used to help configure Stripe with a publishable key.
//Appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
//The code helps configure Stripe with a publishable key.
STPPaymentConfiguration.shared().publishableKey = Constants.publishableKey
...
}
Two errors are displayed when building the app after adding the Swift line to the Objective C App Delegate
//AppDelegate.h
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
STPPaymentConfiguration.shared().publishableKey = Constants.publishableKey
Property 'shared' not found on object of type 'STPPaymentConfiguration'
Use of undeclared identifier 'Constants'
This was a similar error in compiling before #objc was added to the demo Swift function, MockApiClient. Should it be added elsewhere? I've tried adding #objc to the enum as mentioned in the answer here to no avail yet.
//Constants.swift
//This is the file the original Swift app delegate accesses
import Foundation
enum Constants {
static let publishableKey = "pk_live_..."
static let baseURLString = "http://54.33.123.227:1234"
static let defaultCurrency = "usd"
static let defaultDescription = "Receipt" //change to describe actual app & charge
}
Steps taken:
Opened the Objective C project and created a bridging header
Created a demo class in Swift while still in the Obj C project to make sure it can be used, in this case to print from an Objective C file when the view is loaded. Specifically derived from an NSObject. Adding the override to the initializer and using the #objc prefix.
// MockApiClient.swift
import Foundation
class MockApiClient: NSObject
{
override init()
{
print("Initializer called in Mock API client")
}
#objc func executeRequest()
{
print("The execute request has been called in the Mock API Client")
}
}
//ViewController.h
//Prints the Swift request written in the MockApiClient the the view loads
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MockApiClient *client = [MockApiClient new];
[client executeRequest];
}
Copied the #import "ViewController.h" import to the automatically generated project-Bridging-Header.h file to expose the Objective C in it to swift
Added the necessary Swift files to the Objective C project so that the Constants.publishablekey data from Constants.swift can be found
How can this Swift App delegate code be added to the App delegate of an Objective C project?
Edit: error when adding #objc to the enum declaration in Constants.swift
Edit: error when adding #objc to the enum declaration in Constants.swift
Swift enums used as namespace cannot be exposed to Objective-C.
You may need to use class to make it work both for Swift and Objective-C:
#objcMembers
class Constants: NSObject {
static let publishableKey = "pk_live_..."
static let baseURLString = "http://54.33.123.227:1234"
static let defaultCurrency = "usd"
static let defaultDescription = "Receipt" //change to describe actual app & charge
private override init() {}
}
The ability of Objective-C to see things defined in Swift depends on the automatically generated header file. This is not the bridging header. It is a header file buried in your derived data called YourProject-Swift.h. Your Objective-C .m file needs to #import "YourProject-Swift.h" (using the correct name).
Then, your Swift things need to get into that file. For that to happen, they need to be of a type that Objective-C can see at all (i.e. classes) and they need to be explicitly exposed to Objective-C with appropriate #objc attributes.

EXC_BAD_ACCESS exception when calling a Swift closure from C

I'm trying to call a Swift closure from C.
The following piece of code shall represent what I'm currently working on.
First, in Swift, I initialize a static constant, the closure that is supposed to be called later.
This closure is then passed to a C function (api_set_callback_block) that stores the block pointer.
Some time after that, the C function api_trigger_block is called. This function should invoke the Swift closure. Instead of doing so, it always throws a runtime error: EXC_BAD_ACCESS when trying to access cb_block_cb() (also see below).
Usually, this should mean that something tries to access a previously stored variable was deallocated. However, I don't get how that could be the case as I was passing a static constant.
I double-checked that cb_block_cb is not NULL when accessing it.
void (^cb_block_cb)(int, int) = NULL;
void api_set_callback_block(void (^cb_block)(int, int))
{
if (cb_block == NULL)
{
puts("error: when setting callback block: cb_block is null");
return;
}
cb_block_cb = cb_block;
}
void api_trigger_block()
{
if (cb_block_cb == NULL)
{
puts("error: when triggering callback block: cb_block_cb is null");
return;
}
cb_block_cb(3,3); // <-- This is where the exception gets thrown
}
class CustomClass: NSObject {
public static let callback: (Int32, Int32) -> Swift.Void = { (cid, aid) in
print("Callback block called!")
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
api_set_callback_block(CustomClass.callback)
DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
api_trigger_block()
}
return true
}
// ...
}
Thank you for the interesting question.
I see a few options.
Option 1:
If you have control over the C API, make (some part of) it Objective-C by just changing the extension from .c to .m. Then your Swift code should interoperate with it just fine. At least the part of it that directly interacts with Swift code should be made Objective-C. Objective-C portion will hopefully be interoperable with the remaining C parts (the ones in .c files).
Option 2:
Write an Objective-C wrapper (in a .m file) around the C API and use the wrapper in Swift code. Sample wrapper based on your example:
// This is where we store the block passed in from Swift
void (^cb_block_cb2)(int, int) = NULL;
// This wrapper will be used in the block passed to C API
void block_wrapper(int i1, int i2) {
cb_block_cb2(i1, i2);
}
// Obj-C wrapper around C API block setter
// Make this available to Swift, e.g. via bridging header.
void api_set_callback_block2(void (^cb_block)(int, int))
{
if (cb_block == NULL)
{
puts("error: when setting callback block: cb_block is null");
return;
}
cb_block_cb2 = cb_block;
api_set_callback_block(^(int a, int b){ block_wrapper(a, b);});
}
// Obj-C wrapper around C API block trigger
// Make this available to Swift, e.g. via bridging header.
void api_trigger_block2()
{
puts("Entered api_trigger_block2()");
if (cb_block_cb2 == NULL)
{
puts("error: when triggering callback block: cb_block_cb2 is null");
return;
}
api_trigger_block();
puts("Returned from callback in api_trigger_block2()!!!!");
}
Conceptually, the above options are similar. You may be able to come up with some other options along the same lines. At this point I can't give a good technical explanation as to why the above works. As far as I can tell from experimenting, Swift functions/closures can't always be passed to C functions compiled with a C compiler and taking compatible blocks. However, it works fine if the same functions are compiled as Objective-C. A block defined in an Objective-C file can be passed to C code just fine.
For the record, I also tried using #objc and #convention(c) annotations in Swift code trying to fix this, but to no avail.
Hope this helps.

performSelector error with global function and AppDelegate class

I'm following this apple document and I'm trying to translate some of its parts in Swift language. I have this global function, with performSelector:
func RunLoopSourceScheduleRoutine(info:UnsafeMutableRawPointer? ,rl:CFRunLoop? , mode:CFRunLoopMode?) {
let obj : RunLoopSource = Unmanaged<RunLoopSource>.fromOpaque(info!).takeUnretainedValue()
let del = UIApplication.shared
let theContext = RunLoopContext(withSource: obj, andLoop: rl!)
del.performSelector(onMainThread:#selector(AppDelegate.registerSource) , with: theContext, waitUntilDone: false)
}
And AppDelegate class, in this class there are: methods that automatically adds Xcode in the normal routine of project creation (didFinishLaunchingWithOptions, applicationWillResignActive, etc) I added the sourcesToPing parameter and the registerSource() method:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var sourcesToPing : [RunLoopContext] = Array()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func registerSource(sourceInfo:RunLoopContext) {
sourcesToPing.append(sourceInfo)
}
}
but the compiler get the following error , in RunLoopSourceScheduleRoutine() function:
argument '#selector' refers to instance method 'registerSources(source Info:)' that is not exposed to Objective-C
what is the problem ? and how does it solve?
PerformSelector is an Objective-C method that predates GCD (Grand Central Dispatch). It should be possible to do it that way, but selectors are not type-safe and are awkward to use.
I'm not sure what's wrong with your current code. As Martin points out in his comment, the error you're reporting is complaining about a method called registerSources() but you show code for a method called registerSource() (with no final "e".) If you want to get that code working you need to get to the bottom of that discrepency.
Instead, why not use GCD code like this:
dispatchQueue.main.async() {
registerSource(theContext)
}
That will accomplish the same goal but using the more modern GCD

Parse.com subclassing in Swift 2

My question is about a bridge header that does not seem to work in Swift 2. I copied this code strait from the Parse.com iOS guide into xCode to see if it would work.
#import <Parse/PFObject+Subclass.h>
class Armor : PFObject, PFSubclassing {
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Armor"
}
}
This doesn't work. I get an error on the #import <Parse/PFObject+Subclass.h> line with the error Consecutive statements of a line must be separated by ';'. So, my question is how I would go about subclassing in Parse.com with Swift 2. I have looked around the internet and haven't found anything. I think that there may have been a change in how Swift imports bridge headers, but I am not at all sure because I have never used a bridge header before. So, I could be doing something idiotic. Anyway, any help is greatly appreciated. Thanks.
First of all make sure you have the latest Parse SDK. If you have the latest SDK you can create a PFObject Subclass like this;
class Armor: PFObject, PFSubclassing {
static func parseClassName() -> String {
return "Armor"
}
}
When you create your custom Subclass, you should register your subclass to Parse SDK in AppDelegate like this;
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Registering SubClasses
Armor.registerSubclass()
// Initialize Parse.
Parse.enableLocalDatastore()
Parse.setApplicationId("Your API Key", clientKey: "Your Client Key")
return true
}
Post SDK release 1.14.0 you should not need to register subclasses.
See the changelog from https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/releases/tag/1.14.0.
There is also discussion of this issues in #1023 and 1035 where removing the calls to registerSubclass() has resolved looping problems in PFUser.

Passing dictionary objects to Objective C protocol in Swift

I'm trying to pass a dictionary object to an Objective C protocol using swift.
the protocol code snippet is as follows:
#protocol MessageDelegate
- (void)handleNewMessageArrived:(NSDictionary *)messageContent;
#end
and this is the swift class the implements the protocol:
class ViewController: UIViewController, MessageDelegate
{
...
func handleNewMessageArrived(messageContent : NSDictionary!)
{
...
}
}
But the build fails, and the error I get is:
"the type 'ViewController' does not conform to protocol 'MessageDelegate"
I looked at this SO Question but it deals with a specific object type.
is there an error in the way I declare\implement the delegate method? or in the way I assume the arguments are mapped in swift?
I'm new to Swift so any Help will be much appreciated.
Try implementing the method in your Swift class like this:
func handleNewMessageArrived(messageContent: [NSObject : AnyObject]!) {
// Handle the message
}
In case of Swift 3, this is what you will need
func handleNewMessageArrived(messageContent: [AnyHashable : Any]!) {
// Handle the message
}

Resources