How do I cast an __NSMallocBlock__ to its underlying type in Swift 3? - swift

I had a trick to help test UIAlertController that worked in Swift 2.x:
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButtonAtIndex(index: Int) {
let block = actions[index].valueForKey("handler")
let handler = unsafeBitCast(block, AlertHandler.self)
handler(actions[index])
}
}
This fails under Swift 3.x with fatal error: can't unsafeBitCast between types of different sizes, which tempts me to believe there might be a way to make the cast work. Can anyone figure it out?

Found a solution that works in Swift 3.0.1
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
if let block = actions[index].value(forKey: "handler") {
let blockPtr = UnsafeRawPointer(Unmanaged<AnyObject>.passUnretained(block as AnyObject).toOpaque())
let handler = unsafeBitCast(blockPtr, to: AlertHandler.self)
handler(actions[index])
}
}
}
(Originally, the block value was the actual block, not a pointer to the block—which you obviously can't cast to a pointer to AlertHandler)

My answer is based on #Robert Atkins's, but shorter.
The problem here is that, valueForKey returns a Any typed object, and because in Swift,
MemoryLayout<Any>.size == 32
MemoryLayout<AnyObjcBlockType>.size == 8
an assertion will be triggered in unsafeBitCast when casting between types of different sizes.
One work-around is to create an intermediate wrapper and transform back to raw pointer, which satisfies MemoryLayout<UnsafeRawPointer>.size == 8.
A much simpler way is to create an indirect reference directly using protocol AnyObject, relying on the fact that MemoryLayout<AnyObject >.size == 8, we can write following valid code:
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
if let block = actions[index].value(forKey: "handler") {
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}

If your UIAlertController is an action sheet you can modify Robert's answer to dismiss the UIAlertController before you executed the handler.
dismiss(animated: true, completion: {() in handler(self.actions[index])})
I was using this extension for testing and without this modification my assertions for presented view controller were failing.

Related

Interesting confusion assigning an optional protocol method to a variable

Let's say I have a UITextField. I want to check if its delegate is set, and if so, check to see if a specific optional method is implemented. If it is implemented, then I need to call it, otherwise perform a fallback action. A common way to do this is:
let field = // some UITextField instance
if let delegate = field.delegate, delegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) {
if delegate.textField?(field, shouldChangeCharactersIn: someRange, replacementString: someString) == true {
// do something here
}
} else {
// Fallback action
}
That is all working. But then I had an urge to try a different approach. Instead of using responds(to:) I want to assign the optional delegate method to a variable. I came up with the following which actually works:
Note: The following code requires a deployment target of iOS 15.0 to compile. If you set the target to iOS 16.0 then it doesn't compile. Not sure why.
if let delegate = field.delegate, let should = delegate.textField {
if should(field, field.selectedRange, title) {
// do something here
}
} else {
// Fallback action
}
While this works I'm really confused why it works.
The let should = delegate.textField part makes no reference to the method's parameters.
The UITextFieldDelegate protocol has 4 optional methods that start with textField. These are:
func textField(UITextField, shouldChangeCharactersIn: NSRange, replacementString: String) -> Bool
func textField(UITextField, editMenuForCharactersIn: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu?
func textField(UITextField, willDismissEditMenuWith: UIEditMenuInteractionAnimating)
func textField(UITextField, willPresentEditMenuWith: UIEditMenuInteractionAnimating)
So how does this work? How does the compiler know which one I meant? It actually seems to only work with the one that happens to be first.
I can't find any way to do this for the other 3 delegate methods. If I really want to call the func textField(UITextField, editMenuForCharactersIn: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? method I can't see how. The following code will not compile:
if let delegate = field.delegate, let editMenu = delegate.textField {
let suggested = [UIMenuElement]()
if let menu = editMenu(field, field.selectedRange, suggested) {
}
}
This gives the error:
Cannot convert value of type '[UIMenuElement]' to expected argument type 'String'
on the if let menu = should(field, field.selectedRange, suggested) { line. This clearly indicates it is assuming the func textField(UITextField, shouldChangeCharactersIn: NSRange, replacementString: String) -> Bool method.
Is there a syntax (that I'm missing) that allows me to assign the specific protocol method to a variable?
I'm going to stick with the tried and true use of responds(to:) for now but I'd love an explanation of what's going on with the attempts to assign the ambiguously named protocol method to a variable and if there is a way to specify the parameters to get the correct assignment.
My searching on SO didn't yield any relevant questions/answers.
My code is in an iOS project with a deployment target of iOS 15.0 and later using Xcode 14.0.1. The Swift compiler setting is set for Swift 5. It seems the code doesn't compile with a deployment target of iOS 16.0. Strange.
You can disambiguate either by strongly typing or spelling out the parameters. Neither of us knows why your code is compiling without disambiguating. It doesn't work for us.
if
let shouldChangeCharacters: (_, _, String) -> _ = field.delegate?.textField,
let editMenu: (_, _, Array) -> _ = field.delegate?.textField,
let willDismissEditMenu = field.delegate?.textField(_:willDismissEditMenuWith:),
let willPresentEditMenu = field.delegate?.textField(_:willPresentEditMenuWith:)
{ }
I'm not sure about the actual answer to your question, but I will say that your working code doesn't compile for me if I am targeting iOS 16. I get an error that says:
error: ambiguous use of 'textField'
if let delegate = textField.delegate, let should = delegate.textField {
UIKit.UITextFieldDelegate:13:19: note: found this candidate
optional func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
UIKit.UITextFieldDelegate:23:19: note: found this candidate
optional func textField(_ textField: UITextField, editMenuForCharactersIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu?
But instead of holding onto the function with your should variable, why not unwrap the result as a part of your if statement?
Something like this:
if let delegate = textField.delegate,
let should = delegate.textField?(field, shouldChangeCharactersIn: someRange, replacementString: someString) {
if should {
// perform some action
}
} else {
// perform fallback action
}
The resulting logic should be the same.

Trying to call selector to static function in swift

I'm trying to achieve the following but am running into issues :-)
create a protocol that UIViewController and UIView subclass can adopt
which contain one static method to be called on this class (call it
configuration
I then want to use the objectiveC runtime to find the classes that adopt this protocol
On each of those class I want to call the configuration method
The configuration method is to return a dictionary (key: a description string, value: a selector to be called on the class)
So far I was able to create the protocol, find the class implementing the protocol but i'm running into compiling issues.
Here is the protocol
#objc public protocol MazeProtocol: NSObjectProtocol{
#objc static func configurations() -> NSDictionary
}
Here is the extension to adopt the protocol on one of my class:
extension MapCoordinatorViewController: MazeProtocol {
static func configurations() -> NSDictionary {
let returnValue = NSMutableDictionary()
returnValue.setObject(#selector(test), forKey: "test" as NSString)
return returnValue
}
#objc static func test() {
print("test")
}}
and here is the code i'm using to try to call the selector returned from the configuration method:
let selectorKey = controllerClass.configurations().allKeys[indexPath.row]
let selector = controllerClass.configurations().object(forKey: selectorKey)
controllerClass.performSelector(selector) <================ error here
ControllerClass is declared as let controllerClass: MazeProtocol.Type
I get the following compile warning:
Instance member 'performSelector' cannot be used on type 'MazeProtocol'
What am I missing?
You can technically force this to work. Please don't. This is horrible Swift. To get this to work, you have to undermine everything Swift is trying to do. But yes, with warnings, you can technically get this to compile and work. Please, please don't.
First, you need to make selector be a Selector. You're using an NSDictionary, which is terrible in Swift, and so you get Any? back. But, yes, you can as! cast it into what you want:
let selector = controllerClass.configurations().object(forKey: selectorKey) as! Selector
And then, defying all the type gods, you can just declare that classes are actually NSObjectProtocol, because why not?
(controllerClass as! NSObjectProtocol).perform(selector)
This will throw a warning "Cast from 'MapCoordinatorViewController.Type' to unrelated type 'NSObjectProtocol' always fails", but it will in fact succeed.
After all that "don't do this," how should you do this? With closures.
public protocol MazeProtocol {
static var configurations: [String: () -> Void] { get }
}
class MapCoordinatorViewController: UIViewController {}
extension MapCoordinatorViewController: MazeProtocol {
static let configurations: [String: () -> Void] = [
"test": test
]
static func test() {
print("test")
}
}
let controllerClass = MapCoordinatorViewController.self
let method = controllerClass.configurations["test"]!
method()

When to use Never [duplicate]

I read the code block enclosed in curly braces after the keyword else in the context of a guard-else flow, must call a function marked with the noreturn attribute or transfer control using return, break, continue or throw.
The last part is quite clear, while I don't understand well the first.
First of all, any function returns something (an empty tuple at least) even if you don't declare any return type. Secondly, when can we use a noreturn function? Are the docs suggesting some core, built-in methods are marked with noreturn?
The else clause of a guard statement is required, and must either call
a function marked with the noreturn attribute or transfer program
control outside the guard statement’s enclosing scope using one of the
following statements:
return
break
continue
throw
Here is the source.
First of all, any function returns something (an empty tuple at least) even if you don't declare any return type.
(#noreturn is obsolete; see Swift 3 Update below.)
No, there are functions which terminate the process immediately
and do not return to the caller. These are marked in Swift
with #noreturn, such as
#noreturn public func fatalError(#autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
#noreturn public func preconditionFailure(#autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
#noreturn public func abort()
#noreturn public func exit(_: Int32)
and there may be more.
(Remark: Similar annotations exist in other programming languages
or compilers, such as [[noreturn]] in C++11, __attribute__((noreturn)) as a GCC extension, or _Noreturn for the
Clang compiler.)
You can mark your own function with #noreturn if it also terminates
the process unconditionally, e.g. by calling one of the built-in functions, such as
#noreturn func myFatalError() {
// Do something else and then ...
fatalError("Something went wrong!")
}
Now you can use your function in the else clause of a guard statement:
guard let n = Int("1234") else { myFatalError() }
#noreturn functions can also be used to mark cases that "should not
occur" and indicate a programming error. A simple example
(an extract from Missing return UITableViewCell):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyTableViewCell
switch (indexPath.row) {
case 0:
cell = tableView.dequeueReusableCellWithIdentifier("cell0", forIndexPath: indexPath) as! MyTableViewCell
cell.backgroundColor = UIColor.greenColor()
case 1:
cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! MyTableViewCell
cell.backgroundColor = UIColor.redColor()
default:
myFatalError()
}
// Setup other cell properties ...
return cell
}
Without myFatalError() marked as #noreturn, the compiler would
complain about a missing return in the default case.
Update: In Swift 3 (Xcode 8 beta 6) the #noreturn attribute
has been replaced by a Never return type, so the above example
would now be written as
func myFatalError() -> Never {
// Do something else and then ...
fatalError("Something went wrong!")
}
simple playground to see how it works ...
//: Playground - noun: a place where people can play
import Foundation
#noreturn func foo() {
print("foo")
exit(1)
}
var i: Int?
guard let i = i else {
foo()
}
print("after foo") // this line will never executed
//prints foo and finish
For instance, consider an assync operation that return either a value or error in result type. We usually write this as follows.
enum Result<Value, Error> {
case success(Value)
case failure(Error)
}
func fetch(_ request: URLRequest,
completion: (Result<(URLResponse, Data), Error>) -> Void) {
// ...
}
Now, if you know a function that always return value, never an error, then we could write :
func alwaysSucceeds(_ completion: (Result<String, Never>) -> Void) {
completion(.success("yes!"))
}
When compiler see Never, it won't force you to write all the switch cases to be exhaustive, so you can skip .failure as follows.
alwaysSucceeds { (result) in
switch result {
case .success(let string):
print(string)
}
}
Ref : https://nshipster.com/never/

Using perform selector function of NSObjectProtocol crash in Swift 3

I called perform selector function of NSObjectProtocol and try to get return value, crash why? Does anyone can help me. thx.
class Convert: NSObject {
func value(_ value: CGFloat, from srcUnit: DWUnitType, to dstUnit: DWUnitType) {
let selector = Selector("_centimeterToMillimeter:")
let newValue = perform(selector, with: value).takeUnretainedValue() as? CGFloat
print(newValue)
}
func _centimeterToMillimeter(_ value: CGFloat) -> CGFloat {
return value * 10.0
}
}
Not sure whether the bridging of CGFloat to Objective-C will work (possibly this is why you are crashing). Try this
func _centimeterToMillimeter(_ value: Any) -> Any {
return (value as! CGFloat) * 10.0
}
instead for your conversion function (or something to this effect). Just as a general comment, using the runtime (e.g. perform:_:with) your are abandoning the comfort of compile time checks and stepping into a world of hard to debug crashes. Better know what you are doing.

Create a CFRunLoopSourceRef using IOPSNotificationCreateRunLoopSource in Swift

I am trying to subscribe to changes in power state on macOS. I discovered there is a way using IOKit, though it is a bit convoluted. I need to import it using #import <IOKit/ps/IOPowerSources.h> in an ObjC Bridging header. Then I get access to the function IOPSNotificationCreateRunLoopSource, which has the signature:
IOPSNotificationCreateRunLoopSource(_ callback: IOPowerSourceCallbackType!, _ context: UnsafeMutablePointer<Void>!) -> Unmanaged<CFRunLoopSource>!
I got some help from the answer in Callback method to Apple run loop, but still doesn't manage to create a function of type IOPowerSourceCallbackType in Swift. What is the missing piece to have this compile?
The issue is that IOPowerSourceCallbackType is a C function.
According to Apple's documentation these functions are available as closures:
C function pointers are imported into Swift as closures with C function pointer calling convention
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-ID148
So the easiest way is to use a closure:
IOPSNotificationCreateRunLoopSource({ (context: UnsafeMutableRawPointer?) in
debugPrint("Power source changed")
}, &context)
A second option is to use a top-level function:
func powerSourceChanged(arg: UnsafeMutableRawPointer?) {
debugPrint("Power source changed")
}
IOPSNotificationCreateRunLoopSource(powerSourceChanged, &context)
For reference the complete implementation of how I'm using this:
class WindowController: NSWindowController {
static var context = 0
override func windowDidLoad() {
super.windowDidLoad()
let loop: CFRunLoopSource = IOPSNotificationCreateRunLoopSource({ (context: UnsafeMutableRawPointer?) in
debugPrint("Power source changed")
}, &WindowController.context).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
}
UPDATE
To let it interact with the instance the loop was setup from, you have to pass self as context, however self isn't a pointer.
When you try to pass self as pointer by prepending it with & (&self), you'll get an error that self is immutable.
To convert it a to an opaque pointer you can use the Unmanaged class:
let opaque = Unmanaged.passRetained(self).toOpaque()
Which then can be used as an UnsafeMutableRawPointer:
let context = UnsafeMutableRawPointer(opaque)
What we can use as the context for IOPSNotificationCreateRunLoopSource.
And then in the callback, by using the Unmanaged class again, we can resolve this pointer back to its initiating instance:
let opaque = Unmanaged<WindowController>.fromOpaque(context!)
let _self = opaque.takeRetainedValue()
Full example:
func PowerSourceChanged(context: UnsafeMutableRawPointer?) {
let opaque = Unmanaged<WindowController>.fromOpaque(context!)
let _self = opaque.takeRetainedValue()
_self.powerSourceChanged()
}
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
let opaque = Unmanaged.passRetained(self).toOpaque()
let context = UnsafeMutableRawPointer(opaque)
let loop: CFRunLoopSource = IOPSNotificationCreateRunLoopSource(
PowerSourceChanged,
context
).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
func powerSourceChanged() {
debugLog("Power source changed")
}
}
Bonus
A related article about CFunction pointers