Swift private access control causing issues - ios

I'm working on an iOS application and I'm using swift in that. For more readability and organising functions I've used extensions in my swift file.
// MARK: Class Declaration
class PaymentView
{
// Some stuffs
}
// MARK: Lifecycle methods
extension PaymentView
{
// Overriden for adding gesture recogniser
override func awakeFromNib()
{
super.awakeFromNib()
// Causes a crash when tapped on view
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("paymentViewSelected:"))
self.addGestureRecognizer(tapGesture)
// Works correctly !!!
paymentViewSelected(tapGesture);
}
}
// MARK: Private Methods
extension PaymentView
{
private func paymentViewSelected(sender : UITapGestureRecognizer)
{
print("Method called")
}
}
My issue is when I tap on my view the application crashes with unrecognised selector error. If I remove that private access control specifier from the method it works perfectly.
My question is, I can call the paymentViewSelected: directly from the awakeFromNib regardless the private. But why it is crashing when used as a selector ?
According to Swift AccessControl Reference
Private access restricts the use of an entity to its own defining
source file. Use private access to hide the implementation details of
a specific piece of functionality.
But my class, extension, gesture all are in same file. I think I'm missing some basic key concept here. Please help me to understand the issue.

About Your point : I can call the paymentViewSelected: directly from the awakeFromNib regardless the private. But why it is crashing when used as a selector ?
Its because methods that are marked with private can accessible inside the class, but the object of class cannot call the method marked with private.
In your tapGesture, it is called using object of class automatically, once you tap on the view.
In this case your method is not available because it is marked with private, this is the reason of crash error unrecognised selector.

If you are calling a method from a selector and it is private they cannot be called because the method is called from outside. When you call paymentViewSelected() in the awakeFromNib it is called inside the class. However when it is called via Selector, it is called from outside. like object.paymentViewSelected(). You cannot call private method from outside.

Related

Why is the #objc tag needed to use a selector?

I am trying to use a selector to see if a certain protocol can perform an action. When I try it like this:
protocol Test {
func hello()
func goodBye(a: String)
}
class Tester: NSObject, Test {
override init() {}
func hello() { }
func goodBye(a: String) { }
}
let a: Test = Tester()
let result = a.responds(to: Selector("goodByeWithA:"))
In this case, result evaluates to false.
But if I add the #objc tag to the protocol, it evaluates as true.
#objc protocol Test {
func hello()
func goodBye(a: String)
}
Why is this?
On a side note, I know that it is now recommended to use the #selector syntax and to move away from using strings, but for various reasons, I have to use a string in this case.
EDIT: This only started happening once I migrated my project to Swift 4.2
By default Swift generates code that is only available to other Swift code, but if you need to interact with the Objective-C runtime – all of UIKit, for example – you need to tell Swift what to do.
That’s where the #objc attribute comes in: when you apply it to a class or method it instructs Swift to make those things available to Objective-C as well as Swift code. So, any time you want to call a method from a UIBarButtonItem or a Timer, you’ll need to mark that method using #objc so it’s exposed – both of those, and many others, are Objective-C code.
Don’t worry: if you forget to add #objc when it’s needed, your code simply won’t compile – it’s not something you can forget by accident and introduce a bug.

React-native bridge is nil when method called from Swift ViewController

I have a class to send events defined as follows:
import UIKit
#objc(NativeEventSender)
class NativeEventSender: RCTEventEmitter {
override func supportedEvents() .. {
return ["Supported Event"]
}
#objc func sendEventToJS(_ titleString: String) {
self.sendEvent(withName: "Event", body: titleString)
}
}
This class is exposed to Objective-C using RCT_EXTERN_MODULE() and RCT_EXTERN_METHOD for the sendEventToJS method.
The sendEventToJS method is called in my ViewController.swift. This ViewController is exposed to React-Native through a UIView that is returned by a view manager.
Now every time the sendEventToJS function is fired, the app crashes because the Bridge is nil, with a message saying it wasn't set. The view otherwise renders properly. I have already tried converting the Obj-C example in the documentation to Swift. Tried everything from the sending event issue on the react-native repository and it still doesn't work. Have also tried a few examples on Github where the sendEvent(withName, body) method was used. Any help is greatly appreciated!

Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization?

Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!

Swift 4 CADisplayLink without #objc [duplicate]

I'm trying to convert my project's source code from Swift 3 to Swift 4. One warning Xcode is giving me is about my selectors.
For instance, I add a target to a button using a regular selector like this:
button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)
This is the warning it shows:
Argument of '#selector' refers to instance method 'myAction()' in 'ViewController' that depends on '#objc' attribute inference deprecated in Swift 4
Add '#objc' to expose this instance method to Objective-C
Now, hitting Fix on the error message does this to my function:
// before
func myAction() { /* ... */ }
// after
#objc func myAction() { /* ... */ }
I don't really want to rename all of my functions to include the #objc mark and I'm assuming that's not necessary.
How do I rewrite the selector to deal with the deprecation?
Related question:
The use of Swift 3 #objc inference in Swift 4 mode is deprecated?
The fix-it is correct – there's nothing about the selector you can change in order to make the method it refers to exposed to Objective-C.
The whole reason for this warning in the first place is the result of SE-0160. Prior to Swift 4, internal or higher Objective-C compatible members of NSObject inheriting classes were inferred to be #objc and therefore exposed to Objective-C, therefore allowing them to be called using selectors (as the Obj-C runtime is required in order to lookup the method implementation for a given selector).
However in Swift 4, this is no longer the case. Only very specific declarations are now inferred to be #objc, for example, overrides of #objc methods, implementations of #objc protocol requirements and declarations with attributes that imply #objc, such as #IBOutlet.
The motivation behind this, as detailed in the above linked proposal, is firstly to prevent method overloads in NSObject inheriting classes from colliding with each other due to having identical selectors. Secondly, it helps reduce the binary size by not having to generate thunks for members that don't need to be exposed to Obj-C, and thirdly improves the speed of dynamic linking.
If you want to expose a member to Obj-C, you need to mark it as #objc, for example:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
}
#objc func foo() {
// ...
}
}
(the migrator should do this automatically for you with selectors when running with the "minimise inference" option selected)
To expose a group of members to Obj-C, you can use an #objc extension:
#objc extension ViewController {
// both exposed to Obj-C
func foo() {}
func bar() {}
}
This will expose all the members defined in it to Obj-C, and give an error on any members that cannot be exposed to Obj-C (unless explicitly marked as #nonobjc).
If you have a class where you need all Obj-C compatible members to be exposed to Obj-C, you can mark the class as #objcMembers:
#objcMembers
class ViewController: UIViewController {
// ...
}
Now, all members that can be inferred to be #objc will be. However, I wouldn't advise doing this unless you really need all members exposed to Obj-C, given the above mentioned downsides of having members unnecessarily exposed.
As Apple Official Documentation. you need to use #objc to call your Selector Method.
In Objective-C, a selector is a type that refers to the name of an
Objective-C method. In Swift, Objective-C selectors are represented by
the Selector structure, and can be constructed using the #selector
expression. To create a selector for a method that can be called from
Objective-C, pass the name of the method, such as
#selector(MyViewController.tappedButton(sender:)). To construct a selector for a property’s Objective-C getter or setter method, pass
the property name prefixed by the getter: or setter: label, such as
#selector(getter: MyViewController.myButton).
As of, I think Swift 4.2, all you need to do is assign #IBAction to your method and avoid the #objc annotation.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.cancel))
#IBAction func cancel()
{
self.dismiss(animated: true, completion: nil)
}
As already mentioned in other answers, there is no way to avoid the #objc annotation for selectors.
But warning mentioned in the OP can be silenced by taking following steps:
Go to Build Settings
Search for keyword #objc
Set the value of Swift 3 #objc interface to Off
below is the screenshot that illustrates the above mentioned steps:
Hope this helps
If you need objective c members in your view controller just add #objcMembers at the top of the view controller. And you can avoid this by adding IBAction in your code.
#IBAction func buttonAction() {
}
Make sure to connect this outlet in storyboard.

Swift: Unable to override operationDidFinish from GroupOperation class

In a custom subclass of GroupOperation, I'm trying to override operationDidFinish(). When I attempt to implement the function in my subclass, I get this error message:
Method does not override any method from its superclass
If I remove the override keyword, I get
Method 'operationDidFinish(:withErrors:)' with Objective-C selector
'operationDidFinish:withErrors:' conflicts with method
'operationDidFinish(:withErrors:)' from superclass 'GroupOperation'
with the same Objective-C selector
Weirdness. I'm pretty sure my method signature is spot on, and I'm not trying to overload an obj-c method, so all should be well. What gives?
For reference, my class looks like this:
class ServerAuthenticationOperation: GroupOperation {
// properties... initializer stuff...
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
print("I finished!")
}
}
I assume you're using Swift 2.
Objective-C does not support method overloading, so you have to select a different name for your method. Or, you can try these options:
Rename the method using the #objc(newMethodName:)
Use #nonobjc
Edit:
It seems working for the repo you provided, you can check it here. https://www.dropbox.com/s/hb07u3hyjhjuews/OverrideTest.zip?dl=0

Resources