Данный материал, рассчитан на новичков в разработке, сконцентрированных на языке Swift, где я простым языком постараюсь максимально доходчиво донести базовые вещи, которые иногда бывает сложно понять, даже спустя продолжительное время в разработке. Такие выводы я сделал на основании ревью кода некоторых товарищей, которые достаточно много времени посвятили разработке, но все же совершали типичные ошибки. Если Вы уверенный в себе разработчик, то не думаю, что сможете подчерпнуть для себя полезную информацию, а коль начинаете свой путь, добро пожаловать к ознакомлению. Изначально я планировал изложить все в формате одной публикации, но информации так много, что я решил разбить материал на части.  Весь свой информационный мусор, я буду коллекционировать на своей стене в ВК, так что добро пожаловать. Вы должны понимать синтаксис языка, иметь общее представление о разработке и ОПП, хотя бы отдаленное. В остальном, каких-либо требований и знаний не требуется. Обычно, в учебных пособиях, книгах и прочих источниках информации, class'ы объясняют примерно так «class это - описание объекта, а объект это экземпляр класса и бла бла бла», в принципе, это частично отражает суть конструкции, но называть class в рамках языка Swift, описанием объекта, будет не совсем корректно т.к. он же еще и представляет собой тип данных и вообще можно использовать классы как независимые, самодостаточные сущности, которые не требуют инициализации. Поэтому начинать со слов «Возьмем объект животного, пускай это будет кот...» я конечно же не буду. И вообще, давайте не будем о "сложном" т.к. материал рассчитан все же на новичков, а новички могут и не знать, что такое, эти ваши объекты и инициализации. В общем я считаю подобное (я про формулировку) не достаточно информативным, поэтому будем разбирать все на примерах с переходом от простого к более сложному. Попутно к ознакомлению с классами, мы будем так же рассматривать и другие возможности языка, но обо всем по порядку. Итак, начнем, сейчас мы создадим простой фрагмент кода Он не решает какую-либо конкретную задачу или проблему, мы просто балуемся. Но сие баловство, открывает нам интересные моменты. Область видимости, негласные правила именования и неявное указание типов. Собственно давайте разберемся. Мы создаем константу factor (множитель) и функцию multiplication(умножение), константа у нас относится к типу Int, функция принимает  Int, производит вычисления и возвращает результат, тоже в Int. Этот результат, мы принимаем в константу result, которая НЕЯВНО объявляется как Int т.к. принимает Int, а функция имеет доступ к константе factor т.к. она попадает в область видимости функции, потому как находится на одном уровне с ней (запоминаем, дальше вернемся к этому). Так же, всё, что Вы видите, имеет название по назначению, иными словами, Вы больше не будете смеяться над мемами из пабликов с iT юмором т.к. существует простое правило присваивания названий и вы сейчас с ним столкнулись (надеюсь не  в первый раз). Что видим, то и пишем. Что делает, так и называем и т.д. Дальше мы тоже об этом поговорим. Код всегда должен быть понятным и информативным. Но собственно продолжим. Ожидаемо, что результатом функции будет 25 Но давайте представим, что по каким-либо причинам, нам нужно использовать эту функцию в других частях программы и с разными множителями. Приходит, что либо на ум? Я покажу два простых и ожидаемых варианта подобной реализации (опустим возможность рефакторинга этого убожества). Очевидный вариант, передавать аргумент содержащий множитель в функцию, что освобождает нас от внешней зависимости и менее очевидный и более извращенный способ - сделать factor переменной, и изменять ее перед вызовом. Соответственно изменяя зависимость перед вызовом, мы достигаем нужного результата и множитель нам подконтролен. В нашем странном проекте, требуется, чтобы значение множителя по умолчанию было равно 5, и если в варианте с передачей множителя аргументом это реализуется вот так, с внешней зависимостью это будет выглядеть иначе хотя мы можем вообще передавать сквозные параметры и сделать вот так Перед продолжением я поясню. Чтобы закрепить вышеописанное. Что мы теперь знаем, функция, может работать с внешними данными, менять внешние данные, принимать аргументы и возвращать, что-либо. У нее есть область видимости, и пока мы затронули только один уровень. Обычные аргументы, которые передаются в функцию, являются константами (т.е. не изменяемы и существуют внутри тела функции), а сквозные параметры (inout) от того и сквозные, что передавая их в функцию, функция может их изменять (мутировать). Ничего сложного. Давайте теперь немного усложнять наш код и ставить таки реальные задачи. Есть у нас например интернет-магазин и продаем мы ножи. Продаем мы их как в России так и в Америке, за доллары и за рубли соответственно. Покупаем за евро т.к. сами мы ничего не производим, а просто закупаем в виде полосок стали, чтобы стоимость зависела от длины клинка (удобно для примера), чем длиннее нож, тем он дороже. И конечно коэффициента, который зависит от сложности выполнения работ. Давайте все это оформим в коде. Теперь эти данные нам необходимо как-либо обработать, чтобы получить стоимость для разных стран. Стоимость конечно же рассчитываем исходя из длины и коэффициента. Наценка у нас будет 40%, закупочная стоимость идет за метр стали, длина сохраняется в сантиметрах. Математика получается достаточно простой. Реализацию подсчетов так же сделаем весьма грубой (вместо типа данных для финансов, будем использовать double) и начнем с получения стоимости в рублях Добавим функцию для получения прайса в долларах Итого у нас есть работающий код, убогий, но выполняющий свою функцию. Какие проблемы есть у этого кода на текущий момент? Внедрение внешних зависимостей в функцию, но от этого легко избавится И дублирование участков кода, что тоже не очень хорошо. Давайте воспользуемся средствами языка и улучшим ситуацию, создав перечисление которое будем передавать аргументом в функцию, что позволит нам возвращать различные варианты прайса, в зависимости от необходимости. Удалим функцию получения прайса в рублях и изменим функцию получения прайса в долларах, которая отныне будет универсальной для возврата прайса.  Код работает, но он громоздкий и им неудобно пользоваться. Что нам делать, если мы получаем например два ножа? Как именовать переменные? name2, coefficient2? Конечно же нет. Нам помогут объекты. И сейчас мы опишем объект нашего ножа классом и познакомимся с инициализатором. Перенесем функцию в класс, которая станет его методом. Перенесем перечисление в класс, которое тоже станет перечислением класса. Такие значения как курс доллара, курс евро и наценку мы оставим в глобальной области т.к. они универсальны для всех ножей. Когда мы описываем класс, мы описываем его свойства и методы. Если описываемые свойства не имеют значений, они должны быть определены в инициализаторе.  Инициализатор получает значения как функция получает аргументы и формирует экземпляр объекта. Поэтому мы можем получать данные о ножах непосредственно в экземпляры. Теперь обе константы являются экземплярами класса Knife т.е. объектами, и мы без каких либо проблем можем обращаться к его свойствам и методам для получения данных. Как вы можете заметить, это куда более удобный вариант работы с данными, так как каждый экземпляр обладает определенными методами и работает со своими внутренними данными, которые так же можно смешивать с глобальными. Но давайте реализуем в нашем текущем коде нововведение, представим, что нам завезли ножи у которых есть чехлы и прайс зависит еще и от стоимости чехла. Нам, что новый класс описывать? Да нет. Мы создадим новый класс, который унаследует методы и свойства нашего первого класса. Теперь мы можем получить экземпляр унаследованного класса, добавив дополнительный параметр со стоимостью чехла при инициализации. И он будет уметь все то же, что и родительский класс, плюс прибавлять к итогу стоимость чехла. Я не стал разделять на доллары и рубли т.к. мы просто рассматриваем вопрос наследования (зачем это вообще надо) и ее так сказать практическую часть, а выше разобранный пример, просто плюсует цифру к результату. Что в нем происходит поймете чуть позже, продолжайте ознакомление. Теперь же мы, переходим к сути работы с классами, вышеуказанный пример нам больше не нужен, он был присказкой, чтобы были понятны проблемы, которые решаются с их помощью. Рассмотрим вариации использования классов. Начнем конечно же с создания класса. В нашем классе есть свойство и метод. Как же мы можем взаимодействовать с классом? Вариант первый - создать его экземпляр. Теперь мы можем обратится к свойству либо методу через него Но без экземпляра, данный класс бесполезен, поэтому существует практика, инициализировать классы сами в себя, в статичное свойство. Статичное свойство класса доступно глобально, если мы просто будем обращаться к классу и недоступно в его экземплярах. Используется, когда мы хотим иметь возможность создавать экземпляры, а так же обеспечить единство, то есть иметь только один экземпляр объекта. К слову, в подсказке Xcode, вы вероятно заметили init(), это как раз таки метод инициализации класса, если можно так выразится, с которым мы знакомились ранее и Swift позволяет опускать его до круглых скобок. Ну и как мы говорили ранее, инициализатор не требуется, если класс самодостаточен (определена структура, свойства и т.д.), но это не означает, что мы не можем им воспользоваться. Можно например вызвать в нем свой собственный метод или выполнять вычисления. И конечно инициализаторов может быть несколько. Как видите, экземпляр класса инициализированный с использованием разных инициализаторов, может быть построен по разному. Но это еще не все, мы можем вообще не использовать инициализацию класса и использовать его глобально, как тип. Сделаем его методы, методами типа. Соответственно значения хранятся непосредственно в типе и не требуют экземпляра, с ними можно работать, они сохраняют свое состояние. Почему я называю его типом? (вообще правильнее вероятно называть классом и объектом, но это может привести к путанице, поэтому тип и класс) Сейчас поймете, давайте познакомимся с еще одной удобной возможностью которая относится как к экземплярам так и типам класса. Мы используем статичное свойство типа, свойство класса, метод типа и метод класса. Они существуют обособлено - экземпляр не имеет доступа к свойствам и методам типа, а тип не имеет доступа к свойствам и методам экземпляра. Соответственно имена могут быть одинаковыми. В методах мы возвращаем тип и класс у которых мы вновь можем вызывать методы. Все это, частично раскрывает колоссальные возможности этого замечательного языка, но давайте двигаться дальше. Теперь, в следующем разделе, мы более детально рассмотрим наследование. Мы уже знакомы с наследование по первому примеру, но настал момент рассматреть более детально. Создадим класс Sum, который будет содержать два свойства и метод, позволяющий их складывать с возвращением результата. Обратите внимание мы не используем self в инициализаторе т.к. имена параметров, передаваемых в инициализатор, отличаются от свойств класса, соответственно Swift позволяет его опускать. мы не используем return в методе sum, т.к. Swift позволяет его опускать при использовании однострочных вычислений  А теперь модифицируем его с помощью еще одного удобного инструмента в языке, вычисляемых значений, это освободит нас от метода и даст более удобный доступ к результату, добавив вычисляемое свойство calc. Т.к мы используем только вычисления (есть и другие возможности, но об этом позже), мы можем опустить конструкцию get{} и конечно так же опустим return итоговый вариант будет выглядеть так Выведем результат Кстати, как вы могли заметить, нам нет необходимости инициализировать класс в переменную или еще куда-либо, мы просто можем создать его налету. Собственно наш первый класс готов и давайте создадим еще один, но уже для умножения значений, унаследовав его от класса для сложения И еще один класс, который будет унаследован уже от класса умножения Он унаследовал методы и свойства от родительских классов по цепочке. Т.е. он обладает методами и свойствами класса умножения и сложения. Родительский класс называется Super классом и об этом, мы тоже чуть позже поговорим. Конечно же можно переопределять унаследованное, и сейчас я покажу зачем это может пригодится. Давайте рассмотрим наблюдаемое свойство в новом классе Конструкция didSet означает, что в момент изменения свойства number, будет выполнен блок кода внутри {} уже с полученным новым значением. Теперь создадим класс наследующий NewClass override позволяет переопределять методы, пример использования вы могли наблюдать неоднократно, например в viewDidLoad. Когда вы проектируете класс, он может выполнять множество различных действий, многие из которых необходимо наблюдать и выполнять различные действия после их завершения, от этого класса можно получать наследование и при необходимости использовать оставленные методы. Т.е. метод method() класса NewClass не обязательно должен, что-либо выполнять, его можно оставлять пустым, для последующего использования. Так же, желательно передавать параметры, в подобные методы, даже если они находятся в области видимости, что-бы в последующем, при переопределении, было понятно, что в него приходит (какие данные получает).  Доступы и ограничения Настало время поговорить о крайне удобных возможностях языка, о доступах и ограничениях. Давайте вернемся к NewClass и внесем изменения Теперь, при изменении свойства number - выполняется метод buferMethod, который в свою очередь вызывает method() информирующий об изменении number т.к. нам может понадобиться выполнить ряд других операций в момент изменения и вызываемый метод в наблюдателе не будет является конечной точкой.  Далее переопределим buferMethod в классе childClass и изменим его название на СhildClass т.к. типы и классы принято начинать с верхнего регистра. и посмотрим, что произойдет ожидаемо, что метод method никогда не будет вызван. Это является проблемой т.к в процессе разработки, вы без проблем можете об этом забыть либо другой разработчик использующий ваш класс по неведению переопределит метод, который переопределять не следовало. Во избежании подобных ситуаций, существует инструмент. Перед методом необходимо указать запрет на переопределение final в таком случае, метод переопределить не удастся и Xcode соответственно, выведет  ошибку с пояснениями final так же можно использовать для запрета наследования, обозначив им класс в таком случае, его нельзя будет использовать как родителя.   Ограничения так же являются великолепной возможностью, позволяя открывать либо закрывать методы и свойства. Ограничения нужны в первую очередь как гарантия безопасности использования класса (он будет вести себя как задумано и никто не внесет несанкционированных изменений), во вторую очередь для удобства. Объяснять долго, давайте опять рассмотрим пример в коде. Мы создали класс, который выполняет простую задачу, дает нам доступ к числовому значению int и позволяет его изменять строго на положительное т.к. изменения происходят через проверяющий метод. Но вся наша логика безполезна в подобной реализации т.к. мы без проблем установим свойство напрямую в intCell т.к. разрабатывая класс, мы ожидаем использование метода setInt для модификации свойства intCell. Это первая проблема, проблема с безопасностью, о которой я говорил. Так же она проявляет себя в возможности вызова методов, которые не должный быть выполнены самостоятельно, что видно на примере метода checkInt , который не должен быть использован нигде, кроме как внутри метода setInt. Если бы метод checkInt изменял состояние класса, то его вызов так же мог бы привести к ошибкам. Помимо безопасности, Xcode будет предлагать ненужные методы и свойства, что является второй проблемой, неудобством использования класса. поэтому при проектировании необходимо указывать доступы методов и свойств, переделаем наш код. теперь мы не имеем доступа к методам и свойствам private а если попробуем обратится напрямую - получим ошибку. На этом первая часть материала подходит к концу, немного забегу вперед и обращу ваше внимание на то, что и так заметил наблюдательный читатель (мы объявляли экземпляры в константы и изменяли их). Классы являются ссылочным типом и значения передаются по ссылке, это кстати одно из основных отличий класса и структуры, но об этом после. Чтобы было проще понять, оглянитесь вокруг, вы находитесь в каком-либо помещении и в нем однозначно есть дверь или окно. Класс будет зданием, а помещения свойствами. Когда вы присваиваете свойство экземпляра например переменной, переменная как будто бы открывает портал в эту комнату и если бросить в портал яблоко, оно попадет в помещение. Пример Продолжение будет во второй части.  Ссылка на вторую часть - Тыц See you later.... Разработчик