Objective-C для Java-программистов

Как все начиналось

Как как. А как обычно. В один прекрасный момент у нас завелся замечательный зверь, под названием iPod-Touch.

Игрушка для программиста

Наш iPod. Игрушка для программиста

Так как в нашей “семье” программистов ни одно такое приобретение просто так не проходит, то меня “пересадили” с  Flash’а, на   iPod.  А вообще я на  Java-е программирую. Впрочем это не важно. Думаю, что это не последний язык, с которым мне пришлось столкнуться.

Я описал некоторые сложности и особенности, которые, считаю, необходимо знать, при переходе с Java на  Objective-C  для разработки приложений на iPhone(iPod). Я рассмотрел несколько вопросов, таких как, откуда взялся этот  Objective-C и с чем его едят, особенности объектно ориентированного подхода в нем, как тут называются вещи, к котором так привыкли   Java-программисты (вызовы методов, интерфейсы, и т.п.), на которые следует обратить внимание, чтобы не наступить на те же грабли, что когда-то наступил и я (грабли очень устали, и просили их больше по возможности не тревожить).

 

Objective-C

Прочитать про него можно вот на википедии. А мысли по поводу того, насколько он  Objective, я буду выкладывать тут. Я сначала подумал “Слава тебе, ‘ТОТ, КТО ГДЕ-ТО ЕСТЬ’”, объектно-ориентированный С! Забываем про вызовы каких-то левых функций непонятно откуда, про множественное наследование, и про другие фичи, которые мне не нравились в С, здесь все будет нормально. Не тут-то было! Первым на меня набросилось

@class definition

Во-первых, каждый класс лежит в двух файлах, в одном, обычно имеющем расширение “.h” описывается интерфейс(@interface) класса (просьба не путать с  Java-интерфейсами), в другом, имеющем расширение “.m “, описывается реализация( @implementation) класса. В принципе, это не так критично, можно достаточно быстро привыкнуть. Выглядит это примерно следующим образом

[sourcecode language='c']
//MyClass.h
@interface MyClass : SuperClass {
//определение переменных экземпляра
}
//определение методов
@end

....

//MyClass.m
#include 
@implementation MyClass
// реализация методов
// реализация методов протокола
[/sourcecode]

Теперь пару слов об интерфейсах, которые описываются в файлах-заголовках. Это совсем не те интерфейсы, что есть в Java-е. Если проводить аналогии, то интерфейсы в Objective-C это абстрактные классы в Java-e. А вот аналогом джавовских интерфейсов в обжектив-цы есть протоколы(@protocol), причем они могут иметь опциональные методы, то есть те, которые не обязательно реализовывать в конечном классе. После джавы очень непривычно, но “маємо, те що маємо”(с). Итак теперь пару слов о такой вещи как

@protocol

Как я уже только что говорил, протоколы — это прямая аналогия интерфейсам в джаве. То есть, они просто содержат описание методов, которые должны быть реализованы в классе, имплементирующем   протокол(интерфейс). Из особенностей можно отметить необязательные  @optional методы, которые могут быть описаны в протоколе(интерфейсе). Вот синтаксис их описания

[sourcecode language='c']
// описывается в любом .h файле
@protocol MyProtocol
// определение методов
@optional
// определение необязательных методов
//
@end

// А вот и пример
@protocol MyProtocol
- (id)  doRelax: (NSString *) relaxType;
// вот это очень удобно
@optional
- (void) doWork: (NSString *) workType;
@end
[/sourcecode]

Где-то так. Самое интересное, что компилятор не особо ругается, если не все методы интерфейса реализованы. Просто предупредит. Ошибка вылезет на этапе выполнения, и то, если будет вызван не реализованный метод. С этим помогает бороться @optional директива — и компилятор спокоен, и ошибок не будет. Дальше я не буду особо вдаваться в синтаксис, ибо это очень хорошо описано на википедии. Особенности, особенности, и еще раз особенности. Еще одной очень полезной вкусностью в  Objective-C есть

@property и @synthesize

Очень часто в  Java-проектах можно найти файлы-контейнеры, которые просто содержат информацию, и не имеют никаких методов, кроме методов инициализации и получения/изменения данных — то есть ровно столько же сеттеров и геттеров, сколько и переменных в классе. Обычно описываются переменные, а потом автогенерируются все сеттеры и геттеры. Класс становится страшно большим, и самое обидное, что никто не смотрит на все эти гет-сет методы. Зачем, если можно просто посмотреть на переменную, и понять, что в 999 из 1000 случаев, в классе есть такой геттер/сеттер. В общем, директива компилятора @property имеет под собой функцию описания доступа к переменным класса. «Тебе страшно? Мне — нет.»(с) Карлсон.
На примере станет понятнее.

[sourcecode language='c']

//Restaurant.h
@interface Restaurant : NSObject {
  NSObject *name;
  NSString *url;
  NSObject *zipPrivate;
}

  @property(nonatomic,readonly) NSObject* name;
  @property(nonatomic,retain, getter=getTheURL) NSString *url;
  @property(nonatomic,retain) NSObject *zip;

@end

//Restaurant.m
#import "Restaurant.h"

@implementation Restaurant
  @synthesize name;
  @synthesize url;
  @synthesize zip = zipPrivate;

  - (void)dealloc {
    [url release];
    [zip release];
    [super dealloc];
  }
@end
[/sourcecode]

Вот и все. Как видим, тут тоже не обошлось без предварительного объявления. В результате получаем класс, с набором сеттеров и геттеров, плюс в добавок ко всему получаем возможность доступаться к свойствам класса через точку

[sourcecode language='c']
  restaurant.name = @"myRest";
  [restaurant setName:@"myRest"];
[/sourcecode]

а теперь о том, как можно параметризировать генерацию свойств класса.

  • nonatomic — параметр, чем-то похожий на synchronized в Java, вернее на его отсутствие. Если мы не пишем многопоточные приложения , то ставим nonatomic — не ошибемся
  • readonly — вроде должно быть понятно по смыслу. Если нет — то это параметр, говорящий о том, что свойство класса только для чтения. Сеттер не генерируется, при попытке присвоить что-нибудь свойству класса возникает ошибка на этапе компиляции.
  • retain -  ой.. как бы так сказать… Указывать надо, если мы хотим, чтоб значение сохранялось, вне зависимости от того, что творится в системе. Параметр говорит о том, что присваивание будет происходить по ссылке + мы захватываем ссылку, и не даем garbage collectorу, в случае чего, освободить память по этой ссылке.  Не забываем «отпускать» такие свойства в методе «dealloc».
  • сopy — присваивание происходит путем копирования данных. Объект, который присваивается должен реализовывать протокол NSCopying. В джаве, это выглядело бы как b = a.clone();
  • assign(по умолчанию) – простое присваивание по значению. Отличие от retain в том, что в этом случае ,    garbage collector может спокойно освободить память, на которую эта ссылка указывает (если это ссылка, конечно;).

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

А теперь чуть-чуть про @synthesize. Это директива компилятора, которая где-то за кулисами вставляет геттер и сеттер. Обычно имя свойства и имя переменной совпадают,  но, иногда, приходится делать так, чтобы свойство работало с переменной с другим именем, в примере таким свойством есть zip, которое работает с пременной zipPrivate.

Вот такая вот печенька. Очень удобно. Хотелось бы, чтобы это в каком-нибудь виде появилось в Java. Было бы замечательно. Но, пока это все — мечты. А теперь, о несильно приятных вещах для Java-программистов.

Освобождение памяти, да и просто работа с памятью

Да. Память нужно освобождать. Забудьте о том, что кто-то это будет делать за вас. Вы, вы и только вы ответственны за ее освобождение. Даешь свободу памяти собственными руками! С другой стороны, нужно следить за тем, чтобы память, которую вы используете, не освободил кто-нибудь другой, ну или вы не освободили память, которую кто-нибудь использует

Итак,  выделение памяти под объект осуществляется методом  alloc, освобождение -   dealloc. Метод dealloc САМИ РУКАМИ НЕ ВЫЗЫВАЕМ, вместо этого вызываем метод release.
Это работает следующим образом: у каждого объекта есть счетчик ссылок, который устанавливается в 1 при создании(alloc). Каждый метод   retain увеличивает этот счетчик на 1, каждый метод release — уменьшает на 1. Если счетчик равен нулю — то память освобождается (насколько я заметил, освобождается сразу же).

А теперь, главное правило, которое сильно поможет — вы отвечаете за освобождение памяти только тех объектов, которые создали сами, либо у которых вы вызвали  retain метод. Все остальные объекты — это не ваша забота. То есть, получается, что каждому  alloc или retain, должен соответствовать свой  release. Кроме этого, если объект создавался через каку-нибудь фабрику, или функцию, ctx = CGContextCreate() (именно создавался, а не брался откуда-то готовый), то надо бы найти соответсвующую функцию осовобождения объекта CGContextRelease(ctx).
А теперь — несколько примеров (один;) по работе с памятью.

[sourcecode language='c']
// Все те же рестораны
 Restaurant * b  = [Restaurant alloc];  //создаем ресторан
 NSObject * obj = [NSObject alloc]; // создаем объект (кол-во ссылок на объект КСО= 1)
 b.url = obj; // присваиваем свойству, КСО = 1, т.к. метод доступа - "assign"
 [obj release];  // осовобждаем объект, КСО = 0, память освобождается

 b.zip = b.url; // Вылетаем, так как b.name указывает туда, где уже ничего нету

//А вот тот же случай, но для свойства "zip", у которого метод доступа - "retain".

 Restaurant * b  = [Restaurant alloc];  //создаем ресторан
 NSObject * obj = [NSObject alloc]; // создаем объект (кол-во ссылок на объект КСО= 1)
 b.zip = obj; // присваиваем свойству, КСО = 2, т.к. метод доступа - "retain"
 [obj release];  // осовобждаем объект, КСО = 1

 b.url= b.zip; // Все нормально, мы все еще можем доступиться до объекта через b.zip

[/sourcecode]

Вообще, по работе с памятью нужна отдельная статья, впрочем, достаточно хорошо все описано в документации

@rake

grabli1

Нет, это не директива компилятора ;) Это – @грабли. Да, те самые, на которые я несколько раз наступил, пока изучал objective-C.  Я рассмотрел лишь часть особенностей, как мне кажется, необходимых, для того, чтобы нормально стартовать. Думаю, что кому-то эта статья поможет пару раз от них увернуться.

Комментарии

Павел Башмаков

в 14:48, 23.01.2009

Я хочу статью про программирование на iPod :) Чтобы и моей бабушке было понятно.

Бубнов Славик

в 9:19, 26.01.2009

Прикольно написано: доходчиво и с юмором )))
Но надо пробовать, а не только читать ;)

Роман Мазур

в 20:55, 27.01.2009

Оригінальний синтаксис :). Ці квадратні дужки, мінуси, як списки…

Павел Башмаков

в 21:59, 27.01.2009

Ага, как списки в Прологе.
Ребята сами себе коментим. Ой нехорошо. Люди скажут, что псевдоактивность создаем :)

NoOne

в 13:53, 28.01.2009

Я как-раз Java-разработчик и как-раз знакомлюсь с Objective-C (хочу попробовать подевелопить под iPhone, пока-что в сугубо личных энтузазистских интересах :)). Так что статья актуальной оказалась (да и написана внятно), спасибо (продолжайте в том же духе! ;)).

White

в 13:43, 29.01.2009

Не упомянут autorelease, а также принятый в Obj-C метод init (с разновидностями), который вызывается после alloc (Class *obj = [[Class alloc] init]) и инициализирует свойства.
Кроме того, всё-таки не стоит смешивать авторелиз-пул со сборщиком мусора. В последней версии Obj-C есть сборщик мусора, но он не реализован для платформы iPhone, ибо нефиг загружать ресурсы бедного телефона. :)

PS Проектов на iPhone ещё не планируете?

Kilew

в 16:05, 29.01.2009

Уже сделали ;) (проект;)
А по поводу памяти + еще авторелиз. Говорю, ж нужно отдельно писать ;)
В ближайшее время более подробно, все-таки остановлюсь на работе с памятью.

Павел Башмаков

в 18:39, 01.02.2009

Меня держит пару моментов, которые мы недоделали в нашем первом iPhone приложении от написания поста на эту тему. То карту вставить другую, то где-то доп. проверку добавить iPhone или iPod, то оригинальный вид рассписания работы заведения американская душа не признает :) Вобщем всякие мелочи, но которые, в то же время, очень важные и сильно влияют на user experience. На следующей недели такая статья уже точно должна появиться.

Novacoder

в 17:01, 13.04.2009

Помоему есть небольшая ошибочка в коде примера :)

Если в примере с использованием памяти используется класс ресторана, описанный немного выше, то b.name = obj не сработает, т.к. property name – readonly.

Но думаю, что все тут поняли, что имелось ввиду, что свойство name – только assigned (by default) и not readonly.

Но исправить в статье стоит.

Павел Тайкало

в 9:58, 13.05.2009

Поправил, спасибо ;)

New

в 21:56, 12.02.2010

Большое спасибо за статью!! (Хотя уже и сколько времени прошло с ее написания). Перешла с Java на Obj C с некоторыми муками для себя, жаль раньше не наткнулась. В принципе грабли у всех похожи,я так поняла)

[...] с Objective-C работаем с объектами.. и памятью или Objective-C для Java-программистов. Удачи в изучении!!! Таги: iPhone, iPhone & iPod, [...]

[...] опыте изучение это платформы вы можете прочитать здесь. Итак, скоро: ищите нашу первую iPhone разработку на [...]

Оставить комментарий