how to do protocol to pass data?

Hello,


I'm a beginner with Swift and I'm struggling to understand the way the variables are send between class one and class two using protocol.


I want to create a data model for monitoring the heart rate. This data is displayed on the screen so that the user can see them. In parallel, it needs to check this data, i.e. print to the console what the label shows. I can not understand why the delegate property does not get a value (it has always nil) and why it does not print a phrase about what the user sees?


Technologies: XCODE Version 9.2, swift 4.


How can I solve this?


Thanks in advance.


CODE:


import UIKit

protocol HeartRateReceiverDelegate {
    func heartRateUpdated(to bpm: Int)
}

class HeartRateReceiver {
    var delegate: HeartRateReceiverDelegate?
    var currentHR: Int? {
        didSet {
            if let currentHR = currentHR {
                print("The most recent heart rate reading is \(currentHR).")
                delegate?.heartRateUpdated(to: currentHR)
                print("CurrentHR is \(currentHR)") // Check value
                print("Delegate is \(delegate)") // Check value
            } else {
                print("Looks like we can't pick up a heart rate.")
            }
        }
    }
    func startHeartRateMonitoringExample() {
        for _ in 1...10 {
            let randomHR = 60 + Int(arc4random_uniform(UInt32(15)))
            currentHR = randomHR
            Thread.sleep(forTimeInterval: 2)  
        }
    }
}
class HeartRateViewController: UIViewController, HeartRateReceiverDelegate {
    var heartRateLabel: UILabel = UILabel()
    func heartRateUpdated(to bpm: Int) {
        heartRateLabel.text = String(bpm)
        print("The user has been shown a heart rate of heartRateLabel.text")
    }
}

let heartRateReceiver = HeartRateReceiver()
heartRateReceiver.startHeartRateMonitoringExample()

var heartRateViewController = HeartRateViewController()

heartRateReceiver.delegate = heartRateViewController

Accepted Reply

First of all, if you want to learn iOS programming, you should remember this is not a usual way to instantiate a view controller.

var heartRateViewController = HeartRateViewController()

This may work as expected only in very limited condition.


But, in your case, you use this instance only as a delegate, so it should work if you properly set it.


Why your delegate is always nil?


Because you call `startHeartRateMonitoringExample()` before you set `delegate`.


Try changing the order of the 4 lines.

let heartRateReceiver = HeartRateReceiver()
var heartRateViewController = HeartRateViewController()
heartRateReceiver.delegate = heartRateViewController 
heartRateReceiver.startHeartRateMonitoringExample()

Your `startHeartRateMonitoringExample()` finishes after its 10 iterations of setting `currentHR`.

And in your code, `heartRateReceiver.delegate = heartRateViewController` is executed after `startHeartRateMonitoringExample()` is finished.


And you should better remember one more thing. You should never call `sleep` in the main thread in an app.

Use `Thread.sleep(forTimeInterval: 2)` only in the Playground and never use it in actual apps.


You usually use `Timer` when you want to invoke repeated tasks with interval.

Replies

Where are these 4 lines?

let heartRateReceiver = HeartRateReceiver()
heartRateReceiver.startHeartRateMonitoringExample()

var heartRateViewController = HeartRateViewController()

heartRateReceiver.delegate = heartRateViewController

(Please try to format your code using the icon `< >`.)


And you usally do not initiaize a view controller with `init()`.

var heartRateViewController = HeartRateViewController()

Are you sure this `heartRateViewController` is presented?

Your delegate seems to be nil.


Who is delegate of whom ? Are you sure HeartRateReceiver delegates to HeartRateViewController or the other way round ?

Yes, delegate is always nil. And I'm not understand why?


I wrote this code based on the specification from the Apple textbook, but I'm not sure if I wrote it correctly.

This is playground file and part of this code was already written by the authors of the textbook.


This line of code was ready in textbook.

var heartRateViewController = HeartRateViewController()


Oh, sorry. it is mistake. This is my code. I had to create an instance of the class and I wrote it as usual.


How I can to create of instance of class in this case?


These four line I wrote myself.

let heartRateReceiver = HeartRateReceiver()
heartRateReceiver.startHeartRateMonitoringExample()
var heartRateViewController = HeartRateViewController()
heartRateReceiver.delegate = heartRateViewController


let heartRateReceiver = HeartRateReceiver()
heartRateReceiver.startHeartRateMonitoringExample()

Here I had to create an instance of the class and run the function to get conditional data about the pulse.


var heartRateViewController = HeartRateViewController()


Here I had to create a second-class instance.


heartRateReceiver.delegate = heartRateViewController


And here I had to pass the data from one instance to the other.



Could you tell exactly which book ? Which page or chapter ?

Yes, of course. This is 'App Development with Swift' textbook from series 'Everyone Can Code', Lesson 4.1 'Protocols', lab 5 'App Exercise - Heart Rate Delegate'


I would like to send a link to the file with this task, but I'm not sure if this is convenient.

First of all, if you want to learn iOS programming, you should remember this is not a usual way to instantiate a view controller.

var heartRateViewController = HeartRateViewController()

This may work as expected only in very limited condition.


But, in your case, you use this instance only as a delegate, so it should work if you properly set it.


Why your delegate is always nil?


Because you call `startHeartRateMonitoringExample()` before you set `delegate`.


Try changing the order of the 4 lines.

let heartRateReceiver = HeartRateReceiver()
var heartRateViewController = HeartRateViewController()
heartRateReceiver.delegate = heartRateViewController 
heartRateReceiver.startHeartRateMonitoringExample()

Your `startHeartRateMonitoringExample()` finishes after its 10 iterations of setting `currentHR`.

And in your code, `heartRateReceiver.delegate = heartRateViewController` is executed after `startHeartRateMonitoringExample()` is finished.


And you should better remember one more thing. You should never call `sleep` in the main thread in an app.

Use `Thread.sleep(forTimeInterval: 2)` only in the Playground and never use it in actual apps.


You usually use `Timer` when you want to invoke repeated tasks with interval.

Thank you very much! It is working!!!


This is a very happy day in my life!🙂 It turns out I wrote almost everything correctly!


Thanks and for the accompanying explanations. They are very useful for me.


Could you clarify the logic of the delegate's work in this case? For example, you wrote that 'Your `startHeartRateMonitoringExample()` finishes after its 10 iterations of setting `currentHR`.

And in your code, `heartRateReceiver.delegate = heartRateViewController` is executed after `startHeartRateMonitoringExample()` is finished.'


This is hard to undestand for me. I thought that this code `heartRateReceiver.delegate = heartRateViewController` is executed after each iteration of `startHeartRateMonitoringExample()'. And where are the results of these 10 iterations stored then?


And the most difficult question for me is what is the general sequence of executing commands in this example? Why it needs to assign 'heartRateViewController' to 'heartRateReceiver.delegate'? It looks like an extra operation, because the property 'delegate' allready receives a value from 'currentHR'.

I thought that this code `heartRateReceiver.delegate = heartRateViewController` is executed after each iteration of `startHeartRateMonitoringExample()'.


Codes work as programmed, not as expected.


When you write statements sequentially, each statement in your code is executed sequentially one by one.

There's no functionality in Swift, that some following statement being executed each iteration inside a method call.


And where are the results of these 10 iterations stored then?


In your code, no where. Without explicitly storing the results, your code holds nothing.

The output generated by `print` is sent to the Playground console. And the Playground keeps the received texts and shows them in the console area. When you write an app, it may not be executed in the Playground, so nothing would store any sort of results unless you write some code explicitly to store them.


what is the general sequence of executing commands in this example?


I do not understand why you think the property 'delegate' allready receives a value from 'currentHR', as there is no line setting `delegate` in your code.


let heartRateReceiver = HeartRateReceiver()
//This line instantiates an instance of the class HeartRateReceiver,
// and initializes the variable heartRateReceiver with the instance.
//At this point, the property `delegate` is nil.

heartRateReceiver.startHeartRateMonitoringExample()
//This line invokes the execution of the method startHeartRateMonitoringExample.
// so lines inside the method are going to be executed.

->  for _ in 1...10 {
    //This line setup iteration from 1 to 10, the first value 1 is taken,
    // and starts executing the lines in the following code-block.
->      let randomHR = 60 + Int(arc4random_uniform(UInt32(15)))
        //This line generates a randome number, and initialize the local variable `randomHR` with it.
->      currentHR = randomHR
        //This line assigns the random value into the property `currentHR`,
        // and triggers the execution of didSet clause of the property `currentHR`.
----->      if let currentHR = currentHR {
            //This line tests the property `currentHR`, and as it is not nil,
            // it establishes the local variable `currentHR`
            // and initialized it with the unwrapped value of the property `currentHR`.
            //And then, starts executing the `true` code-block of the if statement.
----->          print("The most recent heart rate reading is \(currentHR).")
                //This line outputs the value of the local variable `currentHR`.
----->          delegate?.heartRateUpdated(to: currentHR)
                //This line first tests the content of the property `delegate`, as it is still nil, do nothing.
----->          print("CurrentHR is \(currentHR)") // Check value
                //The value of the local variable `currentHR` is printed again.
----->          print("Delegate is \(delegate)") // Check value
                //Outputs the value of the property `delegate`, still it is nil.
----->      } else { 
                print("Looks like we can't pick up a heart rate.")
            }
            //End of if statement, the `else` code-block is ignored.
        //And end of didSet clause, continues the execution in the for-in code-block.
->      Thread.sleep(forTimeInterval: 2)
        //Blocks the execution of codes in the main thread for 2 seconds.
    //End of the code-block of for-in statement, retrieves next value 2, starts next iteration.
->      let randomHR = 60 + Int(arc4random_uniform(UInt32(15)))
        //...

As you see, when the value of the property `delegate` is printed, there's no chance it is set to non-nil value.

Thank you so much again!


Everything is clear before line 'delegate?.heartRateUpdated(to: currentHR)'.


But what happens next? How is the second line printed into the console - 'The user has been shown a heart rate of...'?


I thought that the following happens:


delegate?.heartRateUpdated(to: currentHR)


In this line, the value of the local variable 'currentHR' is assigned as a parameter to the 'heartRateUpdated(to:)' function.


Then this function is executed in the variable 'heartRateViewController' because this variable is an instance of the class 'HeartRateViewController', where this function is declared.


On the next step this function assigns the value of the currentHR to the 'text' property of heartRateLabel.


heartRateLabel.text = String(bpm)


And here also this function takes the value of the property 'text' from the 'heartRateLabel' and prints this value into the console.


print("The user has been shown a heart rate of \(heartRateLabel.text!)")


As a result, two lines are printing in the console. For example:


The most recent heart rate reading is 72.
The user has been shown a heart rate of 72.


At this moment the first iteration of the loop of 'startHeartRateMonitoringExample' function is finished .


Then the cycle is repeated 9 more times and 9 more pairs of such messages are printed in the console.


If this sequence of executing program commands is correct, then why do we need the line 'heartRateReceiver.delegate = heartRateViewController'? This statement does not participate in the sequence of execution of the program statements that I described.

It's not clear for me why you do not understand whant happens in this line.

delegate?.heartRateUpdated(to: currentHR)


You know `?.` represents Optional Chaining, no?


Assume you have this line:

anInstance?.someMethod(with: someArg)

When executing this line, Swift first tests the content of `anInstance`, and if it is nil, it does nothing. It does not call `someMethod(with:)`.


In your code, when the execution reaches at the line, the property `delegate` is nil. Even if `heartRateViewController` at somewhere else holds an instance of `HeartRateViewController`. It is nothing to do with the property `delegate`. As the line to assign the instance to the property is not yet executed.


So, the next step is not `heartRateLabel.text = String(bpm)`, as I wrote, `heartRateUpdated(to:)` is not called as `delegate` is nil when the line is executed.


Any description based on the false assumption that `heartRateUpdated(to:)` would be executed has no meaning. It is not called.

Now I was completely confused. I understand that Swift checks the instance for a value or nil. But how are the commands actually executed, in what order?


In particular, what happens after line


if let currentHR = currentHR {


if there is no nil?

if let creates a new instance, which scope will be limited to the code inside the if let { }.

Just as if you wrote

if let newCurrentRR = currentRR { }

with a different identifier (that is what is confusing at the beginning to use the same identifier).


The test will succeed only if the instance can be created (currentRR not nil), otherwise the code is skipped.


Note: if let is not a test for equality, it is a test of possible assignment.

In case outer `currentHR` is not nil,


  1. Establishes a new local variable inner `currentHR`.
  2. Initializes the inner `currentHR` with the unwrapped value of outer `currentHR`.
  3. Start executing the true-block of if-statement.


Super clear (for me, of course).


Please try to describe what is confusing.

In this place it is clear for me too. My confuse is - what happens next when the true-block of if-statement start executing?