Protocol Extension on an Objc Protocol

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



Leave a reply



Submit