вторник, 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
  }
  }
}