Macros with return value - objective-c

I'm trying to reproduce this pattern in Swift
#define mustBeKindOfClassFailedReturn(object, objectClass, ret) \
if(![object isKindOfClass:objectClass]) { \
NSLog(([NSString stringWithFormat:#"%# must be kind of %# class, current class is %#", object, NSStringFromClass(objectClass), NSStringFromClass([object class])])) \
return ret; }
used like this
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
insetForSectionAtIndex:(NSInteger)section {
mustNotBeNilFailedReturn(self.adapter, UIEdgeInsetsZero)
mustBeKindOfClassFailedReturn(self.adapter, [WBCollectionViewSectionAdapter class], UIEdgeInsetsZero)
Does anyone know a good solution to this?
Edit
I know about guard but using guard i have to rewrite a lot of code each time i'll have to override a method in my subclasses for exemple
/**
* mustOverride
*/
#define mustOverride \
{ NSLog(#"You must override this function") }
#define mustOverrideFailedReturn(ret) \
{ mustOverride \
return ret; }
EDIT 2
I've ended with solution is it the optimal one ?
func needOverride(function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) -> Bool {
REVLogManager.SharedInstance.logErrorMessage("You must override this function", function: function, file: file, line: line, exception: nil, error: nil)
return false
}
func doesObject(function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__, matchingObject: AnyObject!, matchesClass matchingClass: AnyClass) -> Bool {
guard matchingObject.isKindOfClass(matchingClass) else {
let message = "\(matchingObject) must be kind of \(matchingClass) class, current class is \(matchingObject.dynamicType)"
REVLogManager.SharedInstance.logErrorMessage(message, function: function, file: file, line: line, exception: nil, error: nil)
return false
}
return true
}
I'm calling the method like this
public func actualScrollOffsetDistanceWithScrollView(scrollView: UIScrollView!) -> Float {
guard needOverride() else { return 0.0 }
return 0.0
}
And
guard doesObject(matchingObject: self, matchesClass: REVListSectionAdapter.classForCoder()) else { return }

Swift doesn't have a macro capability of this kind.
In terms of accomplishing your goal. The pattern you are following would most correctly be handled using guard statements. A function containing a guard statement must exit the enclosing scope (or trap) and may do so with a return.
Please check the Swift documentation for information about Control Flow and Early Exit control

My answer follows the same legacy pattern using in Swift. You can use classForCoder, but I prefer dynamicType for type checking.
func mustBeKindOfClassFailedReturn(object: AnyObject, objectClass: AnyClass, returnVal: Any?) {
if !object.isKindOfClass(objectClass) {
print("\(object) must be kind of \(objectClass) class, current class is \(object.dynamicType)")
return returnVal
}
return nil // or UIEdgeInsetsZero, or whatever
}
You may also get similar results with !(object is objectClass), but things get tricky when comparing certain number types. See Swift type inference and type checking issue
You can also replace AnyObject and Any with NSObject if you know you're dealing with Objective-C types. From your comment it seems like ret could be a value type, so that's why I make it an Any type.

Swift has a better solution for this kind of problem, the guard statement. In this case, just put this in your method.
guard let adapter = self.adapter as? WBCollectionViewSectionAdapter else {
/* return value if the adapter is not setup properly. */
return .Zero
}
/* continue with valid and correctly classed adapter */
If you're looking for a way to handle this for sub classes, you may want to consider generics. It will allow you implicit type safety without having to rewrite logic in your subclasses.

Related

Objective C Array completionHandler used in Swift

I have a completion handler in a framework written in Objective C...
This is a typedef for a block type. It takes an array of PHErrors.
typedef void (^PHBridgeSendErrorArrayCompletionHandler)(NSArray *errors);
When I try to use this in Swift, I'm doing....
anObject.aMethod(completionHandler: { (errors: [ AnyObject?]) -> () in
...rest of code
}
But I keep getting this error:
Cannot convert value of type '([AnyObject?]) -> ()' to expected argument type 'PHBridgeSendErrorArrayCompletionHandler!'
Can anyone help, I'm baffled, it looks like it should work to me.
Or better yet, you can still use your typedef as typealias.
DEFINE
typealias PHBridgeSendErrorArrayCompletionHandler = (_ errors: [Error]?) -> Void
IMPLEMENTATION
func myFunctionWithErrorCompletion(completion: PHBridgeSendErrorArrayCompletionHandler) {
// Define empty array to add errors to
var errors:[Error]?
// Do Your Logic that may store errors to array
// Completion and pass errors
completion(errors)
}
USAGE
func anotherOfMyFunctions() {
// Call the function
myFunctionWithErrorCompletion { (errors) in
if let completionErrors = errors {
// React to errors
}
}
}
anObject.aMethod(completionHandler: { (errors: [ AnyObject?]) -> () in
}
should be
anObject.aMethod() { errors in
}
In order to dig any deeper, I have to know what PHBridgeSendErrorArrayCompletionHandler is
So my friend solved this problem by simply changing AnyObject to Any
(errors: [Any]?) in
Which baffles me because all objects in an NSArray are objects! So didn't think to try Any.
Im pretty new to Swift mind
Try this..
anObject.aMethod(completionHandler: { (errors:NSArray?) -> () in
...rest of code
}

Unable to set value via protocol

In my Objective C code I had this:
if ([view conformsToProtocol:#protocol(UITextInputTraits)]) {
id<UITextInputTraits> field = view;
field.enablesReturnKeyAutomatically = YES;
}
Now I'm trying to convert that to swift, so I did this:
if var field = view as? UITextInputTraits {
field.enabledReturnKeyAutomatically = true
}
I'm getting a compiler error saying that 'field' is immutable. What's the right way to accomplish this?
The problem is caused by Swift's peculiar way of dealing with optional protocol requirements. Optional protocol properties have no setter. (I regard this as a bug in the language.) You'll have to work around it.
You can say (horrible):
switch view {
case let field as UITextField:
field.enablesReturnKeyAutomatically = true
case let field as UITextView:
field.enablesReturnKeyAutomatically = true
default: break
}
Another way (equally horrible):
let setter = #selector(setter:UITextInputTraits.enablesReturnKeyAutomatically)
if view.responds(to:setter) {
view.perform(setter, with: 1 as NSNumber)
}

Having trouble converting a simple Objective-C block to Swift

Not sure what I'm doing wrong. I'm using this module https://github.com/wenzhaot/InstagramPhotoPicker (the header file for the block is here https://github.com/wenzhaot/InstagramPhotoPicker/blob/master/TWPhotoPicker/TWPhotoPickerController.h)
In Objective-C, the syntax is:
photoPicker.cropBlock = ^(UIImage *image) {
//do something
};
I tried converting it to swift, but I'm getting an error
photoPicker.cropBlock{ (image:UIImage) -> () in
//Do something
//error: cannot invoke 'cropBlock' with an argument list of type '((UIImage) -> ())'
}
How to get it to work?
You're just missing = sign. Also image is implicitly unwrapped optional:
photoPicker.cropBlock = { (image: UIImage!) -> () in
//Do something
}
or
photoPicker.cropBlock = { image in
//Do something
}

Passing a Boolean pointer from Swift

I am trying to call an objective-C method from swift. The method signature is:
-(BOOL)getPassThroughSync:(BOOL *)enabled error:(NSError **)error;
I am not yet able to pass in a Boolean pointer. Here is what I have so far:
var passThrough: Bool?
if scanner.getPassThroughSync(&passThrough, error: nil) {
}
This does not compile due to an invalid argument list.
Similarly, I want to call
-(BOOL)getUSBChargeCurrent:(int *)current error:(NSError **)error;
requiring an int pointer.
What am I missing?
Eventually, with help from the comments on other answers, the following worked:
var passThrough: ObjCBool = false
if scanner.getPassThroughSync(&passThrough, error: nil) {
cell.detailTextLabel?.text = passThrough ? "Yes" : "No"
}
Similarly,
var current: Int32 = 0
if scanner.getUSBChargeCurrent(&current, error: nil) {
cell.detailTextLabel?.text = String(current) + "mA"
}
I think the answer you're looking for is in the Pointers section of this Apple document. Essentially, you have to declare a pointer type: Type * maps to UnsafeMutablePointer<Type>.
To access the value stored in the pointer, use its memory property.
For more details on UnsafeMutablePointer see this document.

iap - conversion from objective c to swift

I'm trying to convert my app from objective c to swift, and i have everything except for the in app purchase working.
i have the objective c helper class imported in the project, but i'm having trouble doing the RequestProductsCompletionHandler section
in the old objective c version i have the code
[[MTIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
_products = products;
if([[self appData] isPro] == FALSE)
[[self bUpgrade] setUserInteractionEnabled:TRUE];
}
}];
and i'm trying to convert this section to objective c but so far i've been unable to convert the requestProductsWithCompletionHandler part
can anyone help?
i've tried creating the completion handler the same was as in objective c using
requestProductsWithCompletionHandler(sucess: Bool, products : NSArray)
but i get the compiler errors "Extra argument 'products' in call" and "Expected member name or constructor call after type name".
if i try it without the parameters i get "Missing argument for parameter #1 in call"
In Swift, your completion handler would be a closure. The syntax is semi-similar to blocks in Objective-C but, instead of defining the parameters outside the block:
^(BOOL success, NSArray *products) { /* ... */ }
you define them inside the closure:
{ (success: Bool, products: [AnyObject]!) in /* ... */ }
So, your call to requestProductsWithCompletionHandler should look something like this:
MTIAPHelper.sharedInstance().requestProductsWithCompletionHandler {
(success: Bool, products: [AnyObject]!) in
if success {
// etc.
}
}
You can also let Swift infer the parameter types for you:
MTIAPHelper.sharedInstance().requestProductsWithCompletionHandler {
(success, products) in
if success {
// etc.
}
}