Swift protocol extension in Objective-C class
Objective-C protocols cannot have default implementations.
Objective-C protocols can have real optional methods/properties, unlike Swift protocols, which only have required methods/properties. The workaround for this in Swift is the use of a default implementation, however, sadly those cannot be seen in Objective-C.
I would suggest creating a pure Swift protocol and for all Objective-C classes that want to extend this, write the conformance in Swift, then create @objc
wrapper functions in Swift that call the default protocol implementations - if it needs to be called, if it doesn't need to be called, simply ignore it.
Something along the lines of:
protocol SwiftProtocol {
func requiredFunc()
func optionalFunc()
}
extension SwiftProtocol {
func optionalFunc() {}
}
@objc extension ObjcClass: SwiftProtocol {
@objc func requiredFunc() {
print("do something")
}
// This will call the default implementation - can be omitted if you don't need to call the default implementation from Objective-C
@objc func objc_optionalFunc() {
optionalFunc()
}
}
How to use @objc protocol with optional and extensions at the same time?
I think this is not possible in swift (because of the way it bridges to @objc
protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance...
problem.
fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil
func trigger() {
closure?()
}
}
// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"
protocol CustomProtocol: class {
func setup()
}
extension CustomProtocol where Self: NSObject {
fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}
set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}
let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)
// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)
self.associatedObject = object
}
func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}
class CustomClass: NSObject, CustomProtocol {}
let instance = CustomClass()
instance.setup()
I added Self: NSObject
constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.
Protocol extension with button and selector
A workaround is adding a closure sleeve to the UIButton
instead of a target action as in shown here https://stackoverflow.com/a/41438789/5058116 and copied below for convenience.
typealias Closure = () -> ()
///
class ClosureSleeve {
let closure: Closure
init(_ closure: @escaping Closure) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping Closure) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
Then simply replace:
someButton.addTarget(self, #selector(someButtonPressed), for: .touchUpInside)
with:
someButton.addAction { [weak self] in
self?.someButtonPressed()
}
and hey presto.
Using @objc in Swift 5 protocols
I would use the closure form of notification observation rather than a selector/method:
protocol TrackScreenshot {
func registerObserver(handler: (()->Void)?)
func removeObservers()
}
extension TrackScreenshot where Self: ScreenTracking {
func registerObserver(handler: (()->Void)?) {
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { (notification) in
handler?()
}
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
}
}
Then your use is something like:
self.registerObserver { [weak self] in
guard let self = self else {
return
}
print("Screen shot")'
}
How to extend protocols / delegates in Objective-C?
The syntax for creating a protocol that implements another protocol is as such:
@protocol NewProtocol <OldProtocol>
- (void)foo;
@end
If you want to call a method in NewProtocol
on a pointer typed as OldProtocol
you can either call respondsToSelector
:
if ([object respondsToSelector:@selector(foo)])
[(id)object foo];
Or define stub methods as a category on NSObject
:
@interface NSObject (NewProtocol)
- (void)foo;
@end
@implementation NSObject (NewProtocol)
- (void)foo
{
}
@end
How does protocol extension work in Swift?
I don't think there is even a need to inherit from NSObject
if you are making a BaseClass
to be inherited by other classes.
You can simply add classTag
in the BaseClass
itself, i.e.
class BaseClass {
var classTag: String {
return String(describing: type(of: self))
}
}
class SubClass: BaseClass {
func someFunc() {
print(self.classTag)
}
}
Another option can be to use protocol
and protocol extension
and provide the default definition of classTag
, i.e.
protocol SomeProtocol {
var classTag: String { get }
}
extension SomeProtocol {
var classTag: String {
return String(describing: type(of: self))
}
}
Then, you can conform SomeProtocol
to the classes wherever required, i.e.
class SubClass: SomeProtocol {
func someFunc() {
print(self.classTag)
}
}
In any case, inheriting from NSObject
is unnecessary since you don't need any NSObject
specific functionality for that.
Related Topics
Ondelete Causing Nsrangeexception
Compare App Versions After Update Using Decimals Like 2.5.2
What Does "Get" Mean in a Protocol's Property Declaration
Declaration Is Only Valid at File Scope (Extension)
Select Multiple Rows in Tableview and Tick the Selected Ones
Find Nearest Smaller Number in Array
Swift Enum Loses Initialized Values When Set as a Property
Default Argument Not Permitted in a Tuple Type When Defining Function Type
Nsurl Found Nil While Unwraping an Optional Value
How to Cast Any to an Optional
Swift 2 Array of Tuples Not Working as in Swift 1
Storing/Passing Function Types from Swift Protocols
Transform a Swift Sequence in to Adjacent Pairs
Expandable Sections Uitableview Indexpath Swift
How to Capture Depth Data from Camera in iOS 11 and Swift 4