вторник, 12 мая 2009 г.

12.05.09. Книга Cocoa Design Patterns.Template Method

Template Method

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

Примеры: –dealloc из NSObject, -drawRect из NSView.

Пример:

@implementation MYCircleShape
- (BOOL)doesContainPoint:(NSPoint)aPoint
{
BOOL result = [super doesContainPoint:aPoint];
if(result)
{
NSPoint center = NSMakePoint(NSMidX(frame), NSMidY(frame));
float radius = MIN(NSWidth(frame) / 2.0f,
NSHeight(frame) / 2.0f);
float radiusSquared = radius * radius;
float deltaX = aPoint.x – center.x;
float deltaY = aPoint.y – center.y;
float distanceSquared = (deltaX * deltaX ) + (deltaY *
deltaY);
result = (distanceSquared <= radiusSquared);

}

return result;

}

@end

Методы в базовом классе могут иметь реализацию по умолчанию. Переопределенные методы делятся на три группы:

  • те, которые могут вызвать реализацию по умолчанию, но скорее всего она ничего полезного не делает,
  • те, которые могут вызвать, а могут не вызвать реализацию по умолчанию,
  • те, которые обязаны вызвать реализацию по умолчанию,

Пример из Cocoa - отображение представления заданного NSView. В этом алгоритме –drawRect: является одним из шагов, который подкласс NSView должен определить. Этот метод вызывает окно NSWindow в процессе формирования изображения.

NSResponder также определяет несколько методов, которые производные классы должны переопределить, если они хотят обрабатывать события.

Недостатки паттерна:

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

Альтернативой может быть использование делегата.

Переопределяемый метод должен иметь одну ясную задачу. Например, –drawRect: предназначен только для настройки способа изображения представления, поэтому это хорошая кандидатура для шаблонного метода.

12.05.09. Книга Cocoa Design Patterns. MVC, Documents, Preferences

Цели применения паттернов:
  • уменьшение связанности,
  • проектирование с учетом всевозможных изменений,
  • проектирование согласно интерфейсам, а не реализации,
  • предпочтение агрегации наследованию.

MVC (выписаны новые характеристики, которые не были отмечены в старых постах):

Контроллер конкретного приложения как правило не может быть повторно использован. Он сильно связан с представлением и моделью. Представление тяготеет с частому изменению, следовательно, нужен посредник между ним и моделью, чтобы не менять модель при каждом изменении представления. Знания, необходимые при разработке модели, существенно отличаются от знаний, необходимых при разработке графического представления.

Два примера одного калькулятора

1) без MVC

#import Cocoa/Cocoa.h
@interface PayCalculator : NSObject
{
IBOutlet NSTextField *employeeNameField;
IBOutlet NSFormCell *hourlyRateField;
IBOutlet NSFormCell *hoursWorkedField;
IBOutlet NSFormCell *standardHoursInPeriodField;
IBOutlet NSTextField *payAmountField;
IBOutlet NSButton *employeeIsExemptButton;
}
- (IBAction)calculatePayAmount:(id)sender;
@end

#import "PayCalculator.h"
@implementation PayCalculator
- (IBAction)calculatePayAmount:(id)sender
{
if(NSOnState == [employeeIsExemptButton state])
{ // Pay the hourly rate times the standard number of hours
// regardless of hours worked
[payAmountField setFloatValue:[hourlyRateField floatValue] *
[standardHoursInPeriodField floatValue]];
}
else
{ // Pay the hourly rate times the number of hour worked
float payAmount = [hourlyRateField floatValue] *
[hoursWorkedField floatValue];
if([hoursWorkedField floatValue] >
[standardHoursInPeriodField floatValue])
{ // pay 50% extra for overtime
float overtimePayAmount = 0.5f *
[hourlyRateField floatValue] *
([hoursWorkedField floatValue] -
[standardHoursInPeriodField floatValue]);
payAmount = payAmount + overtimePayAmount;
}
[payAmountField setFloatValue:payAmount];
}
}
@end

2) с MVC



@interface MYEmployee : NSManagedObject
{}
- (NSNumber *)payAmount;
@end

#import "MYEmployee.h"
@implementation MYEmployee
+ (void)initialize
{ // Configure dependant attributes
[self setKeys:[NSArray arrayWithObjects:@"isExempt",
@"hourlyRate", @"hoursWorked", @"standardHours", nil]
triggerChangeNotificationsForDependentKey:@"payAmount"];
}
- (NSNumber *)payAmount
{ // return a caculated pay amount based on other attributes
NSNumber * tmpValue;
[self willAccessValueForKey: @"payAmount"];
if([[self valueForKey:@"isExempt"] boolValue])
{ // Pay the hourly rate times the standard number of hours
// regardless of hours worked
tmpValue = [NSNumber numberWithFloat:
[[self valueForKey:@"hourlyRate"] floatValue] *
[[self valueForKey:@"standardHours"] floatValue]];
}
else
{ // Pay the hourly rate times the number of hour worked
float payAmount =
[[self valueForKey:@"hourlyRate"] floatValue] *
[[self valueForKey:@"hoursWorked"] floatValue];
if([[self valueForKey:@"hoursWorked"] floatValue] >
[[self valueForKey:@"standardHours"] floatValue])
{ // pay 50% extra for overtime
float overtimePayAmount = 0.5f *
[[self valueForKey:@"hourlyRate"] floatValue] *
([[self valueForKey:@"hoursWorked"] floatValue] -
[[self valueForKey:@"standardHours"] floatValue]);
payAmount = payAmount + overtimePayAmount;
}
tmpValue = [NSNumber numberWithFloat:payAmount];
}
[self didAccessValueForKey: @"payAmount"];
return tmpValue;
}
@end

Cocoa's Document Architecture



NSDocument - абстрактный класс, должен быть переопределен по паттерну Template Method. Должны быть переопределены критические методы:

- (BOOL)writeToURL:(NSURL *)absoluteURLofType:(NSString *)typeName error:(NSError **)outError

-(BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString*)typeName error:(NSError **)outError.

NSDocumentController является делегатом NSApplication.

При использовании Core Data есть уже готовый подкласс NSPersistentDocument класса NSDocument, который управляет объектом NSManagedObjectContext, хранящий всю нужную информацию.

Cocoa's Preference Pane Architecture

Приложение может добавить окно для своих настроек в систему System Preferences. Эта подсистема сделана по принципу MVC.

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

12.05.09. Книга Cocoa Design Patterns.Two Stage Creation

Two Stage Creation

Разделение этапа создания объекта на два: выделение памяти и инициализация.

[[SomeClass alloc] init].

Преимущества:

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

Должен быть один или несколько Designated Initializer. Он как правило имеет больше всего аргументов и его вызывают все остальные инициализаторы. Designated Initializer обязан вызвать Designated Initializer своего базового класса.

@interface MYCircle : NSObject
{
NSPoint center;
float radius;
}
// Designated Initializer
- (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius;
@end
@implementation MYCircle
// Designated Initializer
- (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius
{
self = [super init];
if(nil != self)
{
center = aPoint;
radius = aRadius;
}
return self;
}
@end

В каждый метод добавляются два скрытых параметра: self и _cmd.

Правила при работе с Designated Initializer (DI):

  • DI должен вызывать DI своего базового класса
  • self-у должен быть присвоен результат выполнения DI базового класса
  • Нужно проверить не вернул ли DI базового класса nil.
  • DI должен переопределить DI базового класса.
  • Все остальные инициализаторы должны вызывать DI.

Creating Temporary Instances

+ (id)stringWithString:(NSString *)aString
{
return [[[self alloc] initWithString:aString] autorelease];
}

Основная особенность в том, что созданный временный объект будет удален автоматически, если клиент не возьмет владение (retain) на себя.

четверг, 7 мая 2009 г.

7.05.09. Application Architecture.Document Architecture

Document Architecture

Особенности document-based application:

  • возможность создания нового документа,
  • сохранение этого документа,
  • загрузка документа позже из файла,
  • каждый документ - в отдельном окне,
  • централизованное управление и мониторинг открытыъ документов в окнах.

Ключевые классы: NSDocument, NSDocumentController, and NSWindowController.

Объект NSDocumentController управляет объектами NSDocument. Каждый NSDocument управляет одним или несколькими объектами NSWindowController. В каждом NSWindowController происходит сопоставление документа с одним или несколькими окнами. NSWindowController управляет представлением документа.

Разделение обязанностей:

NSDocumentController управляет документами (созданием, сохранением, загрузкой). Это координирующий контроллер. Информацию о документах он берет из списка свойств приложения Info.plist. Готов к употреблению (подклассов создавать не нужно).

NSDocument является моделью документа. Это модель-контроллер. Абстрактный класс, нужно создать производный класс конкретно для своей модели документа. Должен уметь загружать/сохранять объекты модели. Возможно будет иметь несколько способов хранения данных. Управляет одним или несколькими контроллерами NSWindowController.

NSWindowController управляет представлением документа в окне. Это представление-контроллер. В простых случаях не требуется создания подкласса, этот контроллер обладает минимумом, необходимым для управления окном. Однако, в более сложных случаях понадобится создавать подклассы, обладащие информацией о представлениях документов. Обычно, экземпляр этого подкласса является File's Owner для nib-файла документа. Если документу нужно несколько окон для представления, то для каждого из них нужен свой NSWindowController.

Подробнее.

6.05.09. Application Architecture.Nib Files

Nib Files and Other Application Resources

В nib-архиве сохраняется весь граф объектов, при этом каждый объект остается уникальным и все отношения сохраняются.

При архивировании каждому объекту отправляется команда закодировать себя в поток. При разархивировании происходит обратная операция, причем каждый объект декодирует себя сам.

Процесс кодирования может быть последовательным (протокол NSCoder) и основанным на использовании ключей (используются классы NSKeyedArchiver and NSKeyedUnarchiver). Также должен быть реализован протокол NSCoding.

В nib-файле сохраняется весь граф объектов интерфейса со всеми отношениями между объектами. В нем должен быть корневые объекты для иерархии представлений, объекты контроллеров, прокси-объекты для доступа к нужным классам. Кроме того, в nib-файле всегда должен быть:

  • File's Owner. Объект, который владеет nib-файлом и управляет объектами в нем. Этот владелец должен быть внешним по отношению к nib-файлу. Он является канал для связи внутренних и внешних объектов.
  • First Responder. Первый объект в цепочке responder chain. Его также можно назначить target-ом.

Для класса представления, производного от NSView, нужно использовать объект Custom View из палитры. При разархивировании будет вызван метод awakeFromNib.

В приложении есть главный nib-файл, у которого File's Owner - NSApp. Он содержит основное меню и возможно некоторые окна. При запуске программы главный nib-файл разархивируется и окна раскрываются. Объекты из других nib-файлов загружаются при необходимости.

Ресурсы приложения хранятся также в nib-файле в папке Resources. При выполнении программы доступ к ресурсам возможен через объект класса NSBundle.

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

6.05.09. Application Architecture.Controls and Menus

Controls and Menus

Также см. один из прошлых постов.

Контролы являются производными от NSControl, который порожден от NSView.

Ячейки же являются производными от NSObject и их классы соответствуют контролам: NSButtonCell, NSStepperCell, NSTextFieldCell.

Controls That Manage Multiple Cells

Если у контрола несколько ячеек, то он может использовать один экземпляр cell для отображения как шаблон, меняя только содержимое при отображении. Так делают NSTableView and NSOutlineView.

NSMatrix использует для каждого элемента отдельный экземпляр cell. Каждая ячейка создается как копия прототипа.

How Controls Manage Cells

Контрол - это полноценное представление, поэтому он может сам реализовать методы NSResponder и сам стать обработчиком событий и участником цепочки responder chain, также самостоятельно обрабатывать сообщение drawRect:. Однако, вместо этого контрол передает эти обязанности своей ячейке.

Контрол в обработчике drawRect: отправляет сообщение drawWithFrame:inView: ячейке, передавая ей прямоугольник, в котором находится контрол. При этом фокус отрисовки фиксируется на контроле, поэтому ячейка может сразу же приступат к отрисовке.



Ячейки при отрисовке должны выполнить обязанности, возложенные на них их классом. Это может быть форма, цвет, стиль, специальное состояние (серый текст для неактивной ячейки). Кроме того, ячейка должна отобразить своё содержимое, которое обычно является строкой или преобразовывается к строке. С ячейкой также ассоциируются вспомогательные объекты такие, как NSColor.

Индивидуальности ячейке добавляют действия (action), которые конкретная ячейка может отправлять target-у. Действия - это селекторы вызываемых методов у цели. Этот аспект задается с помощью NSActionCell.

Rationale for the Control-Cell Architecture

Зачем нужны ячейки? Почему всю работу не может выполнить сам контрол?

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

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

Удобно использовать NSTableView с прототипами ячеек, которые используются при тиражировании элементов таблицы.

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

Menu Characteristics and Architecture

Меню и элементы меню: NSMenu and NSMenuItem.

NSMenuItem содержит селектор для действия и указатель на target.

Перед тем, как показать меню, NSMenu проверяет responder chain для каждого пункта на предмет возможности обработки сообщения от этого пункта. Если ничего подходящего не найдено, то пункт меню блокируется.

Represented Objects

Ячейка и пункт меню могут иметь ассоциированные с ними представленные объекты (Represented Objects). Target может запросить отправителя сообщения-действия о таких объектах.

- (void)changeColor:(id)sender {
NSColor *repObj = [sender representedObject];
[textView setBackgroundColor:repObj]; // textView is an outlet
}

Представленный объект может быть объектом класса NSView и вообще объектом любого класса.

среда, 6 мая 2009 г.

6.05.09. Application Architecture. Views, Responders.

Views

Представление основной объект для отображения информации и приема событий от клавиатуры и мыши. Реальные представления являются подклассами NSView.

Представления можно разделить на:

  • Контролы (кпопки, текстовые поля и т.п.), с которыми пользователь может совершать действия. Обычно идут вместе с ячейками, которые не являются производными от NSView.
  • Контейнеры. Содержат (более удобно оформляют) более примитивные представления.  NSTextView, NSImageView, NSBox, NSSplitView, and NSTabView.
  • Составные представления. Объединяют несколько представлений. 
  • Оболочки. Дают доступ к некоторым системным возможностям  (NSOpenGLView and NSMovieView).

The View Hierarchy

Представления объединены в иерархию. У каждого есть родитель (superview). Базовый родитель - Content View. У него есть только закрытый superview.

Система координат к каждого представления своя.

Каждое предсталение знает о

  • окне
  • superview
  • дочерних представлениях


View Geometry and Coordinates

The Frame

Ограничивающий представление прямоугольник. Видимая область представления зависит от расположения родительских представлений.


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



The Bounds

В то время как frame задает размеры представления и его положение относительно родительского представления, bounds определяют область представления, в котором происходит отрисовка содержимого и обработка событий. По умолчанию frame и bounds совпадают. Т.о. совпадают и начала координат в левом нижнем углу. Однако, начало координат локальной системы координат представления можно перенести в левый верхний угол, что удобно при отображении текста сверху-вниз слева-направо. В этом случае получим flipped coordinates.


Возможно преобразование системы координат представления:

  • начало координат bounds может быть перенесено из точки начала координат frame
  • для bounds может быть свой масштаб по осям
  • система коорнинат bounds может быть повернута относительно системы координат frame.

How Views Get Drawn

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

Для немедленного обновления представлении вызываются display методы. При этом 

  • блокируется представление, требующее обновления
  • вызывается метод представления drawRect: 
  • обновляется буфер окна

Хотя рекомендуется использовать auto-display mechanism. В этом случае все представления будут обновлены совместно с учетом их взаимного расположения.

Для обновления отдельного представления (целиком и по частям) существуют методы:

display, displayRect:, displayRectIgnoringOpacity:.

Для указания необходимости обновления при использовании auto-display mechanism:

setNeedsDisplay:, setNeedsDisplayInRect:

Т.е. этими методами представления помечаются для обновления.

Можно немедленно обновить помеченные представления:

displayIfNeeded, displayIfNeededInRect:, displayIfNeededIgnoringOpacity, эdisplayIfNeededInRectIgnoringOpacity:.

Этот способ промежуточный (в т.ч. по эффективности) между немедленным обновлением одного представления и автоматическим обновлением всех представлений.

Locking Focus

Для отрисовки на представление фиксируется фокус (lockFocus..., unlockFocus). При этом происходит:

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

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

В состоянии находится:

матрица преобразований координат точек из одной системы координат в другую, а также ряд параметров

The current color for fill and stroke operations
Alpha value (transparency)
Line attributes, including width, join, cap, dash, and miter limit
Anti-aliasing value
Current color space
Text attributes: font, font size, character spacing
Blend mode

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



Текущим становится очередное состояние из стека.

What Happens in drawRect:

Как только на представлении фиксируется фокус, ему посылается сообщение drawRect:. Представление реализует этот метод. В метод передается прямоугольник, который будет отрисован. Он может не совпасть с bounds, т.к. является пересечением нескольких прямоугольников.

Основным правилом оптимизации отрисовки является отрисовка только нужных в данный момент областей как можно меньшего размера. Обращение к оконному серверу, координирующему отрисовку - дорогая операция и ее нужно выполнять только по необходимости. Поэтому отрисовка всего представления - наименее эффективный вариант. Лучше запросить у NSView список областей, требующих обновления с помощью  getRectsBeingDrawn:count:

Методы, используемые в drawRect:

Drawing functions (declared in NSGraphics.h) to draw, fill, erase, and perform other operations with rectangles 
Methods to construct lines and shapes with bezier paths (NSBezierPath class)
Methods to create and apply affine transforms, involving translation, scaling, and rotation operations (NSAffineTransform class)
Color and color-space methods (NSColor and NSColorSpace)
Methods for creating and compositing images (NSImage and various image-representation classes)
Methods for drawing text (NSString and NSAttributedString)

Эти методы напрямую обращаются к оконному серверу за реализациями из Core Graphics (Quartz). К реализациям можно обратиться и напрямую.

Threads and Drawing

Отрисовка необязательно может идти только из главной нити, однако изменения свойств NSView, например, прямоугольника для Frame, должно происходить только из главной нити. Кроме того, только одна нить может рисовать в данном представлении в данный момент времени.

Views and Events

Представления в первую очередь обрабатывают события от пользователя. Для этого представление реализует протокол NSResponder, в котором объявлены такие методы, как  mouseDown:, mouseMoved:, and keyUp:. В эти методы как параметр передается объект NSEvent. Если представление не содержит подходящего обработчика, то он ищется у superview и далее по responder chain.

Responders and the Responder Chain

NSResponder определяет поведение NSApplication, NSWindow, and NSView с точки зрения обработки событий. 

Сообщения о событиях делятся на 

  • события (event messages),
  • действия (action messages), которые перенаправляются target-ам.

Также NSResponder задает механизм для управления responder chain, т.е. передачей событий по цепочке обработчиков.



У каждого окна есть своя цепочка responder chain. Основные объекты в ней - это NSWindow и семейство представлений. Чем ниже представление в иерархии, тем больше шансов, что именно оно обработает событие. У окна NSWindow есть первый обработчик (first responder), обычно выбранное представление в окне.

Для событийных сообщений цепочка обычно заканчивается на NSWindow, но есть возможность добавить и другие объекты после окна.

Для действий длина цепочки определяется следующими правилами:

  • если в приложении есть главное окно и ключевое окно, то сначала действие пытается обработать цепочка ключевого окна, класс ключевого окна, его делегат, затем цепочка главного окна, класс главного окна, его делегат и наконец NSApp и его делегат.
  • для каждого типа приложения simple, document-based, или приложение с window controller структура цепочки отличается.

понедельник, 4 мая 2009 г.

4.05.09. Application Architecture.

The Global Application Object

NSApp - глобальный объект NSApplication.

The Main Event Loop

В функции main вызывается функция NSApplicationMain, в которой происходит следующее:

  1. Приложение получает объект NSApp с помощью метода sharedApplication класса.
  2. Загружается главный nib-файл.
  3. Запускается основной метод приложения ([NSApp run]).

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



В п.3. работает бесконечный цикл.

  • В нем работают наблюдатели, получающие события и обрабатывающие их.
  • Очередное событие извлекается из очереди событий (nextEventMatchingMask:untilDate:inMode:dequeue:).
  • Обработка события заключается в его перенаправлении нужного объекту (как правило, NSWindow object (sendEvent:)) или отрисовке преставления.

Как только очередь исчезает, то оконный сервер снова начинает ее заполнять.

More About Event Dispatch

События могут быть весьма разнообразными. Поэтому и получатели событий выбираются NSApp разные (NSApp работает диспетчером).

Например, событие нажатия клавиши перенаправляется ключевому окну (Key Window) с помощью sendEvent:

Событие клика мышью отправляется окну, в котором находился курсор.

Событие клика мышью на контроле порождает сообщение-действие и отправляется с помощью sendAction:to:from: указанной цели или перенаправляется по цепочке (responder chains).

Также возможны другие типы событий.

Window Management

NSApp содержит список окон, с помощью которого управляет ими.

Handling Apple Events

Существуют несколько системных событий (начинаются на kAE), отправляемых приложению при запуске или действиях в Finder-е и т.п.

Windows

По способу работы с окнами приложения могут быть:

  • Document-based
  • Single-window

Также могут модальные окна в виде панелей.

Окна NSWindow создает оконный сервер. Работа с окнами идет по их номеру. Сервер после создания окна получает графический контекст окна и window's backing store - область памяти для работы с устройством отображения окна.

Window Buffering

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

Возможны несколько типов буферизации.

Window Z-Order and Levels

Окна объединяются по уровням согласно их назначению. Некоторые уровни всегда располагаются выше других. Уровнями и z-порядком управляет оконный сервер, а также NSWindow для окон приложения.

Если окно убрано из списка видимых окон, то сообщения ему не доставляются.

Parts of a Window

У окна есть два представления: frame view and a content view. Они являются подклассами NSView. Frame включает заголовок, границу и служебные кнопки. Этот объект представления создается NSWindow как private и его нельзя изменить, создав подкласс. Однако при создании этой переменной можно указать, какие кнопки показывать и т.д.

Content view имеет superview, но оно является также private объектами. Поэтому Content view является корневым представлением для всех представлений, доступных для изменения. В итоге образуется иерархия представлений. Представление имеет ссылку на своё окно.



Window Coordinates

Для расположения окон на экране используется система координат экрана.

У окон своя система координат.

Начало координат в левом нижнем углу.

Frame view использует систему координат экрана. Content view - окна.

Windows and Drawing

Отрисовка - обязанность представлений. Однако NSWindow следит за областями представлений, требующими обновлений и дает им команду на обновление (display, displayRect:, or displayRectIgnoringOpacity:).

Window Status

Окна (главное и ключевое) могут иметь разный статус (активное, неактивное и т.п.).

Event Dispatch

Большинство событий приходит через sendEvent: к NSWindow. Дальше оно перенаправляет событие нужному представлению.

Panels

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

Подробнее.

воскресенье, 3 мая 2009 г.

3.5.09. Bindings, Notifications

Bindings

Привязка какого-либо значения из модели к элементу представления. При изменении значения на любой из сторон отношения сразу же меняется значение на другой стороне.

Привязка идет через контроллер.

С помощью привязок сеть отношений между объектами модели можно расширить на контроллеры и далее на элементы представления.

Для работы с привязкой нужно, чтобы классы поддерживали key-value coding and key-value observing.

Подробнее.

Notifications

Обычный способ взаимодействия объектов - отправка сообщений. Но, чтобы этот способ работал, отправитель сообщения должен знать получателя сообщения и быть увереным, что он сможет его обработать.

Широковещательная рассылка уведомлений - способ уменьшить связанность подсистем. Наблюдателей может быть несколько, а отправитель может о них не знать.



Вводится центральный элемент - диспетчер событий.

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

Подробнее.

Сравнение уведомления с делегатами.

Преимущества уведомлений (отношения один-ко-многим):

  • отправитель не знает получателей, которых может быть много
  • уведомления могут быть разного типа
  • уведомления могут быть межпроцессные

Преимущества делегатов (отношение один-к-одному):

  • делегат может вернуть значение отправителю и повлиять на само событие (например, отменить его)

Преимущества привязок по сравнению с уведомлениями:

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

Преимущества уведомлений по сравнению с привязками:

  • уведомления могут сообщать об изменении не только свойств,
  • может быть неудобно делать классы KVO, KVB совместимыми.

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

Рекомендации по использованию уведомлений:

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

The Notification Object

NSNotification. Содержит имя (тег) уведомления, объект, который может быть любым объектом, который отправитель хочет отправить наблюдателю. Обычно - это сам объект отправителя (аналог sender). Также содержит словарь для передачи вспомогательной информации.

Notification Centers

NSNotificationCenter. Диспетчер уведомлений от отправителей к наблюдателям.

Notification Queues



Содержит очередь уведомлений. 

Очередь может очищаться от одинаковых уведомлений. Для определения одинаковости вводятся несколько критериев (по имени, по отправителю и т.д.). 

Уведомление может иметь индикатор для перемещения из очереди в центр уведомлений. Сам центр может находиться также в нескольких состояниях. Перемещение уведомления из очереди в центр происходит при совпадении индикатора уведомления и состояния центра.

Способы освобождения очереди (перемещения уведомлений в центр)

NSPostASAP - Posting As Soon As Possible. Уведомления в центре обслуживаются в цикле. При этом способе уведомление перещается в центр сразу же после окончания очередной итерации.

Пример: запросы на отрисовку составного изображения на поверхности окна. Запросы поступают из многих источников. Окончательное изображение формируется на сервере. Было бы накладно отправлять поверхность на сервер после обслуживания каждой операции. Поэтому все отправители, рисующий на поверхности, ставят в очередь уведомление FlushTheServer. Дубликаты этих уведомлений удаляются. Это уведомление имеет атрибут NSPostASAP. Как только текущий цикл обработки запросов на рисование будет выполнен, одно уведомление FlushTheServer будет передано в центр и выполнено, т.е. поверхность будет передана на сервер и отображена в окне.

NSPostWhenIdle - Posting When Idle. Уведомление уходит в центр, если он находится в состоянии ожидания. Время жизни входов центра может закончиться и он перестанет находиться в состоянии ожидания. В этом случае уведомление не будет отправлено.

NSPostNow - Posting Immediately. Синхронный способ передачи. Центр обслужит уведомление и сообщит, что наблюдатели его обработали.

Ownership of Delegates, Observers, and Targets

Владеть делегатами, наблюдателями и целевыми объектами для отправки им действий должны клиенты, а не хост-классы, центр уведомлений и ячейки. Конечно, на них устанавливаются т.н. слабые указатели (без владения, т.е. увеличения счетчика ссылок). Запрет на владение устанавливается, чтобы избежать циклического владения, когда два объекта владеют друг другом. Они не будут никогда удалены.

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

Поэтому при архивировании сохранять все эти объекты нужно явно.

пятница, 1 мая 2009 г.

1.05.09. The Target-Action Mechanism.

Механизм, связывающий событие в контроле GUI и инструкцию по его обработке в некотором другом объекте.

Событие гененерирует cell, в котором размещен контрол. Ловит событие (т.е. сообщение о нем) target, его обработчик события - это action.

Cell обладает информацией о том, как сгенерировать и послать событие. Также она содержит в виде outlet-а информацию о target-е.

Этот outlet может иметь значение nil. В этом случае обработчик события может быть назначен или определен в процессе работы программ. В качестве обработчика подойдет любой объект, у коротого есть реализация нужного метода. Поиск объекта ведет NSApplication.

Определение объекта-обработчика идет по следующей последовательности:

  1. Проверяется, т.е. ищется подходящий метод, у first responder-а в key window (окно, которое реагирует на нажатия клавиш и сообщения от меню и диалогов). От first responder с помощью nextResponder перемещаемся по цепочке responder-ов до класса окна NSWindow.
  2. Проверяется NSWindow и его делегат.
  3. Если главное окно не совпадает с key window, то проверяется цепочка от first responder до NSWindow и делегата главного окна.
  4. Последним объектом для поиска является объект приложения и его делегат.

Контролы не являются владельцами target-ов также, как хост-классы не являются владельцами делегатов и источников данных. Их владельцами дожны быть клиенты контролов и хост-классов.

Action - это и сообщение, отправляемое контролом target-у и метод-обработчик target-а.

Cell содержит действия в виде переменных типа SEL (указатель на функцию). 

Сигнатура сообщения для действия в cell:

- (IBAction) deleteRecord:(id)sender;

IBAction - это void для IB.

Для того, чтобы разгрузить контрол в ячейки перенесена некоторая работа из контрола, например, вся работа по реализации механизма target-action. 



NSMatrix может содержать несколько ячеек. 

NSCell  - абстрактный класс. NSActionCell - конкретный класс, содержащий переменные для target и action.

Ячейка также обычно ответственна за отображение контрола, например, она подсвечивает его, когда над ним проводится курсор.

Иногда, как например у  NSMenuItem, ячейка не используется и необходимые переменные содержатся сразу в контроле.

Для программной установки target и action:

- (void)setTarget:(id)anObject;
- (void)setAction:(SEL)aSelector;

Примеры:

[aCell setTarget:myController];
[aControl setAction:@selector(deleteRecord:)];
[aMenuItem setAction:@selector(showGuides:)];

1.05.09. Delegates and Data Sources

Data Sources

Делегаты обычно добавляются в классы графического интерфейса. Источники данных являются аналогами делегатов, но добавляются в классы представления (NSView), работающие с данными. Их задачами является предоставление данных таким классам, как NSView и обработка запросов от пользователя на изменение данных.

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

Implementing a Delegate for a Custom Class

1. В h-файл добавляется объявление делегата:

- (id)delegate;
- (void)setDelegate:(id)newDelegate;

2. В m-файл добавляется реализация методов доступа. Хост-класс не должен становиться владельцем делегата. Владельцем должен быть клиент хост-класса.

- (id)delegate {
  return delegate;
}
 
- (void)setDelegate:(id)newDelegate {
  delegate = newDelegate;
}

3. Добавить формальный или неформальный (категория NSObject) протокол к хост-классу.

4. Перед обращением к методу делегата проверить, что он его реализует.

- (void)someMethod {
  if ( [delegate respondsToSelector:@selector(operationShouldProceed)] ) {
  if ( [delegate operationShouldProceed] ) {
  // do something appropriate
  }
  }
}

четверг, 30 апреля 2009 г.

30.04.09. T&T. Узнать, в каком классе упало приложение

Если нужно узнать, в каком классе произошло обращение к освобожденному указателю, то

Executables - Get Info - Arguments - Add Variable - NSZombieEnabled=YES

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

Также для обнаружения утечек памяти служит инструмент Leaks. (Run - Start with Performance Tool - Leaks).

30.04.09. Outlets, Delegates.

Outlets

Outlet - переменная экземпляра, с помощью которой устанавливается связь с другим объектом. Эти связи архивируются в nib-файл и восстанавливаются при загрузке nib-файла.

Обычно таким способом связываются объекты-элементы графического интерфейса и необходимые контроллеры, но могут быть связаны и любые объекты, чьи экземпляры представлены в IB.

Delegates and Data Sources

Делегирующий объект обычно наследует от NSResponder, т.е. становится способным обрабатывать событие от пользователя.

Делегирующий класс имеет у себя свойство или outlet с именем delegate.

Также он должен объявить без реализации несколько методов, которые будет реализовывать делегат. Эти методы должны быть объединены в формальный или неформальный (категория NSObject) протокол. Из формального протокола делегат будет обязан реализовать все методы, кроме отмеченных как optional. Из неформального протокола он может реализовывать любые методы.


Имя метода для делегата состоит из типа возвращаемого значения, имени делегирующего объекта с маленькой буквы без NS, аргументов.


- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename; // NSApplication
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url; // UIApplicationDelegate
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; // NSApplication
- (UITableRowIndexSet *)tableView:(NSTableView *)tableView willSelectRows:(UITableRowIndexSet *)selection; // UITableViewDelegate
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame; // NSWindow

Имя обычно включает глаголы: “Should” or “Will” для событий, которые только должны произойти и “Did” or “Has”, которые уже произошли.

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

Для произошедших событий сообщения делегатам носят информирующий характер.


- (void) tableView:(NSTableView*)tableView mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn; // NSTableView
- (void)windowDidMove:(NSNotification *)notification; // NSWindow
- (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame; // UIApplication
- (void)applicationWillBecomeActive:(NSNotification *)notification; // NSApplication

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

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

Сделать свой объект делегатом можно в IB, соединив outlet`а delegate с экземпляром своего объекта.

Делегирующие объекты не должны увеличивать счетчик ссылок на делегата, т.е. посылать ему retain. Однако клиенты делегирующего объекта должны проверить в каком состоянии находится делегат и послать ему retain.

Пример: пусть у приложения есть контроллер и у окон этого приложения есть контроллер и они являются делегатами соответственно приложения и окна. Тогда найти контроллер окна можно так:

id winController = [[NSApp keyWindow] delegate];

А контроллер приложения так:

id appController = [NSApp delegate];

30.04.09. Object Modeling.

Entity-relationship modeling позволяет представить структуру данных в некотором источнике данных таким образом, чтобы данные можно было сопоставить объектам в ОО модели.

Object modeling - разновидность Entity-relationship modeling.

Сущности обладают атрибутами и отношениями с другими сущностями. Атрибуты + отношения - свойства. 

Атрибуты содержат данные. Эти данные могут находиться в переменных примитивных типов (integer, float, or double), структуры Си (array of char or an NSPoint), в экземплярах примитивных классов (NSNumber, NSData, or NSColor). Неизменяемые объекты (NSColor) тоже рассматриваются как атрибуты. 

Отношения указывают на другие сущности.

Отношения могут быть рефлексивными (от сущности к этой же сущности), однонаправленными и двунаправленными.

Кардинальность показывает ко скольким объектам одновременно может быть направлено отношение. Может быть от 0 до n или *.

Доступ к свойствам осуществляется по ключам. Все значения, полученные по ключу, одного типа. Если по ключу доступен объект, то возвращается объект. Если находится значение примитивного типа, то возвращается объект типа NSNumber or NSValue. 

Если отношение один-к-одному, то возвращается объект. Если один-ко-многим, то коллекция.

Key Paths

Сущность.Свойство.Свойство....

30.04.09. The Model-View-Controller Design Pattern

Объекты модели должны быть отделены от представления. За способ представления модели отвечает уровень представления. Модель может попросить представление отобразить себя. Но сделать это она должна обобщенным способом, чтобы не быть привязанным к одному типу представления.

Представление не должно нести ответственность за сохранение информации.

С помощью контроллеров происходит проверка, что представление имеет право обращаться к определенным объектам модели, а также модель сообщает представлению об изменениях.

Контроллеры интерпретируют действия над элементами интерфейса в действия над моделью.

Возможно объединение ролей: view-controller или model-controller.

Классическая реализация MVC:


Такой способ можно реализовать в Cocoa, использую технологию bindings, однако это свяжет представление с моделью, а они должны наиболее удобны для повторного использования. Поэтому в Cocoa всё взаимодействие идет через контроллер.


Напоминание: контроллеры могут быть медиаторами и координаторами.

Медиаторы (от NSController) просто переправляют запросы с помощью target-actions.

Координаторы являются делегатами окон и объекта приложения, поэтому они могут находиться в цепочке (responder chain).

Обычно координаторы владеют медиаторами, которые сохраняются в nib-файл.



Design Guidelines for MVC Applications

В качестве контроллера можно использовать класс производный от NSController или от NSObject.

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

Наименее повторно используемы контроллеры, наиболее - классы представлений и модели.

NSController дает дополнительные возможности по сравнению с простой привязкой. Например, возможность отката изменений.

Предпочтительные зависимости:

  • представление не должно зависеть от модели
  • представление не должно зависеть от медиатора
  • модель может зависеть только от другой модели
  • медиатор не должен зависеть от модели
  • медиатор не должен зависеть от представления или от координатора
  • координатор зависит от всех других участников.

Model-View-Controller in Cocoa (Mac OS X)

Document architecture.

Контроллер для всего приложения - NSDocumentController.

Контроллер для каждого из окон - NSWindowController.

Контроллер+модель - NSDocument.

Bindings.

NSController

среда, 29 апреля 2009 г.

29.04.09. Design Patterns. Memento, Наблюдатель, Прокси, Одиночка, Шаблонный метод

Memento

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

Применяется при архивировании объектов. Объекты при этом должны реализовывать протокол NSCoding. Для архивирования используется объект NSCoder и классы NSKeyedArchiver and NSKeyedUnarchiver.

В простых случаях возможно также сохранение объектов классов NSDictionary, NSArray, NSString, NSData, NSDate, and NSNumber в виде списка свойств. Этот способ работает только для графа объектов, состоящего из указанных классов.

Кроме того, для сложных графов объектов можно применять фреймворк Core Data.

Observer

Зависимость один-ко-многим.

Notifications

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

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

Можно использовать для того, чтобы проверить, что все объекты документа сохранили своё состояние перед тем, как окно документа закроется.

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

Key-Value Observing

Уведомление наблюдателей об изменениях в свойствах наблюдаемого объекта. Умедомление происходит напрямую без центрального объекта.

В MVC таким способом представление уведомляется об изменениях в модели.

Для работы с этим типом уведомлений объекты должны быть KVO-совместимыми.

Proxy

Прокси похож на декоторар. Прокси контролирует доступ к субъекту, декоратор добавляет к нему поведение (и возможно состояние).

Для создания прокси можно использовать абстрактный класс NSProxy, который реализует протокол NSObject и поэтому его можно использовать как корневой объект.

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

Singleton

Единственный объект, имеющий доступ к ценному ресурсу. Создается при первом обращении.

Примеры:

NSFileManager, NSWorkspace, NSApplication

Template Method

Задает структуру алгоритма. Некоторые шаги могут быть переопределены в подклассах.

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

29.04.09. Design Patterns. Категории, фасад, итератор, медиатор

Categories

Категории являются еще одним способом расширить класс (только добавить поведение) без создания подкласса.

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

Категории добавляются к классу на этапе компиляции.

Facade

Предоставляет удобный доступ к подсистемам.

Пример: NSImage, который содержит несколько вариантов представлений изображений, которые автоматически выбираются в зависимости от разрешения и т.п.

Iterator

Перекладывает ответственность по работе с элементами коллекций с самих коллекций на специальные объекты-итераторы. Структура коллекции скрывается от клиента.

Производные от NSEnumerator классы задают поведение итератора для различных коллекций.

NSArray, NSSet, and NSDictionary имеют метод, возвращающий подходящий итератор. Работа с любым итератором задается методами NSEnumerator. Например, для получения очередного элемента нужно отправить сообщение nextObject.

Mediator

Централизует взаимодействие объектов. Объекты начинают общаться не напрямую, а через посредника. Это позволяет менять способ взаимодействия объектов и посредника так, что изменения, касающиеся одного объекта, не затрагивают других.

Контроллеры являются посредниками в MVC.

Контроллеры-медиаторы (производные от NSController) перенаправляют запросы от представления к модели. Поддерживают технологию привязок (bindings) элементов интерфейса к объектам модели. Экземпляры таких контроллеров доступны через IB и поэтому доступны для повторного использования.

NSObjectController - для управления одиночным объектом из модели.

NSArrayController - для управления набором объектов модели. Управляет выбором, добавлением и удалением.

NSTreeController - для управления иерархической структурой объектов модели.

NSUserDefaultsController - для управления настройками.

Контроллеры-координаторы централизуют взаимодействие между объектами. Обычно производны от NSWindowController или NSObject. Являются делегатами классов из фреймворка и целями (targets) для сообщений-действий. Из-за реализации под конкретную программу не взаимозаменяемы.

Без контроллеров-медиаторов связать можно любую пару объектов, если они реализуют неформальные протоколы NSKeyValueCoding and NSKeyValueObserving. Но лучше использовать явного посредника, производного от NSController.

Задачи координирующих контроллеров, решаемые через IB:

  • управляют outlets между объектами модели и объектами представления,
  • являются целями в схеме target-action, сообщения-действия инициируются элементами пользовательского интерфейса,
  • являются делегатами для классов фреймворка.

Все эти связи сохранются в nib-файл.

вторник, 28 апреля 2009 г.

28.04.09. Design Patterns. Абс. фабрика, адаптер, цепочка, команда, target-action, композит, делегат

How Cocoa Adapts Design Patterns

Два основных паттерна в Cocoa: “The Model-View-Controller Design Pattern” and “Object Modeling.”

Abstract Factory

Разновидность фабрики реализуется через Class Cluster.

Adapter

Под адаптером понимается способ добавить классу протокол, так чтобы другой объект, умеющий работать с этим протоколом, а не с любым интерфейсом любого класса, мог работать с этим классом.

Интерфейс - как способ  скрыть конкретный класс.

Chain of Responsibility

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

Обычно такая цепочка получается в случае композиции элементов окон и представлений. Поиск обработчика начинается с самого нижнего объекта в иерархии и в конце концов может дойти до корнего объекта Window. Также сообщение может обработат делегат окна.

Цепочек может быть несколько, но только одна из них может быть активной.

Для организации объектов цепочки нужно использовать базовый класс NSResponder.

Command

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

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

The Target-Action Mechanism

Элемент управления (кнопка и т.п.) может послать сообщение целевому объекту (обычно контроллеру), в результате которого будет выполнено некоторое действие. Действие определяется селектором. Объект Cell элемента управления объединяет целевой объект и действие, т.е. обработчик сообщения. Элемент управления размещен в ячейке.

Composite

Используется в MVC при построении View. Корневым элементом является NSWindow. В нем находится Content View, прозрачная область покрывающая всю внутренную часть окна.



В итоге все View образуют иерархию представлений. Сообщение, отправленное представлению, также доходит и до его дочерних представлений.

Представление имеет две рамки: внешнюю frame и внутренню bounds.

Frame определяет положение представления относительно родительского представления.

Bounds задает координаты внутренней области представления.

Decorator

Воплощает принцип: класс должен быть открыт для расширения, но закрыт для изменения.

Delegation

Первый способ расширить класс (добавить поведение) без создания подкласса.

Хост-объект содержит ссылку на объект-делегат. Сам хост-объект умеет выполнять некоторую операцию обобщенно, что приводит к тому, что реальные действия по этой операции выполняет объект-делегат.

Хост-объект - обычно класс фреймворка. Делегирование - способ конкретизировать поведение такого класса без создания подкласса.

Делегат в Cocoa должен реализовывать методы формального или неформального протокола хост-класса. Хост-класс, прежде, чем отправить сообщение делегату, проверяет наличие метода с помощью respondsToSelector:.

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

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

Делегат может быть только один. Если нужно, чтобы о событиях в хост-классе уведомлялись несколько объектов, то нужно использовать механизм уведомлений (notifications).

пятница, 24 апреля 2009 г.

24.04.09. Design Patterns. Command

Cocoa Design Patterns

Решение задачи, находящейся в контексте.

Патерном описывается обобщенное решение задачи, достигающее некоторой цели при наличии ограничений. Задача рассматривается для определенного контекста.

Конкретная структура решения - "экземпляр" паттерна.

Инкапсулировать изменяющиеся части системы. Проектировать в терминах интерфейсов, а не реализаций.

An Example: The Command Pattern


В Cocoa реализован в NSInvocation для промежуточного хранения отправленных сообщений (undo, состояния).


24.04.09. Ошибки, ресурсы

Error Handling

Исключения в Cocoa предназначены для незапланированных ошибок.

Для запланированных исключительных ситуаций (не открыт файл и т.п.) используется nil, NO, NULL. Чтобы сообщить дополнительную информацию об ошибке используется NSError.

Чтобы сообщить пользователю об ошибке можно использовать NSAlert.

Resource Management and Other Efficiencies

Ресурсы нужно использовать только по необходимости.

Для каждого copy и retain должен быть свой release.

Functions, Constants, and Other C Types

Для повышения производительности можно пользоваться средствами Си: функциями, структурами, enum-ами.

четверг, 23 апреля 2009 г.

23.04.09. The Foundation, Application Kit class hierarchy


Zooming in on the Cocoa architecture—some major dependencies



The Foundation class hierarchy






Overview of the Application Kit




23.04.09. Обязательные методы для подкласса.

Object Infrastructure

Подкласс должен почти всегда реализовывать следующие методы:

  • isEqual: and hash
  • description
  • copyWithZone:
  • initWithCoder: and encodeWithCoder:

23.04.09. Key-Value.

Key-Value Mechanisms

Существуют Key-value binding, key-value coding, and key-value observing.

  • KVB управляет привязками объектов друг к другу.
  • KVC c помощью реализации неформального протокола NSKeyValueCoding позволяет получить значение свойства по его имени через специальный метод этого протокола.
  • KVO c помощью реализации неформального протокола NSKeyValueObserving позволяет объектам регистрироваться в качестве наблюдателей за другим объектом. Наблюдаемый объект автоматически уведомит наблюдателей при изменении своих свойств.

Для того, чтобы сделать свойства объекта совместимыми с этой техникой:

  • в случае атрибутов или отношений 1-к-1 нужно для setter-а заводить метод setKey, а для getter-а - Key,
  • в случае свойства как отношения 1-ко-многим, т.е. коллекций в объекте, нужно getter-у давать имя свойства и если getter не должен возвращать изменяемую коллекцию, то нужно реализовать insertObject:inKeyAtIndex: and removeObjectFromKeyAtIndex: из NSKeyValueCoding.

Для автоматического KVO объект должен удовлетворять KVC.

23.04.09. Методы жизненного цикла. Свойства, getter-ы и setter-ы.

Basic Subclass Design

Объявление класса:



@interface Controller : NSObject {
// атрибуты
}
// методы
@end

При наследовании от некоторого полезного класса фреймворка придется переопределять некоторые методы. После этого нужно сделать так, чтобы эти методы были вызваны фреймворком вместе методов базового класса. В Interface Builder обычно для этого указывается порожденный класс вместе базового.

Instance Variables

Переменная с IBOutlet показывает, что внешние связи будут восстановлены автоматически при загрузке nib-файла.

Entry and Exit Points

Cocoa посылает объектам и классам, которые тоже являются объетком, в разные моменты жизни объекта определенные сообщения. 

  1. initialize. Сообщение получает класс. Это самое первое сообщение, получаемое классом до того, как сообщения получат экземпляры. Сначала сообщение получает суперклассы, затем подклассы. Обычно используется для инициализации некоторого общего состояния, используемого всеми экземплярами.
  2. init. Инициализация экземпляра. Может быть будет достаточно выделенного инициализатора суперкласса.
  3. initWithCoder:/encodeWithCoder: Используется при десериализации, сериализации.
  4. awakeFromNib. Посылается всем объектам, загружаемым из nib-файла, но после того, как будут созданы все экземпляры объектов. Это гарантирует, что можно будет установить все указатели IBOutlet для данного объекта.
  5. dealloc or finalize. Деструкторы.

Объект приложения NSApp посылает сообщения

  • applicationDidFinishLaunching:,
  • applicationWillFinishLaunching:,
  • applicationWillTerminate:

своему объекту-делегату, который может обработать эти ситуации.

Initialize or Decode?

Для сериализации/десериализации (архивирования, разархивирования) объект должен реализовать протокол NSCoding. При разархивировании ему будет послано сообщение initWithCoder:. Если объект нужно инициализировать не только при разархировании, то нужно написать обрабочик и для init.

Исключение: подкласс для класса фреймворка, связанный с Object в IB. В этом случае, экземпляру будет послано init, поэтому лучше всю инициализацию делать вообще в awakeFromNib.

Storing and Accessing Properties

Свойства могут быть двух видов: атрибуты и отношения.

Свойства могут  хранить экземпляры (т.е. не обязательно хранят).

Taking Advantage of Declared Properties

Возможны два способа работы со свойствами: в случае использования garbage collector (1) и в случае memory-managed code (2).

2: В свойствах setter должны быть заданы атрибуты retain или copy. При использовании NSCopying всегда нужно указывать copy.


1: Генерируются setter для name и getter для accountID.


@interface MyClass : NSObject {
NSString *name;
NSNumber *accountID;
}
@property (copy) NSString *name;
@property (readonly) NSNumber *accountID;
// ...
@end
@implementation MyClass
@synthesize name, accountID;
// ...
@end


2: Для currentHost в getter будет увеличивать счетчик ссылок. Для hidden будет сгенерирован getter с заданным именем.


@interface MyClass : NSObject {
NSHost *currentHost;
Boolean *hidden;
}
@property (retain, nonatomic) NSHost *currentHost;
@property (getter=isHidden, nonatomic) Boolean *hidden;
// ...
@end
@implementation MyClass
@synthesize name, hidden;
// ...
@end

Implementing Accessor Methods

Методы getter и setter должны себя вести в соответствие с требованиями к управлению памятью. Setter должен позаботиться об освобождении памяти, занятой предыдущим значением.

Правила:

  • Полученный из getter объект можно безопасно использовать в области видимости вызвавшего getter метода. 
  • Вызывающий getter метод не должен релизить полученный объект в своей области видимости, не отправив ему предварительно retain или copy.


- (NSString *)title {
return title;
}
- (void)setTitle:(NSString *)newTitle {
if (title != newTitle) {
[title autorelease];
title = [newTitle copy];
}
}


В примере выше возможна опасная ситуация: клиент получит указатель на title и почти одновременно с этим будет вызван setter, который авторелизнет title. Через некоторое время пул будет очищен и title перестанет существовать. Клиент останется при этом с недействительным указателем.


Вариант лучше:


- (NSString *)title {
return [[title retain] autorelease];
}
- (void)setTitle:(NSString *)newTitle {
if (title != newTitle) {
[title release];
title = [newTitle copy];
}
}


Счетчик ссылок на title в getter-е увеличен на 1 и title будет послано 1 сообщение release, когда будет чиститься пул. Поэтому клиент останется с действительным указателем даже после [title release] в setter-е.


Когда посылать retain, а когда copy при передаче объектов?

Для этого нужно ответить на вопрос: что нужно получить, сам объект как таковой или его значение? Сам объект возможно изменит своё значение, но это не страшно, если нам нужна именно сущность. Конкретное значение, например, строки вероятно более важно, чем просто какая-нибудь строка.

Для setter-а можно рассуждать так. Если он устанавливает значение атрибута объекта, т.е. некое примитивное значение типа строки, числа или цвета, то это значение нужно скопировать из передаваемого параметра. Если же устанавливается отношение, т.е. отношение с другим объектом, то скорее всего нужно послать параметру retain.

Если придерживать "ленивого" использования атрибутов, т.е. вызывать getter только, когда атрибут понадобится, то его значение нужно устанавливать также в этом getter. Т.е. setter нельзя вызывать явно.

среда, 22 апреля 2009 г.

22.04.09. Наследование от классов Cocoa

Adding Behavior to a Cocoa Program

Основной координирующий объект приложения - NSApplicationMain. Он получает события, находит для них объекты-обработчики и так по кругу. 

Последовательность такая: объект создается, устанавливается autorealese пул, загружается интерфейс из nib-файла, управление передается пользователю, т.е. обрабатываются внешние события.

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

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

Вызовы из библиотеки можно рассматривать как использование независимых друг от друга сервисов.

В Cocoa есть классы, предоставляющие услуги

  • напрямую,
  • предназначенные для выполнения работы за кадром,
  • в обобщенном виде (требуют конкретной реализации),
  • через делегирование и уведомления.

Класс, допускающий делегирование, имеет протокол (интерфейс), методы которого могут реализовать делегаты. Делегат должен при этом зарегистрироваться у делегирующего класса.

Класс может опубликовать список уведомлений, которые он может рассылать всем заинтересованным объектам-подписчикам.

Делегаты и уведомления являются способами встраивания своего кода в фреймворк.

Cocoa API Conventions (некоторые)

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

Строки могут быть литералами (начинаются с @"") или константами. Предпочтительно использовать константные строки.

Метод, возвращающий значение атрибута, должен иметь имя атрибута, устанавливающий - начинаться с set, а затем имя атрибута с большой буквы.

Inheriting From a Cocoa Class

Значительная часть фреймворка имеет обобщенный вид (абстрактные классы). 

В Cocoa часто происходит так: метод моего класса вызывает определенный метод фреймворка, а он в свою очередь вызывает какой-то свой метод, переопределенный в моем классе.



Types of Overridden Methods

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

Некоторые методы имеют реализацию по умолчанию, возвращающую некоторое значение (YES или NO). Это значение определяет, включена или выключена некоторая возможность. Можно переопределить этот метод, чтобы переключить возможности.

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

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

When to Make a Subclass

Нужно определить необходимый базовый класс (например, для MVC).

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

Также не стоит забывать о возможности композиции с одновременным наследованием от того же класса.

21.04.09. Изменяемость. Class Clusters. Категории, протоколы.

Cocoa - Изменяемость (mutability) объектов 

Некоторые классы неизменяемые, производные от них - изменяемые. 

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

Неизменяемые (immutable) лучше сделать, когда  данные объекта будут меняться значительно (NSString,NSData). 

Изменяемые (mutable) данные объекта будут меняться постепенно (массивы, коллекции). 

Изменяемые коллекции можно делать неизменяемыми сделав копию. Копия по умолчанию будет неизменяемая. Ее можно отдать клиенту. 

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

Могут быть проблемы, если хранить изменяемые объекты в коллекциях 

Cocoa - Class Clusters 

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

В абстрактном классе есть методы-примитивы, которые обязательны для переопределения в производных классах. Эти методы обращаются к данным производного класса. 

Остальные методы реализуются через методы-примитивы. 

Composite Object - декоратор. 

Cocoa - Категории, протоколы 
Категория - способ добавить методы к классу не создавая подклассов 

По категориям удобно разделить различные аспекты класса 

Категории можно назначить и NSObject. Добавленные методы будут присутствовать у всех объектов в программе.  

Неформальный протокол - это категория NSObject. Необязательно реализовывать все методы неформального протокола. Проверка реализации осуществляется через respondsToSelector:  

Протокол - аналог интерфейса 

Множественное наследование протоколов 

Проверка реализованности протокола у объекта 

20.04.09. Introspection. Сравнение объектов.

Cocoa - Introspection 

Обнародование информации объектом о себе во время выполнения. 

Поддержка протокола, место в иерархии, способность отвечать на сообщение. 

isKindOfClass - будет работать с объектом-получателем для класса аргумента и для производных классов от класса аргумента. 

isMemberOfClass - только для класса аргумента. 

respondsToSelector - имеет ли объект реализацию метода? 
- (void)doCommandBySelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
[self performSelector:aSelector withObject:nil];
} else {
[_client doCommandBySelector:aSelector];
}
}

conformsToProtocol - реализует протокол? 
- (void)doCommandBySelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
[self performSelector:aSelector withObject:nil];
} else {
[_client doCommandBySelector:aSelector];
}
}

 

Сравнение объектов

 

hash - два одинаковых объекта имеют одинаковый хеш

isEqual - проверяет указатели на равенство

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

  • равенство указателей 
  • совпадение классов
  • совпадение свойств объектов по значению 

Булевские значения - YES, NO

20.04.09. Создание объектов.

Cocoa - Создание объектов 


При инициализации объекта с помощью init... необходимо проверить, что родительский класс вернул правильный объект. 


У родителя могут быть в свойствах объекты, которые нужно инициализировать.


При инициализации объектов ссылками на др. объекты увеличивать счетчик ссылок.

 
Если вместо созданного объекта нужно вернуть ранее существовавший, то у созданного нужно уменьшить счетчик ссылок. 


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


designated initializer отправляет super сообщение, вызывающее у того designated initializer. 


designated initializer обычно имеет больше всех аргументов. 


Остальные инициализаторы вызывают друг друга или designated initializer. 


Вызовы designated инициализаторов образуют цепочку в иерархии классов. 


Наследуемые инициализаторы должны быть переопределены в производном классе так, чтобы вызывался designated initializer производного класса. 


В dealloc объекта сначала освобождаются агрегированные объекты, а затем посылается dealloc вверх по иерархии. 

Основные правила: 

  • Если ты владеешь объектом, то ты ответственен за его освобождение (release).

  • Никогда не релизь объект, которым не владеешь.

Счетчик числа ссылок показывает число владельцев объекта.

16.04.09. Создание объектов, частичное владение.

Cocoa - Создание объектов 
Частичное владение 

При передаче объекта от создателя другим объектам нужно увеличивать счетчик ссылок (retain), чтобы создатель не удалил объект, пока он нужен другим объектам. Другие объекты после того, как переданный объект перестал был нужен посылают ему release, т.е. уменьшают число ссылок на 1. 

Сообщение autorelease приводит к автоматической отправке сообщения release при очистке пула, в котором регистрируется autorelease-объект. Этому объекту будет отправлено столько сообщений release, сколько ему было послано сообщений autorelease. 

Объекты, которым было послано сообщение autorelease, можно безопасно использовать после возвращения из метода, где они были созданы. За их уничтожение отвечает пул. За уничтожение обычных объектов отвечает получатель. 

Если autorelease-объект гарантировано нужен получателю, то нужно увеличить на 1 счетчик ссылок перед отправкой объекта получателю (т.е. отправить и retain и autorelease). 

У объекта есть указатель isa на объект его класса. С помощью него выбирается метод, отвечающий на сообщение. 

Alloc инициализирует isa и устанавливается счетчик ссылок в 1 

init может инициализировать не только выделенный alloc объект

<16.04.09. Типы контроллеров.

Types of Cocoa Controller Objects 

Медиаторы - наследники NSController (NSObjectController, NSArrayController, NSUserDefaultsController, and NSTreeController) 

Координаторы - NSWindowController or NSDocumentController или наследники NSObject 

MVC as a Compound Design Pattern 
 

Координаторы управляют nib-файлом, в котором содержатся представления и медиаторы 

Other Cocoa Architectures on Mac OS X 

 

NSDocumentController управляет NSDocument, который управляет NSWindowController.  

NSWindowController - это представление-контроллер, контроллер владеет представлением. 

У документа может быть несколько окон. 

Документ - это модель-контроллер, контроллер владеет моделью