Примерный порядок реализации делегатов через протоколы на примере 2-х классов QuoteAPI и ProfileController (из QuoteAPI передаём дату в ProfileController).

  1. Создаём протокол в классе QuoteAPI, например:
protocol QuoteAPIDelegate: class {
    func fetchQuotes(quotesArray: [Quote])
}
  1. Далее в классе QuoteAPI создаём weak var delegate:
// Внутри QuoteAPI
class QuoteAPI {
    
    **weak var delegate: QuoteAPIDelegate?**
    
}
  1. В классе QuoteAPI используем делегат. Например, в функции performRequest():
// Внутри QuoteAPI
func performRequest(author: String) {
        
        let url = getURL(forAuthor: author)
        
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { (data, response, error) in
            
            let decoder = JSONDecoder()
            let decodedData = try! decoder.decode(QuoteData.self, from: data!)
            
            let data = decodedData
            let totalQuotes = data.count - 1
            
            for index in stride(from: 0, to: totalQuotes, by: 1) {
                self.quotes.append(Quote(quoteId: data[index].quote_id, quote: data[index].quote, author: data[index].author))
            }
            
            **self.delegate?.fetchQuotes(quotesArray: self.quotes)**
        }
        
        task.resume()
 }
  1. В другом классе (у меня это ProfileController) мы "принимаем" делегат:
// Внутри ProfileController
**var quoteAPI = QuoteAPI()**

override func viewDidLoad() {
        super.viewDidLoad()
        
        **quoteAPI.delegate = self**
                        
        configureUI()
 }
  1. После "приёма" делегата мы обращаемся к функции, которая содержит delegate?.method() в классе из которого исходит делегат.

В моём случае во viewDidLoad из класса ProfileController обращаюсь к методу performRequest() в классе QuoteAPI.

// Внутри ProfileController
**var quoteAPI = QuoteAPI()**

override func viewDidLoad() {
        super.viewDidLoad()
        
        quoteAPI.delegate = self
        **quoteAPI.performRequest(author: selectedCharacter.name)**
                        
        configureUI()
 }
  1. Используем метод из протокола (в моё примере через расширение) в классе, в котором нам нужно получить данные из делегата (в моём случае ProfileController):
// Внутри документа с классом ProfileController
extension ProfileController: **QuoteAPIDelegate** {
	func fetchQuotes(quotesArray: [Quote]) {
			DispatchQueue.main.async {
				print(quotesArray) // quotesArray - дата, которую мы получим из класса **QuoteAPI**
			}
	}
}

А теперь как работает делегат

  1. Чтобы делегат сработал, нужно обращение к функции, которая содержит delegate?.method() в классе из которого исходит делегат.

Т.е. к примеру у нас есть класс QuoteAPI внутри которого есть функция performRequest(), которая содержит делегат, данные из которого мы хотим передать на ProfileController.

Тогда в ProfileController нужно вызвать функцию performRequest(), которая содержится в классе QuoteAPI (т.к. именно в этой функции используется передача данных через делегат).

Тогда делегат срабатывает (т.е. мы как бы активируем метод на другом контроллере и с этого метода "прилетает" значение на текущий контроллер). 2. Нужно понимать, что функция в классе делегате срабатывает как бы "отдельно". Т.е. если в этой функции мы во время делегирование пытаемся передать какое-то значение которое было создано ВНЕ данной функции, то просто при делегировании получим пустоту.