вторник, 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 структура цепочки отличается.