Objective-C для колишніх Win-програмістів

  1. Xcode: створення виконуваного файлу

Мова Objective-C входить у відкритий набір компіляторів GNU GCC, проте використовувати цю мову для програмування під платформу PC безглуздо, і в постачанні GCC він виконує виключно декоративні функції. Зате під платформу Mac він основна мова програмування. У цій статті ми розглянемо кілька кодерскіх прийомів для OS X за допомогою Objective-C і супутніх бібліотек.

Незважаючи на наявність фреймворків (наприклад, Xamarin або той же Java SE), що дозволяють програмувати під Макось на звичних мовах типу C # або Java, ці кошти йдуть в тінь, коли заходить мова про нативном коді. Якщо ми хочемо максимально заточити додаток під мак, скористатися всіма його можливостями - нам доведеться використовувати Objective-C. Він настільки ж Натів для Mac, наскільки C ++ для Windows або Linux. Обійдемося без історії, введення і огляду - ми вже писали про це - і перейдемо відразу до конкретних кодерскім рецептами.

Використання пам'яті - животрепетне питання на будь-якому комп'ютері і в будь-якій операційній системі. І на маці він вирішується по-своєму. У Objective-C є три способи управління пам'яттю:

  • автоматичний збирач сміття;
  • ручне управління пам'яттю;
  • автоматичний лічильник посилань.

Розглянемо їх коротко.

Формально збирач сміття - це добро, але, як ми знаємо з інших його реалізацій (в Java, в CLR), він забезпечує далеко не самий оптимальний менеджмент пам'яті, отжірая при цьому системні ресурси. Тому в операційній системі iOS цей спосіб зовсім відсутнє, і після виходу OS X 10.8 (Mountain Lion) він був позначений, що не рекомендований до застосування. Можливо, в майбутніх версіях Маковської системи він буде зовсім вилучений. Тепер турбота про сміття лягає на наші худі програмістські плечі, і вирішується вона так ...

Під час створення об'єкта посилання на нього приймає значення 1. Після цього, коли треба створити ще одне посилання на об'єкт, програміст отримує її шляхом збільшення лічильника на 1 наступним кодом: [myObj retain] ;. Тут ми відправляємо повідомлення retain нашого об'єкту myObj. У момент, коли програмісту більше не потрібна певна посилання на об'єкт, йому треба викликати [myObj release] ;. В результаті виклику цього повідомлення відбувається зменшення лічильника відповідно на 1. Коли кількість посилань стає дорівнює 0, відбувається автоматичний виклик методу dealloc, іншими словами - деструктора об'єкта. Деструкція успадковується від базового класу NSObject. Отже, він виконує мало корисного, тому тобі треба перевизначати його в кожному успадковане класі для того, щоб він справляв очищення кожної доданої змінної - члена класу. Втім, точно так же ми робимо, коли створюємо класову ієрархію на C ++: в кожному класі переобумовленої деструктор, при цьому в базовому класі оголошуючи його віртуальним. У Objective-C такого робити не треба. Зверни увагу: лічильник посилань може збільшуватися не тільки за допомогою прямої посилки повідомлень retain і release, але і за допомогою інших методів. Так, коли в масив додається об'єкт (за допомогою методу addObject класу NSMuttableArray), створюється нова посилання і, відповідно, збільшується лічильник. І навпаки, коли з масиву видаляється даний об'єкт (методом removeObjectAtIndex класу NSMuttableArray), лічильник зменшується. Зважаючи на це при ручному підрахунку необхідно дуже уважно ставитися до числа посилань, так як при посилці повідомлення release порожньому об'єкту трапиться краш додатки.

Крім самостійної посилки кожному об'єкту повідомлення release, можна створити пул видаляються об'єктів. Він представлений об'єктом класу NSAutoreleasePool. Вміщені в нього посилання очищаються тоді, коли досягається кінець пулу. Хочу підкреслити: в пулі зберігаються тільки посилання, але не самі об'єкти, тому при досягненні кінця пулу для кожної що входить в нього посилання викликається метод release. Щоб помістити посилання на об'єкт в пул, їй треба передати відповідне повідомлення: [myObj autorelease] ;.

Коли ти створюєш проект типу Foundation з темплейта, в автоматично генерується коді присутній наступний шматок:

@autoreleasepool {...}

Він являє собою не що інше, як пул автоматично видаляються об'єктів, і всі створені всередині його посилання на об'єкти будуть реалізовані на його кінець, з чого може слідувати видалення або зменшення лічильника об'єктів. Але зовсім не всі об'єкти автоматично додаються в пул: ті об'єкти, які створюються за допомогою методів alloc, copy, mutableCopy, new, не можуть бути автоматом додані в пул, і програмісту доведеться самому стежити за їх станом і підрахунком посилань. Проте за допомогою посилки повідомлення autorelease цей об'єкт можна помістити в пул автоудаляемих об'єктів. Все це нагадує управління пам'яттю в C ++, коли ми створюємо об'єкти в кадрі функції, по завершенні якої її стек очищається, і, з іншого боку, коли ми створюємо об'єкти в купі з допомогою оператора new і вони залишаються там, поки ми їх примусово не вилучено оператором delete.

Автоматичний підрахунок посилань (Automatic Reference Counting - ARC) з'явився в XCode 4.2, це рекомендований механізм управління пам'яттю. ARC дозволяє уникнути витоків пам'яті, пов'язаних з ручним підрахунком посилань. Компілятор створює код, який коректно виділяє і очищає пам'ять, займану об'єктами. При роботі з ARC існує два типи посилань: сильні та слабкі. Всі створювані посилання за замовчуванням сильні, але є можливість явно це вказати ключовим словом: __strong. Отже, в чому ж перевага сильних? Вони дозволяють уникнути витоків пам'яті шляхом автоматичного видалення «висячих посилань». Подивимося такий приклад. Нехай є клас Chip, у якого створено два примірника:

Chip * c1 = [[Chip alloc] init]; Chip * c2 = [[Chip alloc] init];

Тепер ми хочемо, щоб c2 вказував на c1, тобто c2 = c1 ;. Без використання ARC і сильних посилань тут нас очікує витік пам'яті, оскільки область пам'яті, на яку вказувала посилання c2, стала нічийною. Щоб уникнути подібної витоку, перед привласненням треба реалізувати посилання c2: [c2 release] ;. Саме це «за лаштунками» робить ARC при використанні сильних посилань.

Але якщо вони настільки гарні, навіщо тоді потрібні слабкі посилання? Уяви такий випадок: є два класи, об'єкт 1-го класу містить посилання на об'єкт 2-го класу. Таким чином, ми отримали циклічну посилання, що не є добре, тому що при видаленні об'єкта 2-го класу об'єкт 1-го буде вказувати «на ніщо», а це, як ми знаємо, загрожує краш додатки при зверненні по такому посиланню. У кращому випадку при використанні сильних посилань об'єкт 2-го класу не буде видалений. І ось тут на допомогу приходять слабкі посилання, які оголошуються за допомогою ключового слова __weak. Тепер, якщо посилання в 1-му класі ми зробимо слабкою, а потім видалимо 2-й об'єкт, на який вона посилається, то остання прийме значення nil, а звернення за посиланням з цим значенням не приведе ні до чого поганого.

Xcode: створення виконуваного файлу

Якщо ти прийшов на мак з Windows, то створення виконуваного файлу в Xcode в порівнянні з Visual Studio може викликати деякі труднощі. Щоб його створити, в Маковської середовищі недостатньо стандартної компіляції та побудови програми. Треба перейти по меню: Product -> Archive, відкриється вікно органайзера архіву.

Треба перейти по меню: Product -> Archive, відкриється вікно органайзера архіву

Organizer

У списку на перший момент знаходиться один запис, відповідна єдиною версією збірки. Натисни кнопку Distribute ... З'явиться додаткове діалогове вікно для створення дистрибутива, залиш в ньому вибір за замовчуванням: Save Built Product - і натисни Next. Тобі буде запропоновано задати папку дистрибутива, Save. В результаті буде створений підкаталог, що містить виконуваний файл твого додатки.

Як тобі відомо, основний фреймворк в OS X - це Cocoa. Cocoa не тільки містить засоби для побудови і виведення призначеного для користувача інтерфейсу, але і включає всі інші класи для програмування під OS X. В тому числі класи для маніпуляції файлами, масивами, рядками і так далі. Природно, Cocoa розділений на частини. І все фундаментальні засоби для роботи з системою, в тому числі перераховані вище, містяться в частині фреймворка під назвою Foundation і розташовуються у відповідному заголовки Foundation.h. Як приклад ми розробимо консольний додаток, відмовившись від красивого GUI, щоб сконцентруватися безпосередньо на роботі з файлами.

В принципі, OS X надає той же набір засобів для роботи з файловою системою, як і будь-який інший сучасний системний API. Проте в зв'язку з використанням іншої мови і операційної системи правила застосування розрізняються в порівнянні з тим же Win32 і / або Posix. Отже, давай почнемо розробляти додаток, яке працює з файлами в поточному каталозі проекту, і по ходу написання розберемо файлові функції і їх деталі. Створи новий проект Command Line для OS X типу Foundation (проект filesWork). Після створення проекту для того, щоб працювати з файлами з поточної директорії, треба вказати в Xcode відповідну папку. Це робиться в меню редагування схеми: Product -> Scheme -> Edit Scheme.

Редагування схеми проекту

У вікні, в списку ліворуч вибери пункт Run <назва програми>, потім в правій частині, зазначивши прапорець Use custom working directory, в рядку нижче введи або вибери за допомогою діалогу поточне розташування проекту. Спочатку створимо експериментальний файл. Його можна створити не відходячи від каси, тобто з Xcode: File -> New -> File, в вікні в лівому списку вибираємо Other, праворуч Empty, клацаємо Next, вводимо бажане ім'я - file, натискаємо Create.

Створення порожнього файлу

У відкрився файл введи будь тестові значення, збережи. Над ним ми будемо експериментувати - множити, перейменовувати, видаляти. Тим часом для початку в коді необхідно створити файловий менеджер - об'єкт класу NSFileManager, за допомогою якого здійснюються всі операції над файлами і папками. Відкривши в Xcode файл main.m, після відкриває пул autorelease фігурної дужки, оголосивши три змінні (див. Исходник 1 до статті на сайті), напиши: fm = [NSFileManager defaultManager] ;. Змінні, про які йшла мова вище, - це: 1-я - для зберігання імені файлу, що відкривається, 2-я - сам файловий менеджер, 3-тя - словник файлових атрибутів (об'єкт класу NSDictionary). Потім перевіряємо даний файл на існування:

if ([fm fileExistsAtPath: fName] == NO) {

Повідомлення fileExistsAtPath викликається для файлового менеджера, отримує в якості параметра шлях / ім'я до файлу, наявність якого необхідно перевірити. І в разі його присутності повертає YES, в іншому випадку - NO, і ми виводимо на консоль відповідне повідомлення. З'ясувавши, що файл є в заданому каталозі, ми копіюємо його командою copyItemAtPath:

[Fm copyItemAtPath: fName toPath: @ "newfile" error: NULL];

Метод приймає три параметри: копіюється файл, мета копіювання і покажчик на об'єкт класу NSError, в який в разі помилки записується докладна інформація про неї. Якщо замість цього об'єкта передати NULL, як в нашому прикладі, то спрацює стандартна реакція: при успішному виконанні методу він поверне YES, інакше - NO. Після цього порівнюємо вміст файлу-джерела і файлу-приймача, скориставшись командою contentsEqualAtPath:

[Fm contentsEqualAtPath: fName andPath: @ "newfile"];

Якщо воно однакове, тоді продовжуємо виконання і перейменовуємо копію. Для перейменування, як тобі відомо з досвіду роботи з іншими API, використовується функція переміщення, тільки директорія-джерело і приймач повинні бути однаковими:

[Fm moveItemAtPath: @ "newfile" toPath: @ "newfile2" error: NULL];

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

attr = [fm attributesOfItemAtPath: @ "newfile2" error: NULL]; NSLog (@ "Розмір файлу складає% llu байт", [[attr objectForKey: NSFileSize] unsignedLongLongValue]);

Тепер видалимо оригінальний файл. Це, як завжди, просто - ламати не будувати :). Викликаємо метод removeItemAtPath файлового менеджера і передаємо йому ім'я файлу, що видаляється:

[Fm removeItemAtPath: fName error: NULL]

Під кінець програми виведемо вміст створеного файлу. Для цього методу stringWithContentsOfFile класу NSString першим параметром передаємо ім'я файлу, вміст якого треба вивести, другим - потрібне кодування, в якій буде здійснено виведення, третім - NULL:

NSLog (@ "% @", [NSString stringWithContentsOfFile: @ "newfile2" encoding: NSUTF8StringEncoding error: NULL]);

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

Якщо створити виконуваний файл (як це зробити - дивись в урізанні), помістити в робочу папку наш тестовий file, запустити додаток на виконання, то воно виведе вміст картинки «Висновок додатки в терміналі» (в залежності від вмісту файлу).

Якщо створити виконуваний файл (як це зробити - дивись в урізанні), помістити в робочу папку наш тестовий file, запустити додаток на виконання, то воно виведе вміст картинки «Висновок додатки в терміналі» (в залежності від вмісту файлу)

Висновок додатки в терміналі

До того ж для роботи з директоріями існує кілька методів: currentDirectoryPath повертає поточну для програми папку, changeCurrentDirectoryPath змінює поточну директорію, copyItemAtPath копіює структуру директорії, createDirectoryAtPath створює нову папку, NSHomeDirectory дозволяє отримати шлях до домашньої директорії поточного користувача, а також інші. Наприклад, перша з цього списку функція використана в наведеній програмі, в результаті на початку виконання програма виводить свою домашню директорію на консоль.

Часто виникає потреба роботи з «сирими» даними всередині файлів. В такому випадку треба читати / записувати / обробляти не обов'язково текстові дані, тому зберігати їх в рядках (NSString) не вийде. У Cocoa нас виручить клас NSData. Попросту кажучи, об'єкт даного класу являє собою буфер для тимчасового зберігання даних. Наприклад, можна скопіювати файл побайтно, не користуючись при цьому системної функцією. Прочитати дані з файлу можна, скориставшись наступною рядком:

NSData * fileData = [fm contentsAtPath: @ "file"];

Тобто в оголошений буфер класу NSData fileData ми поміщаємо вміст файлу file, ім'я якого передається методу в параметрі. Зберігаємо дані в іншому файлі методом createFileAtPath, йому передаються три параметра: ім'я нового файлу, вміст - дані з буфера fileData і атрибути - nil:

[Fm createFileAtPath: @ "newfile3" contents: fileData attributes: nil];

Цим можливості класу NSData не обмежуються. Разом з класом NSFileHandle він набуває додаткових можливостей: об'єкт останнього, будучи покажчиком на джерело та / або приймач, дозволяє здійснити гнучкі маніпуляції над даними, такі як зчитування або запис в довільну позицію файлу. Клас NSFileHandle містить наступні методи (перелік далеко не повний): fileHandleForReadingAtPath - відкриває файл для читання, fileHandleForWritingAtPath - відкриває файл для запису, fileHandleForUpdatingAtPath - для поновлення (читання + запис), seekToFileOffset - переміщається на вказане зміщення. Розберемося з методами класу на конкретному прикладі. Нехай програма читає наш старий файл і якщо файлу для виведення не існує, то створює його і пише в нього вміст першого файлу, а в разі наявності файлу для виведення доповнює його вмістом прочитаного файлу. Таким чином, при кожному наступному запуску програми файл виводу буде рости.

Отже, створи новий проект. Для отримання повного лістингу дивись исходник (проект fileMod), я буду приводити короткі коментарі. Спочатку оголошуємо два файлових хендлом (об'єкти класу NSFileHandle): на якого читають і записується, також оголошуємо буфер даних і створюємо файловий менеджер. Відкриваємо файл file для читання:

inFile = [NSFileHandle fileHandleForReadingAtPath: @ "file"];

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

outFile = [NSFileHandle fileHandleForWritingAtPath: @ "fileout"];

Наступним дією переводимо поточну позицію файлу в його кінець:

[OutFile seekToEndOfFile];

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

buffer = [inFile readDataToEndOfFile]; [OutFile writeData: buffer];

Закриваємо обидва файли і останньою дією виводимо на консоль весь вміст файлу виводу.

Раджу звернути увагу на клас NSFileHandle, оскільки він містить широкі можливості управління файловими потоками. Крім того, об'єкти цього класу використовуються для введення / виведення інформації в сокети і пристрої.

На нижньому рівні мережу в OS X влаштована так само, як і в інших операційних системах, - за допомогою Berkley Sockets API. Однак в прикладному API, такому як Cocoa, є відмінності для реалізації підтримки Obj-C і внесення зручності роботи з сокетами. На щастя, щоб розібратися в роботі з сокетами в Cocoa, нам буде потрібно зовсім небагато часу. Існує тільки три класи, які використовуються для підтримки мережевої взаємодії: NSURL, NSURLRequest, NSURLConnection, а також їх модифікуються аналоги з ключовим словом Mutable, присутнім в назві.

Об'єкти першого класу списку, як можна здогадатися, представляють локатори - як віддалених, так і локальних ресурсів. Для створення об'єкта використовується методURLWithString: NSURL * myURL = [NSURL URLWithString: @ "yazevsoft.blogspot.com"] ;. Клас містить методи для отримання будь-якої частини URL і для створення відносних адрес. Створення шляху до локального файлу здійснюється за допомогою методу fileUrlWithPath, наприклад, так: NSURL * myFile = [NSURL fileURLWithPath: @ "/ Applications /"] ;.

Екземпляри класу NSURLRequest визначають спосіб доступу до об'єкта, на який вказує NSURL. Створення відбувається за допомогою методу requestWithURL, якому передається ініціалізований об'єкт NSURL. За допомогою об'єкта розглянутого класу можна визначити вид доступу по протоколу HTTP (GET, POST, PUT), задати час затримки перед відповіддю і інше.

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

Розробимо мережеве додаток з віконним призначеним для користувача інтерфейсом, в процесі чого розглянемо всі три кроки установки з'єднання. Наша прога буде просто завантажувати зображення з вільного ресурсу в Мережі (з мого блогу). Створи нове Cocoa Application і постав параметри (проект imageLoader). Відкрий файл <...> Document.h, де <...> - ім'я префікса для класу, заданий в майстра генерації проекту (в моєму випадку - App), тут в оголошенні інтерфейсу AppDocument в фігурних дужках додай опис аутлета imageView класу NSImageView , за допомогою якого наша програма буде взаємодіяти з візуальним компонентом цього ж класу:

IBOutlet NSImageView * imageView;

Тепер створимо інтерфейс, що складається з лежачого на поверхні форми зображення. Спочатку відкрий файл AppDocument.xib, з палітри компонентів перенеси на форму компонент Image Well (об'єкт класу NSImageView). Розтягни його ширше. Зв'яжемо компонент з аутлет, щоб отримати управління над першим з коду. Для цього, утримуючи клавішу Ctrl на клаві, від об'єкта File's Owner перетащи зв'язує синю лінію на об'єкт Image Well. В результаті з'явиться меню Outlets, в якому буде тільки один пункт imageView - оголошена нами змінна, клацни на цьому пункті. Аутлет пов'язаний. До речі, ці дії зручно здійснювати в другій зліва панелі вікна редагування xib-файлу. На наступному кроці додамо код для завантаження зображення в компонент. Відкрий файл AppDocument.m і додаткового функцію

- (void) windowControllerDidLoadNib: (NSWindowController *) aController

наступним кодом:

NSString * urlString = [NSString stringWithFormat: @ "http://1.bp.blogspot.com/--8pJeTHLC2g/Un8Uc373peI/AAAAAAAACMY/SWBK5HDP-fU/s1600/ProjectGenom+2013-11-10+09-45-43- 15_1.png # 26759185 "]; NSURL * url = [NSURL URLWithString: urlString]; NSURLRequest * request = [NSURLRequest requestWithURL: url]; [NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^ (NSURLResponse * response, SData * data, NSError * error) {NSImage * image = [[NSImage alloc] initWithData: data]; imageView.image = image; }];

Судячи з коментаря, залишеного в функції при генерації проекту, наш код виконається відразу після створення вікна. Отже, перш за все в ньому оголошується і инициализируется рядок-посилання (прости за настільки абсурдну абракадабру), потім на її основі створюється локатор для віддаленої картинки, після цього на базі локатора формується запит - об'єкт класу NSURLRequest. Далі за допомогою методу sendAsynchronousRequest об'єкта класу NSURLConnection ми створюємо з'єднання з зазначеним в NSURL мережевим вузлом. Цьому методу ми передаємо об'єкт класу NSURLRequest, створюємо чергу операцій (об'єкт класу NSOperationQueue), а останнім параметром передаємо блок коду, який отримає управління в момент, коли дані (зображення) будуть повністю завантажені. Усередині блоку коду отримані дані перетворюються в об'єкт класу NSImage, а потім цей об'єкт виводиться в компонент imageView через привласнення вмісту властивості image компонента. Більш детальну інформацію про блоках коду (що вони собою являють і з чим їх їдять) ти можеш дізнатися зі статті минулого номера] [.

Прийшов час протестувати нашу прогу: откомпіль і побудуй додаток, якщо в коді немає помилок і посилання введена правильно, то у вікні програми повинен завантажитися скріншот з нашої гри Project Genom.

Прийшов час протестувати нашу прогу: откомпіль і побудуй додаток, якщо в коді немає помилок і посилання введена правильно, то у вікні програми повинен завантажитися скріншот з нашої гри Project Genom

XCode і запущене під дебаггера додаток

Сьогодні ми торкнулися три великі теми: управління пам'яттю в OS X, роботу з файлами та мережеве взаємодія з допомогою Cocoa. Проте за бортом залишилася маса важливих і цікавих тем, які ми ще розглянемо найближчих номерах - адже інтерес до платформ від Apple тільки зростає. Бажаю удачі, і до зустрічі на сторінках] [.

Отже, в чому ж перевага сильних?
Але якщо вони настільки гарні, навіщо тоді потрібні слабкі посилання?

Дополнительная информация

rss
Карта