Ознакомьтесь с функциями и методами языка программирования Objective-C.

Понимание разницы между функциями и методами в Objective-C

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

Функции

Функции являются ключевым компонентом многих языков программирования. В простейшем смысле функции - это способ, с помощью которого мы можем повторно использовать код в нашем приложении, что упрощает использование той же функции без необходимости вводить ее снова или копировать и вставлять из другого места в нашем коде. Это прекрасно вписывается в принцип, который часто называют СУХИМ или «не повторяйся». Хорошее практическое правило состоит в том, что если вы видите, что вы сами переписываете или копируете и вставляете один и тот же фрагмент кода снова и снова, вам, вероятно, следует обернуть его функцией, чтобы вы могли легко использовать его повторно. Поскольку Objective-C является надмножеством C, он все еще может полагаться на него для определенных компонентов, включая функции.

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

// main.m
#import <Foundation/Foundation.h>
// declare our function
int addNumbers(int number1, int number2) {
    return number1 + number2;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
       // Call our function and save its return value in a variable
       int sum = addNumbers(1, 2);
        NSLog(@"The sum is %i", sum);
    }
    return 0;
}

В приведенном выше примере мы объявили и определили функцию под названием addNumbers, которая принимает два числа, складывает их и возвращает сумму двух чисел. Следует отметить, что когда функция возвращает значение, мы можем захватить его и сохранить в переменной. Преимущество функции, которая будет складывать числа, полезно, потому что в любом месте, где мы хотим сложить два числа, мы можем. Что ж, возможно, это не самая полезная функция, поскольку у нас есть оператор «+», но вы поняли идею. . .

В приведенном выше примере показано, как мы можем создавать функции, которые принимают аргументы и возвращают значения, которые являются примитивами C; однако мы также можем передавать и возвращать указатели на объекты Objective-C в функции и из них. Это показано в следующем примере:

// main.m
#import <Foundation/Foundation.h>
// Declare our function
void greetPerson(NSString *name) {
     NSString *bigGreeting = [name uppercaseString];
     
     NSLog(@"Hey there, %@%", bigGreeting);
}
// Use the funciton in our main function
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        greetPerson(@"Jamal"); // "Hey there, JAMAL" will be printed to the console
    }
    return 0;
}

Наша функция greetPerson принимает NSString, представляющее чье-то имя (в данном случае мое имя) в качестве аргумента, и выводит результат на консоль. Мы устанавливаем тип возвращаемого значения как void, потому что на самом деле мы ничего не возвращаем из этой функции (подробнее об этом позже). Следует отметить, что функции не всегда должны принимать и возвращать один и тот же тип. Например, у нас может быть функция, которая принимает объект Objective-C или примитив C и возвращает логическое значение, как показано в следующем примере:

// main.m
#import <Foundation/Foundation.h>

BOOL compareNames(NSString *firstName, NSString *lastName) {
    if (firstName == lastName) {
        return YES;
    } else {
        return NO;
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL namesMatch = compareNames("John", "John");
        NSLog(@"%hhd", namesMatch); // 1 will logged to the console
    }
    return 0;
}

Если взглянуть на два приведенных выше примера, мы можем заметить, что на самом деле мы написали нашу функцию вне функции main, прежде чем использовать ее внутри нее. Это называется объявлением функции, и именно так мы сообщаем компилятору о нашей функции перед ее использованием. Объявление функции позволяет компилятору знать, какой тип, имя и аргументы возвращают функции, поэтому, когда мы его вызываем, он точно знает, что он должен делать. Следует отметить одну интересную вещь: вы можете объявить функцию перед main, но фактически реализовать ее позже. Пока компилятор знает о возвращаемом типе, имени функции и ее параметрах (думайте об этом как о временных переменных, которые мы используем для представления аргументов, которые мы собираемся передать функции при ее вызове), мы можем вызвать ее внутри нашего main и реализовать ее (определить функциональность) позже. Вот пример, демонстрирующий это:

// main.m
#import <Foundation/Foundation.h>
// Declare our function's return type, name and inputs so the compiler knows about it
BOOL compareNames(NSString *firstName, NSString *lastName);
// Our main function that executes our code
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         BOOL namesMatch = compareNames("John", "John");
         NSLog(@"%hhd", namesMatch); // 1 will logged to the console
    }
    return 0;
}
// We actually define our function and its implementation
BOOL compareNames(NSString *firstName, NSString *lastName) {
if (firstName == lastName) {
        return YES;
    } else {
        return NO;
    }
}

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

Функции, которые ничего не возвращают

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

// main.m
#import <Foundation/Foundation.h>

void compareNamesRefactored(NSString *firstName, NSString *lastName) {
if (firstName == lastName) {
        NSLog(@"Whoa, this person's first name matches their last name!");
    } else {
        NSLog(@"This person has a normal name.");
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        compareNamesRefactored(@"John", @"David"); // "This person has a normal name." will be logged to the console
}
    return 0;
}

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

Методы

Методы во многом похожи на функции, но с той лишь разницей, что они привязаны к классам и определяются с использованием немного другого синтаксиса в Objective-C. Вы можете думать о методе как о типе функции, которая наделяет класс определенной функциональностью. Например, если у меня есть класс Person, цель этого класса - представить человека в моей программе. Человек может иметь рост, вес, возраст и профессию. Это будет считаться свойствами класса. Однако человек также может ходить, разговаривать и играть в видеоигры. Эти действия, которые человек может делать в нашей программе, будут представлены в виде методов.

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

Методы экземпляра и методы класса

Методы экземпляра можно рассматривать как действия, которые можно выполнять с экземплярами класса. Возвращаясь к нашему примеру с классом Person, если мы выделяем память для инициализации объекта person, мы можем затем отправить сообщение «walk» этому экземпляру person, чтобы заставить человека ходить в нашей программе. Сообщение «прогулка» отправляется экземпляру, инициированному классом, а не самому классу, поэтому это метод экземпляра.

// Sending the objectAtIndex message to an instance of the NSArray class
NSArray *myItems = @[@"Macbook", @"iPhone", @"iPad"];
[myItems objectAtIndex:1];

Методы класса аналогичны; однако они вызываются в самом классе, а не в экземпляре класса. Поэтому вместо отправки сообщения объекту, созданному из класса, мы отправляем сообщение самому классу. Пример показан прямо ниже.

// Sending a message to the NSArray class
[NSArray array];

Мы сообщаем компилятору, является ли метод классом или методом экземпляра, добавляя знак плюса (+) для методов класса и знак минуса (-) для методов экземпляра перед определением метода. Это также помогает нам и другим читателям узнать, является ли определяемый нами метод классом или методом экземпляра.

Пример синтаксиса метода класса:

+ (SomeReturnType)classMethodName

Пример синтаксиса метода экземпляра:

- (SomeReturnType)instanceMethodName

Отправка сообщений

Когда мы вызываем метод объекта, мы называем это «отправкой сообщения» объекту. Эта терминология взята из SmallTalk, языка, который широко использовался в Objective-C (вам может быть знаком этот термин, если вы занимались программированием на Ruby).

Можно подумать об отправке сообщения: объект имеет определенные функции, определенные методом класса, который его создал, но на самом деле он не будет выполнять это действие, если ему не будет отправлено сообщение с указанием сделать это. Пример из реальной жизни - отправить другу текстовое сообщение. Вы отправляете своему другу сообщение «встретимся в 7 Eleven сегодня в полдень» в виде текстового сообщения, и если ваш друг понимает сообщение, он или она может ответить, встретившись с вами в 7 Eleven в полдень, в соответствии с вашими инструкциями при условии, что они понимают ваше сообщение. Если бы вы отправили текст своему другу на таком языке, как суахили, он бы не понял ваше сообщение и не смог бы ответить на инструкции в отправленном вами сообщении.

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

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

Аргументы метода

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

Когда была создана Objective-C, она перестала быть самодокументированной, что означает, что при чтении кода должно быть совершенно ясно, что происходит, без необходимости угадывать цель кода или полагаться на комментарии для объяснения. Поскольку методы часто используются в программах на Objective-C, имена методов были созданы так, чтобы они читались как предложения, написанные на простом английском языке. Например, если бы мы создали метод экземпляра под названием sayHelloToPerson:, нам нужно было бы каким-то образом сообщить методу, с каким человеком следует поздороваться, поскольку в нашей программе может быть много объектов Person. Мы делаем это, передавая указатель на объект человека методу в качестве аргумента. Мы представляем аргументы, которые мы собираемся передать функции, используя специальную временную переменную, называемую параметром, когда мы объявляем и реализуем наши функции. Мы бы определили метод следующим образом:

// person.h
- (void)sayHelloToPerson(Person*)person;

В приведенном выше примере «-» указывает, что это метод экземпляра. Тип возврата - void, что означает, что этот конкретный метод не возвращает значение; просто выводит сообщение на консоль. Имя функции - sayHelloToPerson:, и она принимает один аргумент, который является указателем на объект Person, и этот аргумент представлен параметром под названием «человек». Причина, по которой аргументы имеют параметры, заключается в том, что нам нужен какой-то способ представить значение, которое передается в функцию, если мы хотим что-то сделать с ним внутри определения функции.

// person.m
- (void)sayHelloToPerson(Person*)person {
     NSLog(@"Hello there, %@"., person.name);
}

В приведенном выше примере мы регистрируем сообщение «Здравствуйте, человек‹ введите здесь имя человека ›». Пока мы передаем указатель на Person в качестве аргумента этого метода, мы можем получить имя этого человека и записать его в консоль. Другой способ думать об аргументе - это значение, которое мы присваиваем временной переменной (параметру), которую мы можем использовать для представления значения, которое мы передаем методу при его вызове. Если бы мы использовали этот метод в нашей программе, это выглядело бы примерно так:

// main.m
#import "Person.h"
Person *somePerson = [[Person alloc] init];
Person *anotherPerson = [[Person alloc] init];
anotherPerson.name = @"George";
[somePerson sayHelloToPerson:anotherPerson]; // "Hello there George." would be printed to the console

В приведенном выше примере мы создаем два объекта Person, один из которых называется somePerson, а другой - anotherPerson. Предполагая, что для класса Person определено свойство с именем «name», мы устанавливаем это значение в NSString «George». Затем мы отправляем сообщение sayHelloToPerson: в «somePerson» и в результате записываем сообщение «Привет, Джордж» в консоль с помощью NSLog. Когда мы отправляем сообщение sayHelloToPerson: в «somePerson», мы передаем «anotherPerson» в качестве аргумента метода, чтобы «somePerson» знал, с каким человеком в нашей программе поздороваться.

Прелесть Objective-C в том, что сообщение, отправленное в приведенном выше примере, читается почти как предложение. Буквально сказано: «SomePerson, передай привет человеку, anotherPerson». и объект somePerson делает именно это, когда получает сообщение. Хотя синтаксис поначалу может показаться немного забавным (особенно если вы переходите с другого языка программирования, такого как Ruby, JavaScript или Python), как только вы привыкнете к нему, вы начнете его ценить.

Методы, принимающие несколько аргументов

Что, если мы хотим создать методы, принимающие несколько аргументов? Это действительно не так уж и сложно. Давайте создадим еще один метод в нашем классе Person под названием addNumbersOne: AndTwo:, который дает нашим объектам person возможность, как следует из названия метода, складывать два числа.

Сначала мы включаем объявление метода в файл заголовка класса Person:

// Person.h
- (int)addNumbersOne:(int)firstNumber andTwo:(int)secondNumber;

А затем определите его внутри нашего файла реализации:

// Person.m
- (int)addNumbersOne:(int)firstNumber andTwo:(int)secondNumber {
     return firstNumber + secondNumber;
}

В приведенных выше примерах «firstNumber» и «secondNumber» - это имена параметров, которые представляют аргументы, которые принимает наша функция. Обратите внимание, как имя и тип параметров идут сразу после «:» в объявлении и реализации метода. Когда пользователь отправляет сообщение addNumbersOne: andTwo: объекту-персонажу, значения firstNumber и secondNumber будут такими, какими они были переданы в функцию во время отправки сообщения. Мы указали, что эти аргументы должны быть целыми числами, поэтому, если мы попытаемся отправить сообщение со значениями с любыми другими значениями, кроме целого, мы получим ошибку во время компиляции. Поскольку на самом деле у нас есть оператор return в этом методе, мы должны указать тип возвращаемого значения как в объявлении функции, так и в ее реализации. В данном случае это целое число, поэтому, если бы наш метод возвращал что-либо, кроме целого числа, мы получили бы ошибку во время компиляции. Если бы мы вызывали этот метод в реальной программе, он выглядел бы следующим образом:

// main.m
Person *accountant = [[Person alloc] init];
int sum = [accountant addNumbersOne:3 andTwo:4];
NSLog(@"%i", sum); // 7 would be printed to the console

В этом случае мы создаем новый объект Person с именем «бухгалтер» и отправляем ему addNumbersOne: andTwo: вместе с аргументами 3 и 4. Объект «бухгалтер» в ответ складывает два числа и возвращает сумму, которую мы сохраняем в переменной под названием «сумма». Затем мы записываем сумму в консоль с помощью функции NSLog.

Как легко отличить вызовы функций от методов

Любой простой способ узнать, вызываем ли мы функцию или метод, - это посмотреть, как она вызывается (причудливое слово программирования для «вызывается») в программе. Если мы видим вызов, заключенный в квадратные скобки, мы знаем, что вызывается метод, в противном случае это вызов функции.

// Calling a function that adds two numbers
add(1, 2) // 3 would be returned
// Calling a method that adds two numbers
[someObject addNumbers:1
                   and:2;]; // 3 would also be returned

Обратите внимание, как мы можем прояснить значения наших аргументов, поставив их в ряд через точку с запятой. Это обычная практика в Objective-C и особенно полезна, если вы вызываете метод, который принимает много аргументов.

Резюме

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