Подклассы в Objective C, это один из важнейших механизмов программирования. Я не очень сильно буду распинаться, что это такое и с чем едят, но для того, чтобы у вас возникло общее понимание предмета, самые важные и основные моменты я обязательно здесь затрону. Моя основная цель, это дать конкретные ответы, на вполне конкретные вопросы, пусть даже на очень простые, но ответы на которые бывает иногда ищут очень долго. Проще говоря, опишу все простыми словами, по принципу - как просто.
Содержание
- 1 Основное
- 2 Для чего нужны подклассы?
- 3 Что такое наследование?
- 4 Как создать класс/подкласс?
- 5 Как подключить подкласс в Interface Builder?
- 6 Как получить доступ к методам класса?
- 7 Как в класс добавить свои переменные?
- 8 Результат текущего урока - отдельная библиотека MyTextLib
- 9 Добавление методов в классы - Категории
- 10 Расширения классов
Основное
- Вы можете добавлять новые методы в класс только с помощью категорий. Нельзя использовать категорию для добавления дополнительных переменных экземпляра в класс.
- Подкласс наследует методы другого класса (суперкласса).
- Подкласс наследует переменные экземпляра суперкласса.
- Подкласс может переопределять методы, унаследованные от его суперкласса. Это называется замещением (overriding). Если подкласс переопределяет метод, унаследованный от его суперкласса, то когда соответствующее сообщение отправляется экземпляру этого подкласса, вызывается версия метода, определенная в подклассе.
- Подкласс может определять свои собственные методы. Подкласс состоит из методов, наследуемых от своего суперкласса, а также может иметь некоторые собственные методы.
- Подкласс может также определять свои собственные переменные экземпляра.
Для чего нужны подклассы?
Причина существования подклассов достаточно весомая, если кратко, то это наследование, т.е. возможность нескольким классам совместно использовать имеющуюся функциональность.
Что такое наследование?
Все методы, существующие в классе, всегда наследуются.
Наследование - это механизм языка, позволяющий описать новый класс на основе уже существующего (родительского, базового) класса или интерфейса. Потомок может добавить собственные методы и свойства, а также пользоваться родительскими методами и свойствами всех вышестоящих классов. Наследование передается от вышестоящего класса к нижестоящему классу и только в одном направлении от суперкласса к дочернему классу (подклассу). Например суперкласс Class1 не может воспользоваться методами и переменными нижестоящих классов Class2 и Class3, но зато Class3 может использовать все методы и переменные классов Class1 и Class2.
Как создать класс/подкласс?
Благодаря имеющимся возможностям подклассы (субклассы) например часто используют для кастомизации элементов интерфейса. Ниже живой пример создания подкласса для класса NSString.
Готовый пример:
Заголовочный файл .h подкласса NSString в Objective-C
// // MyTextLib.h // // Created by Saveliy Severniy on 03.03.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import <Cocoa/Cocoa.h> // MyTextLib - это ваше произвольное имя подкласса. // В нашем примере это имя будет MyTextLib. @interface MyTextLib : NSString /* Наш индивидуально созданный метод подкласса для примера */ - (NSString *)parceMyString:(NSString *)string; @end
Файл реализации .m подкласса NSString в Objective-C
// // CustomView.m // // Created by Saveliy Severniy on 03.03.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import "MyTextLib.h" @implementation MyTextLib /* Наш индивидуально созданный метод подкласса для примера */ - (NSString *)parceMyString:(NSString *)string { if ([string length] >= 1) { string = [string stringByReplacingOccurrencesOfString: @ "," withString: @ "."]; } return string; } @end
Выше созданный класс (подкласс для класса NSString) пока почти ничего не делает, это лишь "шапка", в котором мы реализовали для примера наш собственный один метод parceMyString:string, но этот подкласс уже можно подключить к вашему коду и начать реализовывать свои идеи. Чтобы создать подкласс для любого другого класса, как правило, достаточно лишь изменить в шаблоне имя суперкласса, для которого вы хотите создать подкласс. Например, чтобы создать субкласс для NSButton, нужно и вписать это имя суперкласса вместо NSString (в заголовочном файле .h).
Как подключить подкласс в Interface Builder?
Чтобы подключить свой кастомный подкласс к объекту View в Interface Builder, выделите нужный View мышкой и справа в инспекторе свойств в разделе Custom Class введите имя свое нового подкласса или выберите его из списка и сохраните изменения. Все, теперь при запуске приложения к данному объекту будет применен ваш подкласс, в котором автоматически будут вызываться те методы, которые вы переопределили относительно существующих методов из суперкласса.
Если к примеру вас интересует только кастомная реализация интерфейса, этого вполне достаточно. Но если вы в своем новом подклассе дополнительно реализовали некоторые методы, которые требуется вызывать из основного кода контроллера, вам также потребуется выполнить еще некоторые действия. Для получения подробностей переходим ниже к следующему разделу.
Как получить доступ к методам класса?
Не всегда бывает достаточно только создать кастомный подкласс, иногда еще нужно иметь возможность обращаться к методам этого подкласса из основного кода. Теперь по порядку. Чтобы из основного класса можно было вызвать некий метод из подкласса, нужно правильно подключить подкласс к основному классу:
1. Через директиву #import в файле реализации нужного нам контроллера мы подключаем заголовочный файл нашего подкласса. Например к классу ViewController мы подключили подкласс CustomView, вписав строку: #import "CustomView.h".
2. В заголовочном файле ViewController.h через ключевое слово @class указываем имя нашего кастомного класса (подкласса).
3. Ниже, в разделе @interface добавляем аутлет IBOutlet CustomView* myCustomView;
4. Теперь из кода реализации основного контроллера (в данном случае естественно из ViewController.m) мы спокойно можем вызвать метод из нашего нового подкласса следующим образом: [myCustomView parceMyString:@"It works,,!"];
Готовый пример с подключенным классом MyTextLib:
Заголовочный файл ViewController.h
// // ViewController.h // // Created by Saveliy Severniy on 03.02.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import <Cocoa/Cocoa.h> @class MyTextLib; @interface ViewController : NSViewController { // Здесь объявляются переменные. Внизу справка. // Здесь объявляются переменные (только для переменных экземпляра класса). MyTextLib *myTextLib; }
Файл реализации ViewController.m
// // ViewController.m // // Created by Saveliy Severniy on 03.02.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import "ViewController.h" #import "MyTextLib.h"
Как в класс добавить свои переменные?
Здесь нет «переменных класса», только «переменные экземпляра». Метод target-c для работы с переменными класса - это статическая глобальная переменная внутри файла .m класса. «Статический» гарантирует, что переменная не может быть использована вне этого файла (то есть она не может быть внешней).
Бывает необходимость в добавлении в подкласс (экземпляр класса) некоторых своих переменных (не в super class, а в текущий). Это делается очень просто, также, как и в любом другом классе. А чтобы получить доступ к переменным подкласса из основного класса, достаточно выполнить настройки, которые мы уже проходили чуть выше, то есть нужно правильно подключить подкласс к вашему базовому классу.
1. В заголовочном файле MyTextLib.h в разделе @interface добавляем переменную, например - NSCursor *cursor;
2. Дальше, под разделом @interface объявляем (описываем) свойства переменной - @property (strong) NSCursor *cursor;
3. Последний шаг, в файле реализации MyTextLib.m после раздела @implementation синтезируем нашу переменную - @synthesize cursor;
Примечание: Начиная с Xcode 4.4 (LLVM Compiler 4.0) переменные экземпляра и методы доступа автоматически синтезируются для свойства. Автоматически синтезируемое свойство будет использовать переменную, которое имеет имя _<propertyName>, т.е. имя ivar - это имя свойства с префиксом подчеркивания, если директива @synthesize не используется явно для этого свойства.
Просто объявите общедоступные @property переменные в вашем .h, поместите свои частные переменные в расширение класса в верхней части .m файла. Больше нет необходимости в @synthesize.
В более старых проектах часто появляются предупреждений типа:
Autosynthesized property 'myVar' will use synthesized instance variable '_myVar', not existing instance variable 'myVar', то просто удаляете переменные из блока @interface в .h файле и используете к ним доступ как свойство self.myVar или _myVar.
Если по какой-то причине вам нужно получить доступ к переменной напрямую, вы можете использовать @synthesize myVar или @synthesize myVar=_myVar (доступ через _myVar теряется). Но в таком случае проще просто перименовать все myVar в _myVar, чем возиться с @synthesize.
Доступ:
// Всегда
self.myVar = [[NSBundle mainBundle] bundlePath];
// Или (без @synthesize)
_myVar = [[NSBundle mainBundle] bundlePath];
// Или (с @synthesize)
myVar = [[NSBundle mainBundle] bundlePath];
Результат текущего урока - отдельная библиотека MyTextLib
Итоговый заголовочный файл .h подкласса NSString
// // MyTextLib.h // // Created by Saveliy Severniy on 03.03.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import <Cocoa/Cocoa.h> @interface MyTextLib : NSString { //------------------------------------------------------------------------// // Здесь объявляются переменные (только для переменных экземпляра класса). //------------------------------------------------------------------------// // 1. Метод со знаком + : имеет доступ только к self объекту. // 2. Метод со знаком - : имеет доступ только к методам экземпляра и его переменным. NSCursor *cursor; // Кстати. Внизу справка! } //------------------------------------------------------------------------// // Здесь объявляются свойства переменных и методы. //------------------------------------------------------------------------// // Указываем свойство переменной @property (strong) NSCursor *cursor; //------------------------------------------------------------------------// // Методы. //------------------------------------------------------------------------// // 1. + для метода класса (метод может быть вызван без создания экземпляра класса) // 2. - для метода экземпляра (instance) //------------------------------------------------------------------------// // Наш индивидуально созданный метод. Используя этот метод, // мы в string заменяем все запятые на точку. - (NSString *)parceMyString:(NSString *)string; @end //------------------------------------------------------------------------// // СПРАВКА //------------------------------------------------------------------------// // В настоящее время компания Apple рекомендует помещать ключевое слово IBOutlet в объявлении свойства. // // Когда недавно компания Apple изменила компилятор, используемый по умолчанию, с GCC на LLVM, // объявлять переменные экземпляров для свойств стало необязательным. Если компилятор LLVM // обнаружит свойство, которому не соответствует ни одна переменная экземпляра, она создаст ее // автоматически. Но это не освобождает от реализации оператора @synthesize, хотя вы можете и не // писать @synthesize, однако в этом случае вам придется самому написать реализации геттера и сеттера. // // Внимание. Начиная с Xcode 4.5 использование @synthesize в файле реализации стало необязательным. // В этом случае в файле - заголовке объявляется свойство @property NSInteger vSize; и доступ к самой // переменной будет начинаться с символа подчеркивания _vSize, а доступ к сеттеру или геттеру внутри класса [self vSize]. //------------------------------------------------------------------------//
Итоговый файл реализации .m подкласса NSString
// // MyTextLib.m // // Created by Saveliy Severniy on 03.03.18. // Copyright © 2018 Saveliy Severniy. All rights reserved. // #import "MyTextLib.h" @implementation MyTextLib @synthesize cursor; // Наш индивидуально созданный метод. Используя этот метод, // мы в string заменяем все запятые на точку. - (NSString *)parceMyString:(NSString *)string { if ([string length] >= 1) { string = [string stringByReplacingOccurrencesOfString: @ "," withString: @ "."]; } return string; } @end
Добавление методов в классы - Категории
Категория используется для добавления методов в существующий класс. Обычно используется для расширения функциональности одного из существующих классов.
Вы можете добавить методы в класс, объявив их в файле интерфейса под именем категории и определив их в файле реализации под тем же именем. Название категории указывает, что методы являются дополнениями к классу, объявленному где-то в другом месте, а не новым классом. Однако нельзя использовать категорию для добавления дополнительных переменных экземпляра в класс.
Методы, которые добавляет категория, становятся частью типа класса. Например, методы, добавленные к классу NSArray в категории, включаются как методы, которые компилятор ожидает от экземпляра NSArray в своем репертуаре. Однако методы, добавленные к классу NSArray в подклассе, не включаются в тип NSArray. (Это имеет значение только для статически типизированных объектов, поскольку статическая типизация - единственный способ, которым компилятор может узнать класс объекта.)
Методы категории могут делать все, что могут делать методы, определенные в собственном классе. Во время выполнения разницы нет. Методы, которые категория добавляет к классу, наследуются всеми подклассами класса, как и другие методы.
Объявление интерфейса категории очень похоже на объявление интерфейса класса, за исключением того, что имя категории указано в скобках после имени класса, а суперкласс не упоминается. Если ее методы не имеют доступа ни к каким переменным экземпляра класса, категория должна импортировать файл интерфейса для класса, который она расширяет.
#import "ClassName.h" @interface ClassName ( CategoryName ) // method declarations @end
Обратите внимание, что категория не может объявлять дополнительные переменные экземпляра для класса; он включает только методы. Однако все переменные экземпляра в области действия класса также входят в область действия категории. Сюда входят все переменные экземпляра, объявленные классом, даже объявленные @private.
Нет ограничений на количество категорий, которые вы можете добавить в класс, но названия каждой категории должны быть разными, и каждая должна объявлять и определять свой набор методов.
Расширения классов
Расширения классов похожи на анонимные категории, за исключением того, что объявляемые ими методы должны быть реализованы в основном блоке @implementation для соответствующего класса. Используя компилятор Clang / LLVM 2.0, вы также можете объявлять свойства и переменные экземпляра в расширении класса.
Обычно расширения класса используются для повторного объявления свойства, которое публично объявлено, как доступное только для чтения в частном порядке как readwrite:
@interface MyClass : NSObject @property (retain, readonly) float value; @end // Private extension, typically hidden in the main implementation file. @interface MyClass () @property (retain, readwrite) float value; @end
Обратите внимание, что (в отличие от категории) во втором блоке @interface в скобках не указано имя.
Также, обычно класс имеет публично объявленный API и затем имеет дополнительные методы, объявленные частным образом для использования исключительно классом или структурой, в которой находится класс. Расширения классов позволяют объявлять дополнительные необходимые методы для класса в местах, отличных от блока @interface основного класса:
@interface MyClass : NSObject - (float)value; @end @interface MyClass () { float value; } - (void)setValue:(float)newValue; @end @implementation MyClass - (float)value { return value; } - (void)setValue:(float)newValue { value = newValue; } @end
Реализация метода setValue: должна появиться в основном блоке @implementation для класса (вы не можете реализовать его в категории). Если это не так, компилятор выдает предупреждение о том, что он не может найти определение метода для setValue
Частичный перевод официальной документации:
developer.apple.com - Категории
На этом вроде все. На что хватило времени и сил, я постарался разжевать, но надеюсь, я все же до вас донес основные моменты в полном объеме. Если найдете ошибки в тексте, в уроке, или что-то захотите дополнить, буду рад вашим комментам.