Objective-C

Objective-C, відомий також як Objective C, ObjC або Obj-C - компільований об'єктно-орієнтований мова програмування корпорації Apple, побудований на основі мови Сі і парадигм Smalltalk. Зокрема, об'єктна модель побудована в стилі Smalltalk - тобто об'єктами надсилаються повідомлення.

Мова Objective-C є надбезліччю мови Сі, тому Сі-код повністю зрозумілий компілятору Objective-C.

Компілятор Objective-C входить в GCC і доступний на більшості основних платформ. Мова використовується в першу чергу для Mac OS X ( Cocoa) і GNUstep - реалізацій об'єктно-орієнтованого інтерфейсу OpenStep. Також мова використовується для iOS ( Cocoa Touch).


1. Історія

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

Багато хто побачив в об'єктно-орієнтованому програмуванні потенційне рішення виниклої проблеми. З одного боку, Smalltalk використовували майже всі більш-менш складні системи. З іншого - використання віртуальних машин сильно гальмувало роботу системи і вимагало величезних ресурсів.

ObjC був створений Бредом Коксом на початку 1980-х в його компанії Stepstone. Він намагався вирішити проблему повторного використання коду.

Метою Кокса було створення мови, що підтримує концепцію software IC. Під цією концепцією розуміється можливість збирати програми з готових компонентів (об'єктів), подібно до того як складні електронні пристрої можуть бути легко зібрані з набору готових інтегральних мікросхем.

При цьому така мова має бути досить простим і заснованим на мові С, щоб полегшити перехід розробників на нього.

Однією з цілей було також створення моделі, в якій самі класи також є повноцінними об'єктами, підтримувалася б інтроспекція і динамічна обробка повідомлень.

Одержаний в результаті мову Objective-C виявився вкрай простий - його освоєння у С-програміста займе всього кілька днів. Він є саме розширенням мови С - в мову С просто додані нові можливості для об'єктно-орієнтованого програмування. При цьому будь-яка програма на С є програмою і на Objective-C.

Однією з відмінних рис Objective-C є його динамічність - цілий ряд рішень, зазвичай приймаються на етапі компіляції, тут відкладається безпосередньо до етапу виконання.

Ще однією з особливостей мови є те, що він message-oriented в той час як C + + - function-oriented. Це означає, що в ньому виклики методу інтерпретуються не як виклик функції (хоча до цього зазвичай все зводиться), а саме як посилка повідомлення (з ім'ям і аргументами) об'єкту, подібно до того, як це відбувається в Smalltalk-е.

Такий підхід дає цілий ряд плюсів - так, будь-якого об'єкта можна послати будь-яке повідомлення. Об'єкт може замість обробки повідомлення просто переслати його іншому об'єкту для обробки (так зване делегування), зокрема саме так можна легко реалізувати розподілені об'єкти (тобто об'єкти, що знаходяться в різних адресних просторах і навіть на різних комп'ютерах).

Прив'язка повідомлення до відповідної функції відбувається безпосередньо на етапі виконання.

Мова Objective-C підтримує нормальну роботу з метаінформація - так у об'єкта безпосередньо на етапі виконання можна запитати його клас, список методів (з типами переданих аргументів) і instance-змінних, перевірити, чи є клас нащадком заданого і підтримує він заданий протокол і т . п.

У мові є нормальна підтримка протоколів (тобто поняття інтерфейсу об'єкта та протоколу чітко розділені). Для об'єктів підтримується спадкування (не множинне), для протоколів підтримується множинне спадкування. Об'єкт може бути успадкований від іншого об'єкта і відразу декількох протоколів (хоча це скоріше не спадкування протоколу, а його підтримка).

На даний момент мова Objective-C підтримується компіляторами Clang і GCC (під управлінням Windows використовується у складі MinGW або cygwin).

Досить багато в мові перенесено на runtime -бібліотеку і сильно залежить від неї. Разом з компілятором gcc поставляється мінімальний варіант такої бібліотеки. Також можна вільно завантажити runtime-бібліотеку компанії Apple: Apple's Objective-C runtime.

Ці дві runtime-бібліотеки досить схожі (в основному відмінність полягає в іменах методів), далі приклади будуть орієнтуватися на runtime-бібліотеку від компанії Apple.


2. Синтаксис мови

У мові Objective-C для позначення об'єктів використовується спеціальний тип id. Змінна типу id фактично є покажчиком на довільний об'єкт. Для позначення нульового покажчика на об'єкт використовується константа nil (= NULL).

При цьому замість id можна використовувати і більш звичне позначення з явною вказівкою класу. Зокрема останнім дозволяє компілятору здійснювати деяку перевірку підтримки повідомлення об'єктами - якщо компілятор з типу змінної не може зробити висновок про підтримку об'єктом даного повідомлення, то він видасть попередження.

Тим самим мову підтримує перевірку типів, але в нестрогой формі (тобто знайдені невідповідності повертаються як попередження, а не помилки).

Для посилки повідомлень використовується наступний синтаксис:

 [  receiver message  ]  ; 

У цій конструкції receiver є покажчиком на об'єкт, а message - ім'ям методу.

На відміну від мови C + +, посилка повідомлення nil'у є законною операцією, завжди повертає нульове значення (nil).

Повідомлення може також містити параметри:

 [  myRect setOrigin  :  30.0  :  50.0  ]  ; 

У цьому прикладі ім'ям методу (повідомлення) є setOrigin ::. Зверніть увагу, що кожному переданому аргументу відповідає рівно одне двокрапка. При цьому в наведеному прикладі перший аргумент має мітку (текст перед двокрапкою), а другий - ні.

Мова Objective-C дозволяє постачати мітками кожен аргумент, що помітно підвищує читаність коду і знижує ймовірність передачі неправильного параметра. Саме такий стиль прийнятий більшістю розробників.

 [  myRect setWidth  :  10.0  height  :  20.0  ]  ; 

У цьому прикладі в якості імені повідомлення виступає setWidth: height:.

Також підтримується можливість передачі довільної кількості аргументів в повідомленні:

 [  myObject makeGroup  :  obj1, obj2, obj3, obj4,  nil  ]  ; 

Як і функції, повідомлення можуть повертати значення, при цьому на відміну від мови С, типом значення, що повертається за замовчуванням, є id.

 float  area  =  [  myRect area  ]  ; 

Результат одного повідомлення можна відразу ж використовувати в іншому повідомленні:

 [  myRect setColor  :  [  otherRect color  ]  ]  ; 

Як вже говорилося, в Objective-C класи самі є об'єктами. Основним завданням таких об'єктів (званих class objects) є створення екземплярів даного класу (це дуже схоже на патерн Abstract Factory).

При цьому саме ім'я класу відіграє подвійну роль - з одного боку воно виступає як тип даних (тобто він може бути використаний для опису покажчиків на об'єкти даного класу). А з іншого боку ім'я класу може виступати в якості об'єкта, якому надсилається повідомлення (в повідомленнях ім'я класу може брати участь тільки як receiver).

 Rect  *  myRect  =  [  [  Rect alloc  ]  init  ]  ; 

У мові Objective-C немає вбудованого типу для булевих величин, тому зазвичай такий тип вводиться штучно. Далі для логічних величин буде використовуватися тип BOOL з можливими значеннями YES і NO (як це робиться в операційних системах NextStep, Mac OS X).

Першим досить серйозним застосуванням мови Objective-C було його використання в операційній системі NextStep. Для цієї системи було написано велику кількість різних класів на Objective-C, багато з яких до цих пір використовуються в Mac OS X.

Імена всіх цих класів починаються з префікса NS, позначає свою приналежність до операційної системи NextStep. Зараз вони входять в бібліотеку Foundation, на якій будується програми для OS X і iOS.

З одним із них - NSString - ми зіткнемося в даній статті. Цей клас служить для роботи з рядками (при цьому в якості внутрішнього представлення символів використовується Юнікод).

Компілятор підтримує даний тип, автоматично переводячи конструкції виду @ "my string" в покажчик на об'єкт класу NSString, що містить дану рядок (точніше його підкласу, відповідного константним рядками).

Властивості

Припустимо, в класі Company існує instance-змінна name.

 @ Interface  Company  :  NSObject  {  NSString  *  name;  } 

Для доступу до неї ззовні найкраще скористатися властивостями, які з'явилися в Objective C 2.0. Для декларації властивостей використовується ключове слово @ property.

 @ Property  (  retain  )  NSString  *  name; 

У дужках перераховуються атрибути доступу до instance-змінної. Атрибути поділяються на 3 основні групи.

Імена акцессора і мутатори

  • getter = getterName, використовується для завдання імені функції, використовуваної для вилучення значення instance-змінної.
  • setter = setterName, використовується для завдання імені функції, використовуваної для установки значення instance-змінної.

Обмеження читання / запису

  • readwrite - у властивості є як акцессор, так і мутатори. Є атрибутом за замовчуванням.
  • readonly - у властивості є тільки акцессор.

Ці атрибути взаємовиключають одне одного. І остання група атрибути мутатори.

  • assign - для завдання нового значення використовується оператор присвоювання. Використовується тільки для POD-типів або для об'єктів, якими ми не володіємо.
  • retain - вказує на те, що для об'єкта, який використовується в якості нового значення instance-змінної, управління пам'яттю відбувається вручну (не забуваємо потім звільнити пам'ять).
  • copy - вказує на те, що для присвоювання буде використана копія переданого об'єкта.
  • weak - аналог assign при застосуванні режиму автоматичного підрахунку посилань. (ARC повинен підтримуватися компілятором)
  • strong - аналог retain при застосуванні режиму автоматичного підрахунку посилань. (ARC повинен підтримуватися компілятором)

При роботі під GC ніякої різниці між використанням assign, retain, copy немає. Для створення коду властивостей, у відповідності з тим як вони описані в декларації, можна скористатися автогенерації коду:

 @ Synthesize  name; 

Автоматично створений код - не завжди підходяще рішення і може знадобитися створення методів доступу до instance-змінним вручну.

Мова часто критикують за перевантажений, в порівнянні з іншими мовами, синтаксис. Однак при цьому нерідко відзначається його більш висока читабельність.


3. Створення нових класів

Всі нові директиви компілятору в мові Objective-C починаються з символу @.

Як і в C + +, опис класу і його реалізація розділені (зазвичай опис поміщається в заголовні файли з розширенням h, а реалізації - у файли з розширенням m).

Нижче наводиться загальна структура опису нового класу:

 @ Interface  ClassName  :  SuperClass  {  instance variable declarations  }  method declarations  @ End 

У версії runtime від Apple всі класи мають спільного предка - клас NSObject, що містить цілий ряд важливих методів.

Опис змінних нічим не відрізняється від опису змінних в структурах в мові С:

Якщо у вас не Apple, то швидше за все замість NSObject вам буде потрібно Object (# import ).

 @ Interface  Rect  :  NSObject  {  float  width;  float  height;  BOOL  isFilled;  NSColor  *  color;  }  @ End 

Описи же методів помітно відрізняються від прийнятих в C + + і дуже сильно схожі на описи методів в мові Smalltalk.

Кожний опис починається зі знака плюс або мінус. Знак плюс позначає, що даний метод є методом класу (тобто його можна посилати тільки class object'у, а не екземплярам даного класу). Фактично методи класу є аналогами статичних методів в класах в мові C + +.

Знак мінус служить для позначення методів об'єктів - екземплярів даного класу. Зверніть увагу, що в Objective-C всі методи є віртуальними, тобто можуть бути перевизначені.

Нижче приводяться описи можливих методів для класу Rect.

 @ Interface  Rect  :  NSObject  {  float  x, y;  float  width;  float  height;  BOOL  isFilled;  NSColor  *  color;  }  +  newRect;  -  (  void  )  display;  -  (  float  )  width;  -  (  float  )  height;  -  (  float  )  area;  -  (  void  )  setWidth  :  (  float  )  theWidth;  -  (  void  )  setHeight  :  (  float  )  theHeight;  -  (  void  )  setX  :  (  float  )  theX y  :  (  float  )  theY;  @ End 

Зверніть увагу, що ім'я методу може збігатися з ім'ям instance-змінної даного класу (наприклад, width і height).

Тип возвращаемого методом значення вказується у круглих дужках відразу ж після знака плюс або мінус (але перед назвою методу). Якщо тип не вказаний, то вважається, що повертається значення типу id.

Далі йде ім'я методу, де після кожного двокрапки задається тип аргументу (у круглих дужках) і сам аргумент.

Мова Objective-C дозволяє для аргументів методу задавати також один з наступних описувачів - oneway, in, out, inout, bycopy і byref. Дані описувачі служать для завдання напряму передачі даних і способу передачі. Їх наявність помітно спрощує реалізацію та роботу з розподіленими об'єктами (які були реалізовані в операційній системі NextStep до початку 90-х років минулого століття).

Метод, що приймає довільну кількість параметрів, може бути описаний таким чином:

 -  makeGroup  :  (  id  )  object, ...; 

Для підключення заголовного файлу в Objective-C замість директиви # include використовується директива # import, аналогічна # include, але гарантує, що даний файл буде підключений усього один раз.

У ряді випадків виникає необхідність в оголошенні того, що дане ім'я є ім'ям класу, але без явного його опису (така необхідність виникає при описі двох класів, кожен з яких посилається на інший клас).

У цьому випадку можна скористатися директивою @ class, яка проголошувала, що наступні за нею імена є іменами класів.

 @ Class  Shape, Rect, Oval; 

Реалізація методів класу виглядає наступним чином:

 # Import "ClassName.h"  @ Implementation  ClassName method implementations  @ End 

Нижче наводиться приклад реалізації методів класу Rect, описаного вище.

 # Import "Rect.h"  @ Implementation  Rect  +  newRect  {  Rect  *  rect  =  [  [  Rect alloc  ]  init  ]  ;  [  rect setWidth  :  1.0f  ]  ;  [  rect setHeight  :  1.0f  ]  ;  [  rect setX  :  0.0fy  :  0.0f  ]  ;  return  rect;  }  -  (  float  )  width  {  return  width;  }  -  (  float  )  height  {  return  height;  }  -  (  float  )  area  {  return  [  self width  ]  *  [  self height  ]  ;  }  -  (  void  )  setWidth  :  (  float  )  theWidth  {  width  =  theWidth;  }  -  (  void  )  setHeight  :  (  float  )  theHeight  {  height  =  theHeight;  }  -  (  void  )  setX  :  (  float  )  theX y  :  (  float  )  theY  {  x  =  theX; y  =  theY;  }  @ End 

Як видно з прикладу вище, в методах доступні всі instance-змінні. Однак, як і в C + +, є можливість управляти видимістю змінних (видимістю методів керувати не можна) за допомогою директив @ private, @ protected і @ public (діючих повністю аналогічно мові C + +).

 @ Interface  Worker  :  NSObject  {  char  *  name;  @ Private  int  age;  char  *  evaluation;  @ Protected  int  job;  float  wage;  @ Public  id  boss  } 

При цьому до public змінних класу можна звертатися безпосередньо, використовуючи оператор -> (наприклад objPtr -> fieldName).


4. Як працює механізм повідомлень

Компілятор переводить кожну посилку повідомлення, тобто конструкцію виду [object msg] в виклик функції objc_msgSend. Ця функція в якості свого першого параметра приймає покажчик на об'єкт-одержувач повідомлення, в якості другого параметра виступає т. зв. селектор, службовець для ідентифікації посилається повідомлення. Якщо в повідомленні присутні аргументи, то вони також передаються objc_msgSend як третій, четвертий і т. д. параметри.

Кожен об'єкт Objective-C містить в собі атрибут isa - покажчик на class object для даного об'єкта. class object автоматично створюється компілятором і існує як один примірник, на який через isa посилаються всі екземпляри даного класу.

Кожен class object обов'язково містить у собі покажчик на class object для батьківського класу (superclass) і dispatch table. Остання являє собою словник, сопоставляющий селекторам повідомлень фактичні адреси реалізують їх методів (функцій).

Таким чином, функція objc_msgSend шукає метод з даними селектором в dispatch table для даного об'єкта. Якщо його там немає, то пошук продовжується в dispatch table для його батьківського класу і т. д.

Якщо метод (тобто відповідна йому функція) знаходиться, то здійснюється його виклик з передачею всіх необхідних аргументів.

В іншому випадку об'єкту дається останній шанс обробити повідомлення перед викликом виключення - селектор повідомлення разом з параметрами "загортається" в спеціальний об'єкт типу NSInvocation і об'єкту надсилається повідомлення forwardInvocation:, де в якості параметра виступає об'єкт класу NSInvocation.

Якщо об'єкт підтримує forwardInvocation:, то він може або сам обробити посилається повідомлення, або переслати іншому об'єкту для обробки:

 -  (  void  )  forwardInvocation  :  (  NSInvocation  *  )  anInvocation  {  if  (  [  someOtherObject respondsToSelector  :  [  anInvocation selector  ]  ]  )  [  anInvocation invokeWithTarget  :  someOtherObject  ]  ;  else  ..........  } 

Для прискорення пошуку повідомлень по dispatch table використовується кешування, що дозволяє помітно знизити витрати на пересилання повідомлення. Також полегшує пошук методу за таблицями використання так званих селекторів замість звичайних імен. Зазвичай селектор являє собою 32-бітову величину, що дозволяє однозначно ідентифікувати метод.

Тип селектора позначається як SEL і існує ряд функцій і конструкцій, що дозволяють здійснювати перетворення імені в селектор і назад.

Так для отримання селектора повідомлення безпосередньо на ім'я служить конструкція @ selector ():

 SEL  setWidth  =  @ Selector  (  setWidth  :  )  ;  SEL  setPos  =  @ Selector  (  setPosition  :  y  :  )  ; 

Для отримання селектора по рядку символів (на етапі виконання) та перекладу селектора в рядок служать функції NSSelectorFromString і NSStringFromSelector:

 SEL  setWidth  =  NSSelectorFromString  (  @  "SetWidth:"  )  ;  NSString  *  methodName  =  NSStringFromSelector  (  setPos  )  ; 

Потужна підтримка метаінформації в Objective-C дозволяє прямо на етапі виконання перевірити чи підтримує об'єкт метод з даними селектором за допомогою посилки йому повідомлення respondsToSelector ::

 if  (  [  anObject respondsToSelector  :  @ Selector  (  setWidth  :  )  ]  )  [  anObject setWidth  :  200.0  ]  ; 

Досить легко можна послати повідомлення, відповідне даному селектору (без аргументів, з одним, двома або трьома аргументами), за допомогою методу performSelector:, performSelector: withObject:, performSelector: withObject: withObject: і performSelector :: withObject: withObject: withObject:.

 [  myObject performSelector  :  sel withObject  :  nil  ]  ; 

Зверніть увагу, що методи performSelector: завжди повертають значення типу id.

Можна отримати клас для даного об'єкта, пославши йому повідомлення class. Це повідомлення повертає клас у вигляді покажчика на об'єкт типу Class.

 Class  *  cls  =  [  anObject class  ]  ;  NSString  *  clsName  =  NSStringFromClass  (  cls  )  ; 

З іншого боку також можна легко отримати відповідний class object по імені класу:

 Class  *  cls  =  NSClassFromString  (  clsName  )  ; 

Кожен метод фактично являє собою функцію з двома невидимими аргументами - self і _cmd.

Перший є аналогом this, тобто вказує на сам об'єкт - одержувач повідомлення. Другий - містить селектор даного методу.

Аргумент self може використовуватися для посилки повідомлень самому собі, як наприклад в наступному методі:

 -  (  float  )  area  {  return  [  self width  ]  *  [  self height  ]  ;  } 

Однак крім self є ще одна величина, якої можуть надсилатися повідомлення - super. Насправді super не є нормальною змінної - це всього лише ще одне позначення для покажчика на поточний об'єкт. Але при посилці повідомлення super пошук методу починається не з dispatch table поточного об'єкта, а з dispatch table батьківського об'єкта.

Таким чином, надсилаючи повідомлення super ми тим самим викликаємо старі версії методів, перевизначені даним класом.

У мові Objective-C можна по селектору методу отримати адресу реалізує його функції (саме як функції мови С).

Така функція відрізняється від опису методу тільки вставкою в початок списку аргументів двох додаткових параметрів - покажчика на сам об'єкт (self) і селектора даного методу (_cmd).

Пославши об'єкту повідомлення methodForSelector: ми отримуємо у відповідь адресу реалізуючої цей метод функції.

 typedef  float  (  *  WidthFunc  )  (  id  ,  SEL  )  ;  typedef  void  (  *  SetWidthFunc  )  (  id  ,  SEL  ,  float  )  ; WidthFunc widthFunc  =  (  WidthFunc  )  [  myRect methodForSelector  :  @ Selector  (  width  )  ]  ; SetWidthFunc setWidthFunc  =  (  SetWidthFunc  )  [  myRect methodForSelector  :  @ Selector  (  setWidth  :  )  ]  ;  (  *  setWidthFunc  )  (  myRect,  @ Selector  (  setWidth  :  )  , 27.5f  )  ; 

Це дозволяє при необхідності багаторазового виклику одного і того ж методу у заданого об'єкту повністю уникнути всіх витрат, пов'язаних з пересиланням повідомлень.


5. Протоколи

Мова Objective-C містить повноцінну підтримку протоколів (в C + + це абстрактний клас, який також інколи прийнято називати інтерфейсом). Протокол являє собою просто список описів методів. Об'єкт реалізує протокол, якщо він містить реалізації всіх методів, описаних в протоколі.

Протоколи зручні тим, що дозволяють виділяти загальні риси у різнорідних об'єктів і передавати інформацію про об'єкти заздалегідь невідомих класів.

Найпростіше опис протоколу виглядає наступним чином:

 @ Protocol  ProtocolName method declarations  @ End 

Так протокол Serializable може бути описаний таким чином:

 @ Protocol  Serializable  -  (  id  )  initWithCoder  :  (  NSCoder  *  )  coder;  -  (  void  )  encodeWithCoder  :  (  NSCoder  *  )  coder;  @ End 

Протокол може бути успадкований від довільної кількості інших протоколів:

 @ Protocol  MyProto  

Точно також можна при описі класу задати не тільки батьківський клас, але і набір протоколів:

 @ Interface  MyClass  :  SuperClass  

Для перевірки під час виконання програми, чи підтримується об'єктом заданий протокол об'єктів, можна використовувати повідомлення conformsToProtocol ::

 if  (  [  myObject conformsToProtocol  :  @ Protocol  (  Serializable  )  ]  )  [  myObject encodeWithCoder  :  myCoder  ]  ; 

Крім того, ім'я протоколу (протоколів) можна використовувати при описі змінних для явної вказівки компілятору про підтримку відповідними об'єктами протоколу (протоколів).

Так, якщо змінна myObject містить покажчик на об'єкт заздалегідь невідомого класу, але при цьому задовольняє протоколам Serializable і Drawable, то її можна описати таким чином:

 id   MyObject; 

Точно так само, якщо заздалегідь відомо, що myObject буде містити покажчик на об'єкт, успадкований від класу Shape і підтримуючого протокол Serializable, то цю змінну можна описати таким чином:

 Shape   *  myObject; 

Зверніть увагу, що подібне опис служить тільки для повідомлення компілятору, які повідомлення підтримує даний об'єкт.

Як і класи, всі протоколи в Objective-C представлені за допомогою об'єктів (класу Protocol):

 Protocol  *  myProto  =  @ Protocol  (  Serializable  )  ; 

Для попереднього оголошення протоколів можна використовувати наступну конструкцію:

 @ Protocol  MyProto, Serializable, Drawable; 

Ця конструкція повідомляє компілятор про те, що MyProto, Serializable і Drawable є іменами протоколів, які будуть визначені пізніше.


6. Обробка виключень

У мові Objective-C підтримується обробка виключень, дуже схожа на використовувану в мовах C + + і Java.

Для цього служать директиви @ try, @ catch, @ finally і @ throw.

 Cup  *  cup  =  [  [  Cup alloc  ]  init  ]  ;  @ Try  {  [  cup fill  ]  ;  }  @ Catch  (  NSException  *  exc  )  {  NSLog  (  @  "Exception caught:% @"  , Exc  )  ;  }  @ Catch  (  id  exc  )  {  NSLog  (  @  "Unknown exception caught"  )  ;  }  @ Finally  {  [  cup release  ]  ;  } 

Для запуску винятку використовується директива @ throw, в якості аргументу бере покажчик на об'єкт-виняток. Зазвичай в Mac OS X / NextStep для цієї мети використовуються об'єкти класу NSException.

 NSException  *  exc  =  [  NSException  exceptionWithName  :  @  "My-exception"  reason  :  @  "Unknown-error"  userInfo  :  nil  ]  ;  @ Throw  exc; 

Усередині @ catch-блоків директива @ throw може використовуватися без параметра для повторного запуску оброблюваного виключення (rethrowing exception).


7. Синхронізація

Мова Objective-C підтримує синхронізацію для багатопоточних додатків. За допомогою директиви @ synchronized () можна захистити фрагмент коду від одночасного виконання відразу декількома потоками.

@ Synchronized () бере на вхід покажчик на об'єкт мови Objective-C (можна використовувати для цієї мети будь-який об'єкт, у тому числі і self), який грає роль мьютекса (mutex).

При спробі потоку розпочати виконання захищеного фрагмента перевіряється, чи виконується вже цей фрагмент небудь потоком. Якщо так, то порівнюються об'єкти, передані цими потоками в @ synchronized ().

Якщо ці покажчики збігаються, то потік, який намагається увійти в захищений блок, буде припинений (suspended) до тих пір, поки перший потік не вийде з блоку. Тоді виконання другого потоку продовжиться, і вже він "заборона" цей блок для всіх інших потоків.

Наявність подібної можливості помітно полегшує життя при написанні багатопоточних додатків, коли необхідно відстежувати спроби одночасного зміни одних і тих же даних відразу декількома потоками.

 -  (  void  )  criticalMethod  {  @ Synchronized  (  self  )  {  / / Perform modifications to shared objects  . . .  }  } 

8. Створення та знищення об'єктів

У самій мові Objective-C немає спеціальних команд для створення та знищення об'єктів (подібних new і delete). Це завдання лягає на runtime-бібліотеку і реалізується за допомогою механізму посилки повідомлень.

Реально використовуваної і найбільш широко поширеною схемою створення і знищення об'єктів в Objective-C є використовувана в операційних системах NextStep і Mac OS X, яка і буде описана нижче.

Створення нового об'єкта розбивається на два кроки - виділення пам'яті і ініціалізація об'єкта. Перший крок реалізується методом класу alloc (реалізованому в класі NSObject), який виділяє необхідну кількість пам'яті (даний метод використовується для виділення пам'яті не тільки для об'єктів класу NSObject, але і будь-якого успадкованого від нього класу). При цьому в атрибут isa записується покажчик на class object відповідного класу.

Зверніть увагу, що повідомлення alloc надсилається class object-у необхідного класу і це повідомлення повертає покажчик на виділену під об'єкт пам'ять.

Власне сама ініціалізація об'єкта (тобто установка значень його instance-змінних, виділення додаткових ресурсів і т. п.) здійснюється іншими методами, за традицією імена цих методів починаються з init. Зазвичай таке повідомлення надсилається відразу ж після повідомлення alloc, за адресою, повернутому цим повідомленням.

 id  anObject  =  [  [  Rectangle alloc  ]  init  ]  ; 

Наведена вище конструкція є правильним способом створення об'єкта. Зверніть увагу, що наступна конструкція може в ряді випадків не працювати:

 id  anObject  =  [  Rectangle alloc  ]  ;  [  anObject init  ]  ; 

Це пов'язано з тим, що для ряду класів метод init може повернути зовсім інший покажчик (а не self).

Найпростішими прикладами того, коли може виникати подібна ситуація, є Сінглтон (тоді, якщо один примірник класу вже існує, то метод init звільнить виділену alloc'ом пам'ять і поверне покажчик на вже створений єдиний екземпляр) і кешування об'єктів, коли для збільшення продуктивності, виділення об'єктів відбувається відразу блоками і об'єкти не знищуються, а зберігаються для перевикористання.

При створенні нового класу зазвичай немає необхідності перевизначати метод alloc, а от необхідність перевизначення методу init виникає досить часто.

Зверніть увагу, що метод (и) init є звичайним методом, нічим не виділяється серед інших (на відміну від C + +, де конструктор - це особливий метод, у якого, наприклад, не можна взяти адресу).

Тому при створенні нового класу і методу init виклик перевизначеного методу init (за допомогою [super init]) повинен бути зроблений явно на самому початку методу.

Досить часто у об'єктів буває відразу кілька методів, що починаються з init, наприклад init, initWithName:, initWithContentsOfFile: і т. д.

Усталеною практикою в такому випадку є виділення серед усіх init-методів одного, званого designated initializer. Всі інші init-методи повинні викликати його і тільки він викликає успадкований init метод.

 -  (  id  )  initWithName  :  (  const  char  *  )  theName  / / Designated initializer  {  self  =  [  super init  ]  ;  / / Call inherited method  if  (  self  )  {  name  =  strdup  (  theName  )  ;  }  return  self;  }  -  (  id  )  init  {  return  [  self initWithName  :  ""  ]  ;  } 

У ряді випадків виявляється зручним поєднати виділення пам'яті і ініціалізацію об'єкта в один метод (класу), наприклад в класі NSString є ряд методів класу, повертають вже готовий (проініціалізувати) об'єкт:

 +  (  id  )  stringWithCString  :  (  const  char  *  )  cString encoding  :  (  NSStringEncoding  )  enc  +  (  id  )  stringWithFormat  :  (  NSString  *  )  format, ... 

Mac OS X (як і NextStep) для управління часом життя об'єктів використовують reference counting - кожен об'єкт містить усередині себе деякий лічильник, при створенні встановлюваний в одиницю.

Посилка об'єкту повідомлення retain збільшує значення цього лічильника на одиницю (так всі контейнерні класи бібліотеки Foundation при приміщенні в них об'єкта, посилають йому повідомлення retain).

Усталеною практикою є посилка об'єкту повідомлення retain усіма, зацікавленими в ньому сторонами (об'єктами), тобто якщо ви запам'ятовуєте посилання на об'єкт, то слід послати йому повідомлення retain.

Коли об'єкт перестає бути потрібен, то йому просто посилається повідомлення release.

Дане повідомлення зменшує значення лічильника на одиницю і, якщо це значення стало менше одиниці, знищує даний об'єкт.

Перед знищенням об'єкта йому надсилається повідомлення dealloc, що дозволяє об'єкту справити свою деініціалізацію. При цьому це також є звичайним повідомленням і в ньому Ви явно повинні в кінці викликати успадковану реалізацію через [super dealloc].

 -  (  void  )  dealloc  {  . . .  [  super dealloc  ]  ;  } 

9. Управління пам'яттю

9.1. Базові принципи

Управління пам'яттю в Objective C базується на принципі "володіння об'єктом". Основні правила управління пам'яттю в Objective C можна записати так:

  • Для отримання об'єкта у володіння необхідно викликати метод, що містить у назві "alloc", "new" або "copy". Наприклад, alloc, newObject, mutableCopy.
  • Для звільнення об'єкта, який був отриманий за допомогою перерахованих вище функцій, необхідно викликати функцію "release" або "autorelease". У всіх інших випадках звільнення об'єкта не вимагається.
  • Якщо отриманий об'єкт повинен бути збережений, необхідно або стати його власником (викликавши retain), або створити його копію (виклик, що містить у назві "copy").

Дані правила базуються на угоді по іменування в Objective C і, в той же час, самі є основою цієї угоди.


9.2. Базові принципи на практиці

Припустимо, в програмі існує клас Company, у якого є метод workers.

 @ Interface  Company  :  NSObject  {  NSArray  *  workers;  }  -  (  NSArray  *  )  workers;  @ End 

Розглянемо невеликий приклад використання такого класу:

 Company  *  company  =  [  [  Company alloc  ]  init  ]  ;  / / ...  NSArray  *  workers  =  [  company workers  ]  ;  / / ...  [  company release  ]  ; 

Так як об'єкт класу Company створюється явно, він повинен бути вилучений після закінчення використання ([company release]). У той же час, назву методу workers не говорить про те, хто повинен видаляти масив. У такій ситуації вважається, що списком працівників управляє об'єкт Компанія та його видаляти не потрібно.

Convenience конструктори

Багато класи дозволяють поєднати створення об'єкта з його ініціалізацією за допомогою методів, званих convenience конструктори; такі методи зазвичай називаються + className ... Можна припустити, що викликає сторона відповідальна за управління часом життя об'єкта, але подібна поведінка суперечило б угодою по іменування в Objective C.

 Company  *  company  =  [  Company company  ]  ;  [  company release  ]  ; 

У наведеному коді виклик [company release] недопустимий, оскільки в даному випадку управління часом життя об'єкта повинно здійснюватися за допомогою autorelease пулу.

Нижче наводиться приклад коректної реалізації методу company:

 +  (  Company  *  )  company  {  id  ret  =  [  [  Company alloc  ]  init  ]  ;  return  [  ret autorelease  ]  ;  } 
autorelease

Повернемося до методу workers класу Company. Так як повертається масив, часом життя якого викликає сторона не керує, реалізація методу workers буде виглядати приблизно так:

 -  (  NSArray  *  )  workers  {  NSArray  *  copy  =  [  [  NSArray  alloc  ]  initWithArray  :  workers  ]  ;  return  [  copy autorelease  ]  ;  } 

Виклик autorelease додає об'єкт copy в autorelease пул, внаслідок чого повертається об'єкт отримає повідомлення release при видаленні пулу, в який він був доданий. Якщо об'єкту, доданого в autorelease пул, послати повідомлення release самостійно, при видаленні autorelease пулу виникне помилка.

Повернення об'єкта по посиланню

У ряді випадків об'єкти повертаються по посиланню, наприклад, метод класу NSData initWithContentsOfURL: options: error: в якості параметра error приймає (NSError **) errorPtr. У цьому випадку так само працює угоду по іменування, з якого випливає, що явного запиту на володіння об'єктом немає, відповідно, видаляти його не потрібно.

Видалення об'єктів

Коли лічильник посилань об'єкта стає рівним нулю, об'єкт віддаляється. При цьому у об'єкту викликається метод - (void) dealloc. Якщо в об'єкті містяться якісь дані, їх необхідно видалити в цій функції.

 -  (  void  )  dealloc  {  [  workers release  ]  ;  [  super dealloc  ]  ;  } 

Після того, як всім змінним класу було надіслано повідомлення release, необхідно викликати метод dealloc базового класу. Це єдиний випадок, в якому допустимо виклик методу dealloc безпосередньо.

Не існує ніяких гарантій щодо часу виклику методу dealloc. У ряді випадків він взагалі може не викликатися при завершенні роботи програми для економії часу, так як по завершенні програми ОС в будь-якому випадку звільнить виділену пам'ять. Відповідно, в методі dealloc не повинно розташовуватися ніяких методів, що відповідають за закриття сокетів, файлів і т. п.


9.3. Autorelease pool

Autorelease пул використовується для зберігання об'єктів, яким буде надіслано повідомлення release при видаленні пулу. Для того, щоб додати об'єкт у autorelease пул, йому необхідно відправити повідомлення autorelease.

У додатках Cocoa autorelease пул завжди доступний за умовчанням. Для не-AppKit додатків необхідно створювати і керувати часом життя autorelease пулу самостійно.

Autorelease пул реалізується класом NSAutoreleasePool.

 int  main  (  int  argc,  const  char  *  argv  [  ]  )  {  NSAutoreleasePool  *  pool  =  [  [  NSAutoreleasePool  alloc  ]  init  ]  ; Company  *  company  =  [  Company company  ]  ;  NSArray  *  workers  =  [  company workers  ]  ;  [  pool drain  ]  ;  return  0  ;  } 

Видалити об'єкти з autorelease пулу можна не тільки за допомогою відправки пулу повідомлення release, але і за допомогою повідомлення drain. Поведінка release і drain в середовищі з підрахунком посилань ідентично. Але у випадку роботи в GC середовищі drain викликає функцію objc_collect_if_needed.

Autorelease пул в многопоточной середовищі

У Cocoa для кожного з потоків створюється свій власний autorelease пул. По завершенні потоку autorelease пул знищується і всім містяться в ньому об'єктам надсилається повідомлення release.

Autorelease пул головного потоку періодично перестворює з метою зменшення використовуваної пам'яті додатком. У всіх інших потоках займатися пересозданія autorelease пулу необхідно самостійно, що вкрай актуально для довгоживучих потоків.


9.4. Копіювання об'єктів

Всі об'єкти в Objective C потенційно підтримують копіювання. Для того, щоб створити копію об'єкта, необхідно викликати метод copy, визначений в класі NSObject. Для створення копії буде викликаний метод copyWithZone протоколу NSCopying. NSObject не має підтримки цього протоколу і при необхідності протокол NSCopying повинен бути реалізований в класах-спадкоємців.

Копії бувають двох видів: легка копія (shallow copy) і повна копія (deep copy). Різниця між цими копіями полягає в тому, що при створенні легкої копії копіюються не дані, а посилання на об'єкт з даними. У разі повної копії копіюється об'єкт з даними.

Приклад реалізації

Реалізація копіювання може відрізнятися в залежності від того, чи підтримує клас-батько протокол NSCopying. Приклад коду для ситуації, коли батько не реалізує протокол NSCopying:

 @ Interface  Company  :  NSObject    {  NSString  *  name;  }  @ Property  (  retain  )  NSString  *  name;  -  (  id  )  copyWithZone  :  (  NSZone  *  )  zone;  @ End  @ Implementation  Company  @ Synthesize  name;  -  (  id  )  copyWithZone  :  (  NSZone  *  )  zone  {  id  copy  =  [  [  [  self class  ]  allocWithZone  :  zone  ]  init  ]  ;  [  copy setName  :  [  self name  ]  ]  ;  return  copy;  }  @ End 

Якщо батько підтримує протокол NSCopying, реалізація буде дещо іншою: виклик allocWithZone замінюється на copyWithZone.

 id  *  copy  =  [  super copyWithZone  :  zone  ]  ; 
Копіювання незмінних об'єктів

Для immutable об'єктів створення копії недоцільно, і можна обмежитися відправкою самому собі повідомлення retain.

 -  (  id  )  copyWithZone  :  (  NSZone  *  )  zone  {  return  [  self retain  ]  ;  } 

10. Категорії

Мова Objective-C володіє можливістю додавати нові методи до вже існуючих класів. Аналогічною можливістю володіє мову Ruby, C #, JavaScript та інші. При цьому не потрібно исходников класу і додані методи автоматично стають доступними всім класам, успадкованим від змінюваного. Так можна додати новий метод класу NSObject і цей метод автоматично додасться в усі інші класи.

Механізм, що дозволяє розширювати вже існуючі класи (шляхом додавання нових методів, нові instance-змінні додати таким чином не можна), називається категорією.

Категорія має своє ім'я, список методів і ім'я класу, який вона розширює. Опис категорії має наступний вигляд:

 # Import "ClassName.h"  @ Interface  ClassName  (  CategoryName  )  methods declarations  @ End 

Реалізація категорії виглядає наступним чином:

 # Import "CategoryName.h"  @ Implementation  ClassName  (  CategoryName  )  methods bodies  @ End 

За допомогою категорій можна створювати властивості (property), які будуть доступні тільки для читання іншим класам і readwrite усередині свого класу:

 @ Interface  ClassName  {  BOOL  flag;  }  @ Property  (  assign, readonly  )  BOOL  flag;  @ End  # Import "ClassName"  @ Implementation  ClassName  (  )  / / Порожня категорія  @ Property  (  assign, readwrite  )  BOOL  flag;  @ End  @ Implementation  ClassName  @ Synthesize  flag;  -  (  void  )  someAction  {  self.flag  =  YES  ;  }  @ End 

Крім усього іншого категорії можна використовувати для того, щоб забезпечити реалізацію класом якого нового протоколу, наприклад:

 @ Protocol  Printable  / / Сутності, які можна роздрукувати  -  (  void  )  print;  @ End  @ Interface  NSString  (  Printable  )    / / Додаємо системному класу NSString можливість бути роздрукованим  @ End  @ Implementation  NSString  (  Printable  )  / / Реалізуємо нову функціональність  -  (  void  )  print  {  NSLog  (  @  "Мене роздрукували% @!"  , Self  )  ;  }  @ End 

Це позбавляє від необхідності писати клас-адаптер PrintableString для NSString


11. Class objects і Objective-C runtime

При компіляції програми на мові Objective-C компілятор для кожного введеного класу автоматично створює так званий class object - повноцінний об'єкт, що містить у собі всю інформацію про даному класі, включаючи назву, суперклас, список методів і instance-змінних.

При цьому такий об'єкт є повноцінним об'єктом, тобто йому можна посилати повідомлення, передавати як параметр.

Однією з особливостей class object'а є те, що він підтримує всі методи класу NSObject, тобто коли йому надсилається повідомлення, то спочатку йде пошук серед методів класу, а якщо метод не знайдений, то пошук продовжується серед instance-методів класу NSObject.

Ще однією цікавою особливістю є можливість ініціалізації class object'ов - на початку роботи програми кожному class object'у надсилається повідомлення (класу) initialize.

Це повідомлення гарантовано надсилається кожному class object'у, причому всього один раз і до того, як йому буде надіслано будь-яке інше повідомлення. Найпростішим прикладом застосування такого повідомлення є реалізація Singleton'ов - саме в методі initialize слід створити той самий єдиний екземпляр об'єкта і запам'ятати його в static-змінної.

Якщо подивитися на Objective-C runtime від Apple, то ми знайдемо велику кількість С-функцій, службовців для роботи з класами (безпосередньо під час виконання програми).

Найбільш цікавими є наступні:

 Method class_getInstanceMethod  (  Class  aClass,  SEL  aSelector  )  ; Method class_getClassMethod  (  Class  aClass,  SEL  aSelector  )  ;  struct  objc_method_list  *  class_nextMethodList  (  Class  theClass,  void  **  iterator  )  ;  void  class_addMethods  (  Class  aClass,  struct  objc_method_list  *  methodList  )  ;  void  class_removeMethods  (  Class  aClass,  struct  objc_method_list  *  methodList  )  ;  unsigned  method_getNumberOfArguments  (  Method method  )  ;  unsigned  method_getSizeOfArguments  (  Method method  )  ;  unsigned  method_getArgumentInfo  (  Method method,  int  argIndex,  const  char  **  type,  int  *  offset  )  ; Ivar class_getInstanceVariable  (  Class  aClass,  const  char  *  aVariableName  )  ; 

Функція class_getInstanceMethod повертає покажчик на структуру (objc_method), описує заданий instance-метод даного класу.

Функція class_getClassMethod повертає покажчик на структуру (objc_method), описує заданий метод даного класу.

Функція class_nextMethodList повертає один із списків методів для заданого класу. Наведений нижче фрагмент коду дозволяє перебрати всі методи для даного класу.

 void  *  iterator  =  0  ;  struct  objc_method_list  *  methodList;  / /  / / Each call to class_nextMethodList returns one methodList  / /  methodList  =  class_nextMethodList  (  classObject,  &  iterator  )  while  (  methodList  ! =  nil  )  {  / / ... Do something with the method list here ...  methodList  =  class_nextMethodList  (  classObject,  &  iterator  )  ;  } 

Функція class_addMethods дозволяє додавати нові методи до заданому класу.

Функція class_removeMethods дозволяє прибирати методи із заданого класу.

Функція method_getNumberOfArguments Повертає кількість аргументів для заданого методу.

Функція method_getSizeOfArguments повертає розмір місця на стеку, займаного всіма аргументами даного методу.

Функція method_getArgumentInfo повертає інформацію про один з аргументів для заданого методу ..

Функція class_getInstanceVariable повертає інформацію про instance-змінної класу у вигляді покажчика на структуру objc_ivar.

Для кодування інформації про типи використовується спеціальне строкове представлення, однозначно зіставляє кожному типу даних деяку рядок. Явно отримати такий рядок для довільного типу можна за допомогою конструкції @ encode ().

 char  *  buf1  =  @ Encode  (  int  **  )  ;  char  *  buf2  =  @ Encode  (  struct  key  )  ;  char  *  buf3  =  @ Encode  (  Rectangle  )  ; 

12. Різне

Головним джерелом інформації з мови є офіційний сайт Apple. [1]

Корисну інформації з мови Objective-C можна знайти в ньюс-групи [2] і архівах списку розсилки [3].

Проект GNUstep являє собою спробу створення open-source бібліотек на Objective-C, що є аналогом бібліотек Foundation і AppKit в NextStep і Mac OS X.

На сайті проекту GNUstep Ви знайдете багато прикладів використання мови Objective-C і додатків, написаних на ньому.

Оскільки компілятор gcc підтримує мову Objective-C, то практично кожен дистрибутив GNU / Linux дозволяє встановити підтримку для нього.

Для роботи з Objective-C під MS Windows можна пользоватьcя емуляторами POSIX середовища (mingw, cygwin) або встановленої підтримкою середовища POSIX (MS SFU, Services For Unix, безкоштовно доступне на www.microsoft.ru - Сервіси Microsoft Windows для UNIX), необхідно тільки встановити бібліотеку для підтримки мови.


Примітки

  1. Apple Developer - developer.apple.com /
  2. comp.lang.objective-c - groups.google.com / group / comp.lang.objective-c
  3. Objc-language - lists.apple.com / mailman / listinfo

Література

  • Майкл Приват, Роберт Уорнер. Розробка додатків для Mac OS X Lion. Програмування на Objective-C в Xcode = Beginning Mac OS X Lion Apps Development. - М .: Вільямс, 2012. - 384 с. - ISBN 978-5-8459-1789-8
  • Девід Чіснолл. Objective-C. Кишеньковий довідник = Objective-C Phrasebook. - 2-ге вид. - М .: Вільямс, 2012. - 352 с. - ISBN 978-5-8459-1777-5