I'm a swift beginner, so be gentle...
I'm having trouble assigning a function as a parameter.
I have defined this struct:
struct dispatchItem {
let description: String
let f: ()->Void
init(description: String, f: #escaping ()->()) {
self.description = description
self.f = f
}
}
I make use of this within a class called MasterDispatchController like so:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: testStaticTable),
dispatchItem(description: "Editable Table", f: testEditableTable)
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
etc.
Then I have a table view in my code that dispatches out to whichever function was clicked on (there are more than just the two I showed in the code above, but that's unimportant) like so
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f()
}
So... The compiler is not happy with this. It says when I am defining the dispatchItems let statement:
Cannot convert value of type '(MasterDispatchController) -> () -> ()' to expected argument type '() -> ()'
I figure... ok... I'm not sure I exactly understand this, but it seems like the compiler wants to know what class the callback functions are going to come from. I can see why it might need that. So I kind of blindly follow the pattern the compiler gives me, and change my struct to:
struct dispatchItem {
let description: String
let f: (MasterDispatchController)->()->Void
init(description: String, f: #escaping (MasterDispatchController)->()->()) {
self.description = description
self.f = f
}
}
Great the compiler is happy there, but now when I try to call the function with dispatchItems[indexPath.row].f() it says:
Missing parameter #1 in call
The function has no parameters, so I got confused...
I thought maybe it was asking me for the instance of the object in question which would make some sense... that would be "self" in my example, so I tried dispatchItems[indexPath.row].f(self) but then I got an error:
Expression resolves to an unused function
So I'm kind of stuck.
Sorry if this is a stupid question. Thanks for your help.
The problem is that you're trying to refer to the instance methods testStaticTable and testEditableTable in your instance property's initialiser before self is fully initialised. Therefore the compiler cannot partially apply these methods with self as the implicit parameter, but can instead only offer you the curried versions – of type (MasterDispatchController) -> () -> ().
One might be tempted then to mark the dispatchItems property as lazy, so that the property initialiser runs on the first access of the property, when self is fully initialised.
class MasterDispatchController : UITableViewController {
lazy private(set) var dispatchItems: [DispatchItem] = [
DispatchItem(description: "Static Table", f: self.testStaticTable),
DispatchItem(description: "Editable Table", f: self.testEditableTable)
]
// ...
}
(Note that I renamed your struct to conform to Swift naming conventions)
This now compiles, as you now can refer to the partially applied versions of the methods (i.e of type () -> Void), and can call them as:
dispatchItems[indexPath.row].f()
However, you now have a retain cycle, because you're storing closures on self which are strongly capturing self. This is because when used as a value, self.someInstanceMethod resolves to a partially applied closure that strongly captures self.
One solution to this, which you were already close to achieving, is to instead work with the curried versions of the methods – which don't strongly capture self, but instead have to be applied with a given instance to operate on.
struct DispatchItem<Target> {
let description: String
let f: (Target) -> () -> Void
init(description: String, f: #escaping (Target) -> () -> Void) {
self.description = description
self.f = f
}
}
class MasterDispatchController : UITableViewController {
let dispatchItems = [
DispatchItem(description: "Static Table", f: testStaticTable),
DispatchItem(description: "Editable Table", f: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f(self)()
}
func testEditableTable() {}
func testStaticTable() {}
}
These functions now take a given instance of MasterDispatchController as a parameter, and give you back the correct instance method to call for that given instance. Therefore, you need to first apply them with self, by saying f(self) in order to get the instance method to call, and then call the resultant function with ().
Although it may be inconvenient constantly applying these functions with self (or you may not even have access to self). A more general solution would be to store self as a weak property on DispatchItem, along with the curried function – then you can apply it 'on-demand':
struct DispatchItem<Target : AnyObject> {
let description: String
private let _action: (Target) -> () -> Void
weak var target: Target?
init(description: String, target: Target, action: #escaping (Target) -> () -> Void) {
self.description = description
self._action = action
}
func action() {
// if we still have a reference to the target (it hasn't been deallocated),
// get the reference, and pass it into _action, giving us the instance
// method to call, which we then do with ().
if let target = target {
_action(target)()
}
}
}
class MasterDispatchController : UITableViewController {
// note that we've made the property lazy again so we can access 'self' when
// the property is first accessed, after it has been fully initialised.
lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
DispatchItem(description: "Static Table", target: self, action: testStaticTable),
DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].action()
}
func testEditableTable() {}
func testStaticTable() {}
}
This ensures that you have no retain cycles, as DispatchItem doesn't have a strong reference to self.
Of course, you may be able to use unowned references to self, such as shown in this Q&A. However, you should only do so if you can guarantee that your DispatchItem instances don't outlive self (you would want to make dispatchItems a private property for one).
The problem here is that Swift treats class methods and functions differently under the hood. Class methods get a hidden self parameter (similar to how it works in Python), which allows them to know on which class instance they were called. That's why even though you declared testEditableCode as () -> (), the actual function has type (MasterDispatchController) -> () -> (). It needs to know on which object instance it was called.
The correct way to do what you're trying to do would be to create a closure which calls the correct method, like such:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: {() in
self.testStaticTable()
}),
dispatchItem(description: "Editable Table", f: {() in
self.testEditableTable()
})
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
If you're familiar with JavaScript, the {() in ...code...} notation is the same as function() { ...code... } or the same as lambda: ...code... in Python.
Here is the way to achieve what you want.
Implement protocol with parameters you want:
protocol Testable {
func perfromAction()
var description: String { get set }
weak var viewController: YourViewController? { get set } //lets assume it is fine for testing
}
Accessing your UIViewController like this is not quite right, but for now it is ok. You can access labels, segues, etc.
Create Class for each test you want:
class TestA: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
class TestB: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
You can add some custom parameters for each Class, but those 3 from protocol are required.
Then your UIViewController would be like
class YourViewController: UIViewController {
var arrayOfTest: [Testable] = []
override func viewDidLoad() {
super.viewDidLoad()
arrayOfTest.append(TestA(viewController: self, description: "testA"))
arrayOfTest.append(TestB(viewController: self, description: "testB"))
arrayOfTest[0].perfromAction()
arrayOfTest[1].perfromAction()
}
func testCallback(description: String) {
print("I am called from \(description)")
}
}
Related
I'm new to Swift and I'm trying to rewrite a callback as delegation with typealias and I am lost :(
Here is my code:
protocol NewNoteDelegate: class {
typealias MakeNewNote = ((String, String) -> Void)?
}
class NewNoteViewController: UIViewController {
#IBOutlet weak private var titleField: UITextField?
#IBOutlet weak private var noteField: UITextView!
weak var delegate: NewNoteDelegate?
// public var makeNewNote: ((String, String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
titleField?.becomeFirstResponder()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(didTapSave))
}
#objc func didTapSave() {
if let text = titleField?.text, !text.isEmpty, !noteField.text.isEmpty {
// makeNewNote?(text, noteField.text)
delegate?.MakeNewNote(text, noteField.text)
}
}
}
The errors are:
Cannot convert value of type 'String' to expected argument type '(String, String) -> Void'
Extra argument in call
The original optional callback definition and call is commented out. I first tried rewriting makeNewNote as a typealias without the protocol but still got the same errors.
I also tried removing ? from MakeNewNote but that yielded a new error:
Type '(String, String) -> Void' has no member 'init'
I tried a lot of googling and it's been hours. Can anyone help me figure out what's wrong or point me in the right direction? Thanks in advance.
You are confused. There's no value in defining a typealias in a protocol. You might as well make the typealias global. It just defines a type. You want your protocol to define the methods and properties that conforming objects support:
protocol NewNoteDelegate: class {
func makeNewNote(_: String, _: String)
}
That just means that any object that conforms to the NewNoteDelegate protocol must implement the makeNewNote(:,:) function.
I'm not sure what having a function return Void? even does, so I stripped that away.
Also note that having a function with two anonymous parameters is considered bad form in Swift. You should really name all the parameters (except possibly the first one). In Swift, the names let you know the purpose of each parameter.
Consider this sample code (Compiled as a Mac command line tool, but it could just as easily be a Playground. I just happen to dislike playgrounds.)
import Foundation
protocol NewNoteDelegate: class {
func makeNewNote(_: String, _: String)
}
//The Foo class has a delegate that conforms to the NewNoteDelegate protocol.
class Foo {
weak var delegate: NewNoteDelegate?
func doSomething(string1: String, string2: String) {
//Invoke our delegate, if we have one.
delegate?.makeNewNote(string1, string2)
}
}
//This class just knows how to be a NewNoteDelegate
class ADelegate: NewNoteDelegate {
func makeNewNote(_ string1: String, _ string2: String){
print("string 1 = '\(string1)', string 2 = '\(string2)'")
return
}
}
//Create a Foo object
let aFoo = Foo()
//Create an ADelegate object
let aDelegate = ADelegate()
//Make the ADelegate the Foo object's delegate
aFoo.delegate = aDelegate
//Tell our foo object to do something.
aFoo.doSomething(string1: "string 1", string2: "string 2")
That code outputs
string 1 = 'string 1', string 2 = 'string 2'
I am trying to find a way to name function type parameters for my case. To be more clear, let me share a bit of code.
class ViewController: UIViewController {
private lazy var testView: TestView = {
let view = TestView()
view.action = action
return view
}()
func action() {
//stuff
}
}
class TestView: UIView {
var action: () -> () = {}
#objc func buttonAction(_ sender: UIButton) {
action()
}
}
Code above is working well injecting UIButton actions from View to Controller but it becomes worse when there are parameters to be passed through. When I try to change the action inside TestView to,
var action: (name: String, surname: String) -> () = { _, _ in }
it does not let me to name String parameters. Afterwards I tried to change it as using tuple,
var action: ((name: String, surname: String)) -> () = { _ in }
the code inside the TestView works. However I can not match the types view.action = action in ViewController.
I am trying to do it because of two reasons. One of which is to not have any kind of logical inside View and other one is easily identify and avoid confusion the parameters inside ViewController by its given name when there are 2+ parameters that is going to be sent to the Controller.
Lastly, I know I can use delegation. But it has its own problems in my opinion. I am already have been trying to decrease Delegates in the project since I started to struggle naming the protocols.
Thanks in advance.
The swift does not allow named parameters in function declaration, so you can do like the following:
class TestView: UIView {
// var action: () -> () = {}
var action: (_ name: String , _ surname: String ) -> () = { _, _ in }
#objc func buttonAction(_ sender: UIButton) {
action("name", "surname")
}
}
I just went memory-leak hunting in the app I am working on, and noticed that the following produces a memory leak:
class SubClass {
var didCloseHandler: (() -> Void)?
}
class MainClass {
var subClass = SubClass()
func setup {
subClass.didCloseHandler = self.didCloseSubClass
}
func didCloseSubClass() {
//
}
}
This produces a retain cycle, and for good reason - didCloseHandler captures MainClass strongly, and MainClass captures SubClass strongly.
My Question: Is there a way in Swift that allows me to assign a class method to a handler without a retain cycle?
And yes, I am aware that I can do this using subClass.didCloseHandler = { [weak self] self?.didCloseSubClass() }. I'm wondering, though, if it can be done without introducing a new closure.
make a weak reference of subClass in MainClass
If you don't have strong reference to SubClass instance somewhere else - you may try wrapper like this:
func WeakPointer<T: AnyObject>(_ object: T, _ method: #escaping (T) -> () -> Void) -> (() -> Void) {
return { [weak object] in
method(object!)()
}
}
Then use it like this:
func setup() {
subClass.didCloseHandler = WeakPointer(self, MainClass.didCloseSubClass)
}
If you don't need properties from MainClass instance in didCloseSubClass implementation - you can make this method static, which will also solve your problem.
If you have strong reference to SubClass instance somewhere else and it won't be deallocated immediately - weak var subClass will do, as was already mentioned.
EDIT:
I've come up with another idea. It may look a bit more complicated, but maybe it would help.
import Foundation
class SubClass {
#objc dynamic func didCloseHandler() {
print(#function)
}
deinit {
print(" \(self) deinit")
}
}
class MainClass {
var subClass = SubClass()
func setup() {
if let implementation = class_getMethodImplementation(MainClass.self, #selector(didCloseSubClass)),
let method = class_getInstanceMethod(SubClass.self, #selector(SubClass.didCloseHandler)) {
method_setImplementation(method, implementation)
}
}
#objc func didCloseSubClass() {
print(#function)
}
deinit {
print(" \(self) deinit")
}
}
You change closure for #objc dynamic method and set it's implementation to the one from MainClass in setup().
I'm a swift beginner, so be gentle...
I'm having trouble assigning a function as a parameter.
I have defined this struct:
struct dispatchItem {
let description: String
let f: ()->Void
init(description: String, f: #escaping ()->()) {
self.description = description
self.f = f
}
}
I make use of this within a class called MasterDispatchController like so:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: testStaticTable),
dispatchItem(description: "Editable Table", f: testEditableTable)
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
etc.
Then I have a table view in my code that dispatches out to whichever function was clicked on (there are more than just the two I showed in the code above, but that's unimportant) like so
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f()
}
So... The compiler is not happy with this. It says when I am defining the dispatchItems let statement:
Cannot convert value of type '(MasterDispatchController) -> () -> ()' to expected argument type '() -> ()'
I figure... ok... I'm not sure I exactly understand this, but it seems like the compiler wants to know what class the callback functions are going to come from. I can see why it might need that. So I kind of blindly follow the pattern the compiler gives me, and change my struct to:
struct dispatchItem {
let description: String
let f: (MasterDispatchController)->()->Void
init(description: String, f: #escaping (MasterDispatchController)->()->()) {
self.description = description
self.f = f
}
}
Great the compiler is happy there, but now when I try to call the function with dispatchItems[indexPath.row].f() it says:
Missing parameter #1 in call
The function has no parameters, so I got confused...
I thought maybe it was asking me for the instance of the object in question which would make some sense... that would be "self" in my example, so I tried dispatchItems[indexPath.row].f(self) but then I got an error:
Expression resolves to an unused function
So I'm kind of stuck.
Sorry if this is a stupid question. Thanks for your help.
The problem is that you're trying to refer to the instance methods testStaticTable and testEditableTable in your instance property's initialiser before self is fully initialised. Therefore the compiler cannot partially apply these methods with self as the implicit parameter, but can instead only offer you the curried versions – of type (MasterDispatchController) -> () -> ().
One might be tempted then to mark the dispatchItems property as lazy, so that the property initialiser runs on the first access of the property, when self is fully initialised.
class MasterDispatchController : UITableViewController {
lazy private(set) var dispatchItems: [DispatchItem] = [
DispatchItem(description: "Static Table", f: self.testStaticTable),
DispatchItem(description: "Editable Table", f: self.testEditableTable)
]
// ...
}
(Note that I renamed your struct to conform to Swift naming conventions)
This now compiles, as you now can refer to the partially applied versions of the methods (i.e of type () -> Void), and can call them as:
dispatchItems[indexPath.row].f()
However, you now have a retain cycle, because you're storing closures on self which are strongly capturing self. This is because when used as a value, self.someInstanceMethod resolves to a partially applied closure that strongly captures self.
One solution to this, which you were already close to achieving, is to instead work with the curried versions of the methods – which don't strongly capture self, but instead have to be applied with a given instance to operate on.
struct DispatchItem<Target> {
let description: String
let f: (Target) -> () -> Void
init(description: String, f: #escaping (Target) -> () -> Void) {
self.description = description
self.f = f
}
}
class MasterDispatchController : UITableViewController {
let dispatchItems = [
DispatchItem(description: "Static Table", f: testStaticTable),
DispatchItem(description: "Editable Table", f: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f(self)()
}
func testEditableTable() {}
func testStaticTable() {}
}
These functions now take a given instance of MasterDispatchController as a parameter, and give you back the correct instance method to call for that given instance. Therefore, you need to first apply them with self, by saying f(self) in order to get the instance method to call, and then call the resultant function with ().
Although it may be inconvenient constantly applying these functions with self (or you may not even have access to self). A more general solution would be to store self as a weak property on DispatchItem, along with the curried function – then you can apply it 'on-demand':
struct DispatchItem<Target : AnyObject> {
let description: String
private let _action: (Target) -> () -> Void
weak var target: Target?
init(description: String, target: Target, action: #escaping (Target) -> () -> Void) {
self.description = description
self._action = action
}
func action() {
// if we still have a reference to the target (it hasn't been deallocated),
// get the reference, and pass it into _action, giving us the instance
// method to call, which we then do with ().
if let target = target {
_action(target)()
}
}
}
class MasterDispatchController : UITableViewController {
// note that we've made the property lazy again so we can access 'self' when
// the property is first accessed, after it has been fully initialised.
lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
DispatchItem(description: "Static Table", target: self, action: testStaticTable),
DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].action()
}
func testEditableTable() {}
func testStaticTable() {}
}
This ensures that you have no retain cycles, as DispatchItem doesn't have a strong reference to self.
Of course, you may be able to use unowned references to self, such as shown in this Q&A. However, you should only do so if you can guarantee that your DispatchItem instances don't outlive self (you would want to make dispatchItems a private property for one).
The problem here is that Swift treats class methods and functions differently under the hood. Class methods get a hidden self parameter (similar to how it works in Python), which allows them to know on which class instance they were called. That's why even though you declared testEditableCode as () -> (), the actual function has type (MasterDispatchController) -> () -> (). It needs to know on which object instance it was called.
The correct way to do what you're trying to do would be to create a closure which calls the correct method, like such:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: {() in
self.testStaticTable()
}),
dispatchItem(description: "Editable Table", f: {() in
self.testEditableTable()
})
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
If you're familiar with JavaScript, the {() in ...code...} notation is the same as function() { ...code... } or the same as lambda: ...code... in Python.
Here is the way to achieve what you want.
Implement protocol with parameters you want:
protocol Testable {
func perfromAction()
var description: String { get set }
weak var viewController: YourViewController? { get set } //lets assume it is fine for testing
}
Accessing your UIViewController like this is not quite right, but for now it is ok. You can access labels, segues, etc.
Create Class for each test you want:
class TestA: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
class TestB: Testable {
var description: String
weak var viewController: YourViewController?
func perfromAction() {
print(description) //do something
viewController?.testCallback(description: description) //accessing your UIViewController
}
init(viewController: YourViewController, description: String) {
self.viewController = viewController
self.description = description
}
}
You can add some custom parameters for each Class, but those 3 from protocol are required.
Then your UIViewController would be like
class YourViewController: UIViewController {
var arrayOfTest: [Testable] = []
override func viewDidLoad() {
super.viewDidLoad()
arrayOfTest.append(TestA(viewController: self, description: "testA"))
arrayOfTest.append(TestB(viewController: self, description: "testB"))
arrayOfTest[0].perfromAction()
arrayOfTest[1].perfromAction()
}
func testCallback(description: String) {
print("I am called from \(description)")
}
}
I have a data model for a UITableViewCell that looks like this:
class SettingsContentRow {
var title: String
var cellType: Type // How do i do this?
var action:((sender: UITableViewCell.Type) -> ())?
var identifier: String {
get { return NSStringFromClass(cellType) }
}
init(title: String, cellType: Type) {
self.title = title
self.cellType= cellType
}
}
The idea is to put these in an array to facilitate building a settings view using a UITableViewController, and when requesting a cell i can just query the model for both the identifier and the cell Type. But i cannot figure out what keyword to use instead of Type. I have tried Type, AnyClass, UITableViewCell.Type and they all give rise to type assignment errors when i try to instantiate the model class.
The syntax you want is UITableViewCell.Type. This is the type of something that is a subclass of UITableViewCell. You can accept the type of any class using AnyClass, but you should usually avoid that. Most of the time, if you think you want AnyClass, you really want a generic.
When you try to pass your type to this init, it'll be something like:
SettingsContentRow("title", cellType: MyCell.self)
Referring to types directly is a little uncommon, so Swift requires that you be explicit by adding .self to it.
You may in fact want a generic here anyway. I'd probably write it this way:
final class SettingsContentRow<Cell: UITableViewCell> {
typealias Action = (Cell) -> ()
let title: String
let action: Action?
var identifier: String {
get { return NSStringFromClass(Cell.self) }
}
init(title: String, action: Action?) {
self.title = title
self.action = action
}
}
class MyCell: UITableViewCell {}
let row = SettingsContentRow(title: "Title", action: { (sender: MyCell) in } )