Swift closure defindet as class property cannot access other class properies - swift

When directly assigning / implementing a Swift closure it is no problem to access class properties. But when I try to define the closure as class propertie as well, access to other class properties is not possible. Why is this?
Here is an example:
While the closure directly assigned to editorVC.completionBlock can access the class property tableView without any problem, the same code within leads to an error when the closure is defined as class property editorCompletionBlock:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
// Directly assign closure - Works without any problem
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
present(editorVC, animated: true, completion: nil)
}
// Define closure as class property ==> Error
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
// ERROR: Value of type '(MyViewController) -> () -> MyViewController' has no member 'tableView'
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}

Reason:
You can't access self until the initialization process of a type is completed.
In your code, editorCompletionBlock is a stored property and you're trying to access self.tableView inside it. This is the reason it is giving compile time error.
Solution:
Instead, make editorCompletionBlock as a lazy property to get that working.
lazy var editorCompletionBlock: EditorCompletionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
}

Solution 1:
place your editorCompletionBlock in viewDidLoad(:_) and it should work like charm:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
}
present(editorVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
self.tableView.reloadData() ///should work :)
}
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}
Solution 2:
Or you can just declare your closure as lazy:
lazy var editorCompletionBlock: EditorCompletionBlock = { (result)
in
self.tableView.reloadData()
doSomething(withResult: result)
}

Related

Value of protocol type 'InheritingProtocol: Protocol' cannot conform to 'Protocol'

I have the below code which aims an abstraction -without being have to casting Decodables - for DataModels across the app. I wanted use these DataModels to centrelize them. This is how I far I came right now and I am kind of in dead end.
In this configuration, the code tells me that ProfileResponseDelegate cannot conform to ModelDelegate when ProfileResponseDelegate is a protocol, which makes sense.
protocol ModelDelegate: class {
associatedtype DataType: Decodable
func didReceive(data: DataType)
}
class Model<Type, Delegate: ModelDelegate> where Type == Delegate.DataType {
var data: Type?
weak var delegate: Delegate?
func requestData() { return }
}
protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
//throws Value of protocol type 'ProfileResponseDelegate' cannot conform to 'ModelDelegate'; only struct/enum/class types can conform to protocols
class ProfileResponseModel: Model<ProfileResponse, ProfileResponseDelegate> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
When I change ProfileResponseDelegate to be a class -without not being a delegate anymore, but anyway- the code does not allow Controller to inherit from both UIViewController and ProfileResponseDelegate reasoning a class cannot inherit from multiple classes. which again makes sense.
class ProfileResponseDelegate: ModelDelegate {
typealias DataType = ProfileResponse
func didReceive(data: ProfileResponse) {
return
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
override func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
With respect to first configuration, I could not make it work. However for the second one, when Controller just inherits from ProfileResponseDelegate it works just fine.
I have to find a way to make this work -preferably the first configuration- and need your advise. Much appreciated in advance.
UPDATE
So I have removed the associatedType from the ModelDelegate and removed ProfileResponseModel. Right now code looks like this.
protocol ModelDelegate: class {
//associatedtype DataType: Decodable
func didReceive<T: Decodable>(data: T)
}
class Model<Type: Decodable> {
var data: Type?
weak var delegate: ModelDelegate?
func requestData() { return }
}
//protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
class ProfileResponseModel: Model<ProfileResponse> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ModelDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive<T>(data: T) where T : Decodable {
//I want this `data` to come as what it is.
if let response = data as? ProfileResponse {
print(type(of: response))
}
}
}
It works likes this, however my ultimate purpose for doing this to not being have to cast the data to ProfileResponse here -and in other places to other Decodable type-.

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

Swift 4.3 - protocols passing data between 2 files

I am struggling with understanding how protocols work. I have 2 files and want to use protocol to pass data... Here's what I am doing:
In ViewController.swift
protocol workingProtocol { func myFunc(strValue: String)}
class ViewController: UIViewController {
var interactor = workingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
In Interactor.swift
class Interactor {
func myFunc(strValue: String) {
print(strValue)
}
}
The data is not printing from Interactor.swift
Unfortunately I can't see how you inject interaction class, also your code has some problem with syntax. Here is how it should look:
protocol WorkingProtocol: AnyObject {
func myFunc(strValue: String)
}
final class ViewController: UIViewController {
var interactor: WorkingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
final class Interactor: WorkingProtocol {
func myFunc(strValue: String) {
print(strValue)
}
}
And how to use:
let interactor: WorkingProtocol = Interactor()
let vc = ViewController(interactor: interactor)
vc.doneBtn()
Protocols defines a blueprint of methods, properties and other requirements that suite a piece of functionality.
This is an example about how it works based on your code
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController {
var interactor: ProtocolName? = nil
#objc
fileprivate func doneBtn() {
interactor?.functionName(strValue: "Passing data to interactor using protocols")
}
}
class Interactor: ProtocolName {
func functionName(strValue: String) {
print("Showing value\n", strValue)
}
}
let interactor = Interactor()
let viewController = ViewController()
viewController.interactor = interactor
viewController.doneBtn()
Another example:
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController1 {
let interactor = Interactor1()
/// Init or viewDidLoad() if you're using ViewController classes.
init() {
interactor.delegate = self
}
}
extension ViewController1: ProtocolName {
func functionName(strValue: String) {
print("Printing the value: \(strValue)")
}
}
class Interactor1 {
var delegate: ProtocolName?
func someAction() {
delegate?.functionName(strValue: "Executed action in interactor.")
}
}
let vc = ViewController1()
vc.interactor.someAction()

Testing Delegation in Playground giving 'nil'

I have the following code in Playground -I'm learning delegation-...
import UIKit
protocol FollowThisProtocol {
func passingTheValue(aValue: String)
}
class IPassTheValues{
var aDelegate: FollowThisProtocol!
func runThisFunc(){
aDelegate.passingTheValue(aValue: "I like this game")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass.aDelegate = self
}
func passingTheValue(aValue: String) {
localString = aValue
}
}
When I attempt to
print(IReceiveTheValues().localString)
it's giving me nil
It also gives me nil if I run the following lines before attempting to print(IReceiveTheValues().localString)...
IPassTheValues()
IReceiveTheValues()
could you please help me understand why the value is not being passed from the 1st class to the 2nd..?
Or if you can spot something in my code that is contradicting itself, could you please point it out..?
Appreciate your time and help.
You need to create the IPassTheValues object before assigning yourself as the delegate, and then call runThisFunc() on the instance:
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
Then test:
// Create the `IReceiveTheValues` object
let irtv = IReceiveTheValues()
// Run the method
irtv.runReceivefunc()
// Get the resulting string
print(irtv.localString)
I suggest 2 other changes. Make your delegate weak so that you don't get a retain cycle which makes it impossible to delete either object. In order to do that, you will need to add : class to your protocol declaration because only reference objects (instances of a class) can be weak.
Here's the modified code. Try it and see what happens when you delete weak.
protocol FollowThisProtocol: class {
func passingTheValue(aValue: String)
}
class IPassTheValues{
weak var aDelegate: FollowThisProtocol!
func runThisFunc(){
print("Calling delegate...")
aDelegate.passingTheValue(aValue: "I like this game")
}
deinit {
print("IPassTheValues deinitialized")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
func passingTheValue(aValue: String) {
print("Receiving value from helper object...")
localString = aValue
}
deinit {
print("IReceiveTheValues deinitialized")
}
}
func test() {
let irtv = IReceiveTheValues()
irtv.runReceivefunc()
print(irtv.localString)
}
test()

Is self captured within a nested function

With closures I usually append [weak self] onto my capture list and then do a null check on self:
func myInstanceMethod()
{
let myClosure =
{
[weak self] (result : Bool) in
if let this = self
{
this.anotherInstanceMethod()
}
}
functionExpectingClosure(myClosure)
}
How do I perform the null check on self if I'm using a nested function in lieu of a closure (or is the check even necessary...or is it even good practice to use a nested function like this) i.e.
func myInstanceMethod()
{
func nestedFunction(result : Bool)
{
anotherInstanceMethod()
}
functionExpectingClosure(nestedFunction)
}
Unfortunately, only Closures have "Capture List" feature like [weak self]. For nested functions, You have to use normal weak or unowned variables.
func myInstanceMethod() {
weak var _self = self
func nestedFunction(result : Bool) {
_self?.anotherInstanceMethod()
}
functionExpectingClosure(nestedFunction)
}
Does not seem to be the case anymore. This is valid in swift 4.1:
class Foo {
var increment = 0
func bar() {
func method1() {
DispatchQueue.main.async(execute: {
method2()
})
}
func method2() {
otherMethod()
increment += 1
}
method1()
}
func otherMethod() {
}
}
The question remains: How is self captured ?