Не понимаю некоторые основы программирования на swift (инициализация, делегаты, протоколы, viewController)

swift

#1

Всем привет! Я не до конца понимаю некоторые вещи, что касается инициализации, делегатов, протоколов и структур.

Я честно долго пытался самостоятельно с этим разобраться, по несколько раз пересматривал разные курсы, гуглил, писал код, перечитывал учебник, но так и не смог. Буду признателен за подробные разъяснения (потому что до этого коротких объяснений, как вы понимаете, не хватило). Я видел много примеров кода и сам могу с той или иной степенью успеха его воспроизводить, но на фундаментальном уровне у меня нет понимания, почему я это делаю.

1. Инициализация
Допустим, у нас есть следующий код:

class Car: FourWheel {
    var name: String
    var type: String
    
    init(name:String, type:String) {
        self.name = name
        self.type = type
 
        super.init()
        
        changeWheel()
    }
}

Я понимаю, что инициализаторы нужны для подготовки экземпляров класса, но в чем вообще смысл написания инициализатора, почему нельзя обойтись без него, почему нельзя просто задать значения для констант / переменных, указав их тип?

Что значит строчки типа self.name = name, почему нельзя обойтись без них?

Как мы определяем, когда нужно использовать класс, а когда структуру? (Да, я знаю, что структура – это value type, а класс – reference type). Но как на практике понимать, что нам подойдет лучше?

Когда при написании кода нужно использовать нижнее подчеркивание _ например, в этом коде:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}

2. Протоколы, делегирование
Не очень понимаю, зачем они нужны. Если это шаблон для проектирования, почему этот шаблон нельзя задать через наследование? Какое практическое применение делегатов? Что значит “подписаться на делегает” и когда нам это нужно?

3. viewController
У нас есть шаблон с кодом такого вида:

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Как понять, что нужно писать до viewDidLoad, что нужно писать внутри viewDidLoad, а что после него?


#2

Курсы от swiftbook смотрели? Там есть основы программирования на Swift.


#3

Тут 2 пути. Либо смотреть курсы самостоятельно и разбираться, либо пиши в личку. Смогу организовать платные консультации по свифту. А там зависит от твоих скиллов понимания. За час поймешь - круто. За неделю - тоже круто, но не тебе. :)))


#4

А так тоже можно?!)))


#5

когда хочешь

потому что не завезли сахар

1 и 2 вопросы читать основы ооп. что есть конструктор, что аргументы конструктора и что такое поля экземпляра класса, шаблоны проектирования и тд.
3 вопрос https://habr.com/ru/post/129557/


#6

Как “так”? Самому учится? Можно. И за час все просечь, тоже можно. Ко мне товарищ один обратился с кучей вопросов по таблицам, переходам итп. За 1.5 часа объяснил, в коде показал как все это работает. И все. Чувак пропал. Новых вопросов от него не было. ))) Видимо уже в купертино работает. :))))))))


#7

Ну вот про классы и структуры можно было бы поспорить. Но тут флуд можно развести на пару страниц.
Но нет, когда хочешь структуры и классы использовать нельзя. )))


#8

берешь и используешь. программы можно писать вообще без структур и классов. в си не было классов, но на нем все равно люди писали программы(строго говоря, там структуры можно было передавать по указателю). в java нет структур, там только примитивы передаются по значению.

когда апи требует конкретно класс или структуру - можно написать прослойку над апи и продолжать использовать что хочешь.


#9

Ну то есть надо тебе в задаче в массиве хранить миллионы объектов и часто их модифицировать и сортировать, ты берешь и фигачишь туда массив со структурами и норм.
Ладно. Закроем тему для ясности. Можно - значит можно. :slight_smile:


#10

я? извинись.⠀⠀⠀⠀⠀⠀⠀⠀


#11

Соглашусь, но у каждого “нельзя” разный :slight_smile:

Структуры иммутабельны и это прекрасно, их нужно использовать всегда. Классы нужно использовать когда: А. Нужно наследование. Б. Передача объекта по ссылке.
P.S. Адепты ООП думаю со мной не согласятся.


#12

Сейчас структуры можно менять.
Но надо еще посмотреть как работает многопоточность в структурах-классах, а также как работает производительность в классах-структурах. Ну и учесть как работает смесь структур с классами.

Хотя если задачи простые, то разницу трудно заметить.


#13

Я в смысле про копирование, что старый объект не изменяется.

Там компилятор оптимизирует, сложно будет посмотреть.


#14

Опять же, при небольших объемах это все незначительные погрешности.
Но вот есть разница в производительности:

class Foo {
   func foo() {}
}

final class Foo {
     func foo() {}
}

или например в протоколах:

class Foo: FooProtocol {
    func foo() {}
}

class Foo {}

extension Foo: FooProtocol {
   func foo() {}
}

#15

Потому что иногда неизвестно вообще какие будут данные, т.е. не тип, а сами значения. В некоторых случаях, да, можно указать пустую строку или ноль, но это не всегда полезно. Бывают случаи, когда такие стартовые значения мешают. Поэтому класс можно записать без стартовых данных, но добавить в него инициализатор(ы). Тогда при создании экземпляра класса будут затребованы инициализирующие значения.


#16

В данном случае ты имеешь в своем классе 2 name и 2 type: первые name и type - это свойства класса, вторые name и type - это наименования параметров инициализатора (который есть не что иное как функция, предназначенная для инициализации класса). Т.е. ты должен передать например, name из параметров фукнции в name свойство класса. Это и есть инициализация.

А поскольку названия одинаковые, то компилятору нужно различить, где какое name. Поэтому, чтобы он не ошибся, то к name, которое свойство класса, добавляется self. Это означает, что name в данном случае принадлежит этому классу. Т.е. self это замена названия класса.

Еще один нюанс, что в инициализаторе параметры можно называть иначе, т.е. разными словами, это вполне можно делать, здесь запретов нет. Но тогда может получиться так, что если свойств и параметров будет много, то можно будет запутаться. Поэтому из опыта пришли к тому, что удобнее называть параметр также как и свойство. Тогда понятно откуда и куда идет значение. Остается только добавить self для свойства.


#17

Вопрос конечно интересный. Я тоже не нашел определенного ответа, в основном общие рассуждения про ссылочный тип и тип значения. И да, оба этих типа наследуются, различия только в передаче значений от одного экземпляра другому.

Поэтому чисто по ощущениям, если проект небольшой, то маловероятно, что эта разница сыграет какую-то роль, если только не попадешь на какую-то действительно специфическую задачу. И пока с этим напрямую не столкнешься, то различать трудно. Поэтому изначально можно использовать структуры, а если что, тогда брать классы. Или чтобы уже наверняка, то брать класс изначально.

По идее, если проект небольшой, то переделать его в случае ошибки возможно. А если проект большой, то скорее всего будешь работать не один и там более опытные коллеги могут подсказать.

Инициализатор это та же функция, а у функции могут быть внешние и внутренние названия для параметров. Внешнее название для вызова, а внутреннее для использования внутри самой функции. Иметь внешнее и внутреннее название параметра не обязательно, можно использовать одно. Просто, как это часто бывает, из опыта стало ясно, что иногда удобно иметь и два названия.

Затем, насчет подчеркивания. Иногда не имеет значения как называется параметр, т.е. данные могут приходить с любыми названием параметра. Если не ошибаюсь, так реализована например, функция print, загоняешь туда любые данные без всяких названий параметров. В таких случаях, чтобы не тратить время на название параметра, которое не играет роли, решили, что можно использовать нижнее подчеркивание - _

И далее, нижнее подчеркивание тоже не обязательно, можно использовать назание параметра. Хотя возможно, что в некоторых случаях, лучше использовать нижнее подчеркивание, чем название параметра.

Тоже долго не мог понять, что такое делегаты (протоколы отдельная песня). “Прозрел” только когда занялся Табл ВЬю КОнтролером. В общем, система следующая: в таблицу загружаются данные, они берутся из какого-то источника, Data Sourse. Для этого есть специальные методы, их как минимум 3, они присутствуют в шаблоне табл вью контролера по умолчанию и там есть специальный раздел с таким же названием.

И далее, это методы управления данными, как их загружать в таблицу. Но вот управлять внешним видом таблицы сам табл вью контроллер не может. Это типа общее правило, все объекты управляют чем-то внешним, но не могут управлять сами собой. Тем не менее это часто нужно делать. Например, изменить внешний вид таблицы, увеличить высоту ячейки, изменить последовательность строк и т.д. Вот тогда то и нужны делегаты. Т.е. это отдельный объект. В случае с табл вью контроллером, он передает функции управления собой, своим внешним видом и другими вещами, этому внешнему объекту, делегирует ему какие-то действия. И этот делегат уже обрабатывает этот основной объект, делает то, что нужно, что ему сказал основной объект. Вот в принципе основная идея делегата.

Что писать до или после ДидЛоада ты решаешь сам, по идее особых ограничений нет. Единственное из практики, обычно в начале вставляют аутлеты и привязывают кнопки из сториборда. Внутри ДидЛоада обычно вставляются какие-то действия, которые нужно сделать после загрузки приложения. Примеров тут можно много привести, например, самый простой - передать значение из текстового поля в лэйбел. Опять же, каких-то особых и жестких ограничений нет. В некоторых случаях можно вообще обойтись без этого ДидЛоада, но чаще всего он все-таки используется, потому как удобно. Ну и конечно стоит почитать про жизненный цикл приложения, там показывают несколько таких состояний, например, до загрузки, после, до появления экрана, после и т.д.


#18

Боже мой, кажется, я наконец-то понял.
То есть, из-за того, что Swift строго типизированный язык, он требует, чтобы мы всегда обозначали переменные и константы перед тем, как будем с ними работать, но иногда мы не хотим, например, им присваивать конкретное значение, так как оно может мешаться.
И, таким образом, инициализация по своей сути является дефолтной функцией для любого класса, которая в качестве входящих параметров принимает в себя свойства этого класса и присваивает свойствам всех последующих экземпляров этого класса свои свойства, и чтобы избежать путаницы, названия свойств класса и его экземпляров совпадают, поэтому чтобы объяснить компилятору как различать одно от другого, перед свойствами класса пишут слово self?


#19

Хорошо, тогда попробую плотно поработать с table view controller’ом, чтобы разобраться.
А что в итоге с протоколами, какая область их практического применения при решении задач? :slight_smile:


#20

Если занкомы с другими языками, то это аналог Interface.