苹果开发之COCOA编程(第三版)上半部分

第一章:什么是Cocoajava

1.1 历史简介程序员

1.2 开发工具:
Xcode、Interface Builder(一个GUI构建工具)。
在它们内部,使用gcc为编译器来编译代码,并使用gdb来查找错误数据库

1.3 语言
Objective-C的代码由gcc编译-GUN C编译器。该编译器容许自由的在同一文件中混合C、C++及Ojbective-C代码
GUN调试器——gdb,用来设置断点,运行时查看程序中变量的值编程

1.4 对象、类、方法和消息设计模式

对象就像C语言中的结构:它占用内存空间来保存本身的变量。这些变量被称为“成员变量”。因此在处理对象时,首先想到的一个问题是:怎样给一个对象分配空间?这个对象拥有哪些成员变量?在处理完对象后,怎么释放它?
对象的某些成员变量多是指向其余对象的指针,这些指针使一个对象“know about(知道)”另一个对象
类是能够用来建立对象的结构。类中定义了对象中拥有的变量,而且负责为对象申请内存空间——对象是类的一个实例
对象优于结构的地方是它能够包含函数——方法数组

1.5 框架
Cocoa由如下3个framework组成:
1. Foundation:全部的面向对象语言都会有一些标准数值、集合和工具类。字符串、日期、列表、线程和计时器都再Foundation框架中。
2. Appkit:全部和用户界面相关的类都在Appkit框架中。窗口、按钮、文本框、事件,以及画图类包含在Appkit中。它还有个名字:ApplicationKit
3. Core Data:它可让你很方便地把对象存储成文件或把对象从文件中加载到内存。Core Data是一个持久性(持续性)框架缓存

1.7 常见错误
C和OC是大小写敏感的
使用IB时,忘记链接网络

 

第二章:起步数据结构

 1.几个主要类型的项目:
Application:带有窗口的程序
Tool:没有用户界面的程序。也就是命令行工具或是后台程序
Bundle 或 Framework:能够被其余程序或者工具使用的资源目录。Bundle(也叫Plug-in)在运行时动态加载。一般应用程序在编译时须要链接某些框架多线程

2.InterfaceBuilder
Library窗口:有用户界面对象的部件
空白窗口:表明了一个储存在你的nib文件中的NSWindow类的实例对象
File's Owner是NSApplication对象。这个对象从事件序列中得到事件,并将它们转发给相应地窗口。

@interface Foo: NSObject{
    IBOutlet NSTextField *textField
}

Foo有一个成员变量textField:它是指向一个NSTextField对象的指针

3.OC代码与Java区别:

import com.megacorp.Bar;
import com.megacorp.Baz;

public class Rex extends Bar implements Baz{
    ......
}

类Rex继承至类Bar,并实现了Baz声明的方法

#import <megacorp/Bar.h>
#import <megacorp/Baz.h>

@interface Rex : Bar <Baz>{
  ....  
}

@end

4. Objective-C中的类型和常量:OC中用到了一小部分C语言中没有的数据类型:
id:指向一个任何类型的类对象的指针
BOOL:和char同样,可是表示的是一个布尔值
Yes:1;NO:0
IBOutlet:空的宏。能够被忽略(注意,InterfaceBuilder在读取类声明的.h文件时,经过它来获得指示,哪些成员变量是outlet)
IBAction:等价于void。和IBOutlet同样,IB获得指示,哪些方法是action方法
nil:和NULL同样,对象指针赋值为空时使用nil

5.#import保证头文件只被包含一次

6.OC与Java方法区别:

public void increment(Object sender){
    .....  
}
- (void) increment:(id)sender{
    ....
}

注意:OC中全部的方法都是public的,全部的成员变量都是protected的。另外OC是C的扩展,因此能够调用标准C和Unix库提供的函数,如:random()

7. awakeFromNib
全部的对象被压缩封装在nib文件里面。在程序启动,开始接收用户事件以前,这些对象被复活。IB让开发者编辑界面对象的属性,而后保存这些状态到一个nib文件中
在事件处理以前,对象解冻以后,会自动调用awakeFromNib方法。

8. 程序的运行过程
当进程开始后,调用了NSApplication对象NSApp. NSApp读取nib文件并把其中的对象解包(解冻)。而后给每一个对象发送awakeFromNib的消息,而后就开始等待接收事件了

当接收到用户的键盘或鼠标事件,window server 会把接收到的事件放到适当的应用程序的事件队列中。NSApp从队列中读取事件并转发给界面对象(好比一个按钮),这时咱们本身的代码将被调用。若是咱们本身的代码改变了某些view,这些view将重画。这样一个事件检查和反馈的过程就是 main event loop,以下图:

 

第三章:Objective-c 语言

1.NSMutableArray *foo = [[NSMutableArray alloc] init];
alloc方法返回一个指针,指向为这个对象分配好的空间。记住foo只是一个指针变量。在使用foo前必须对其进行初始化,发生init消息来完成初始化。方法init返回一个新的初始化后的对象
NSMutableArray *array; 这样只是声明了一个指针变量,指向一个NSMutableArray对象,此时尚未任何array对象存在

2. #import <Foundation/Foundation.h> : 包含了Foudation框架中全部类的头文件。由于这些头文件都是预编译的,全部编译它们不想要不少时间

3.

4.NSString 对应 C中的char*

5.NSObject、NSArray、NSMutableArray、NSString

NSObject是OC类继承树的根类。它有一个:- (NSString *)description 方法返回一个NSString对象来描述接收者。在调试器中使用po命令,就会调用此方法。一样,使用%@,也会调用此方法
- (BOOL)isEqual:(id)anObject : 此方法定义为当接收者和anObject为同一个对象时就相等——也就是它们指向相同的内存地址。
NSString重载了这个方法,比较接收者和anObject的字符是否相等

NSArray:一个NSArray对象包含指向其余对象的指针列表。指针有一个惟一的整型索引:中间不能有nil,这和java不同。经常使用方法:
- (unsigned)count;
- (id)objectAtIndex:(unsigned)i
- (id)lastObject;
- (BOOL)cotainsObject:(id)anObject;
- (unsigned)indexOfObject:(id)anObject;

NSMutableArray:继承于NSArray,扩展了增长、删除对象的功能,可使用NSArray的mutableCopy方法复制获得一个可修改的NSMutableArray对象。经常使用方法:
- (void)addObject:(id)anObject;//在接收者的最后插入anObject
- (void)addObjectFromArray:(NSArray *)otherArray;//
- (void)insertObject:(id)anObject atIndex:(unsigned)index;//在索引index处插入。若是index被占用,会把index索引以后的对象向后移动,腾出一个空间
- (void)removeAllObjects;
- (void)removeObject:(id)anObject;
- (void)removeObjectAtIndex:(unsigned)index;
不能将nil加到array中,但若是要给array加一个空的对象,可使用NSNull来给array添加一个站位对象:
[myArray addObject:[NSNull null]];

NSString:能够存储Unicode字符。它继承自NSObject。经常使用方法:
- (id)initWithFormat:(NSString *)format,...
- (unsigned int)length;
- (NSString *)stringByAppendingString:(NSString *)aString;

6.继承和组合
建议不要使用继承:如建立NSString、NSMutableArray的子类。最好是让一个较大的对象来包含NSString或NSMutableArray
Objective-c的基本编程思想是:大多数状况下,选择“有一个”,而不是“是一个”
对于强制类型语言,如C++,继承很是重要。可是对于非强制类型语言,如OC,继承就不那么重要了。

7.建立本身的类
NSCalendarDate继承自NSDate类。NSCalendarDate对象包含了日期、时间、时区及一个带有格式的字符串,如:
NSCalendarDate *now = [NSCalendarDate calendarDate];calendarDate建立并返回一个本地日期和时间的默认格式的NSCalendarDate对象,它的时区为机器所设置

+ (id)dateWithYear:(int)year month:(unsigned)month day:(unsigned)day hour:(unsigned)hour minute:(unsigned)minute second:(unsigned)second timeZone:(NSTimeZone *)aTimeZone;这个类方法返回一个自动释放的对象:
NSCalendarDate *hotTime = [NSCalendarDate dateWithYear:2000 month:12 day:30 hour:16 minute:59 seconds:59 timeZone:[NSTimeZone timeZoneWithName:@"PST"];

+ (NSCalendarDate*)dateByAddingYears:(int)year.......此方法返回参数指定的偏移量的日历对象

- (int)dayOfCommonEra;//返回从1A.D到如今的天数
- (int)dayOfMonth;//返回这个月的第几天(1到31)
- (int)dayOfWeek;//返回一周中的星期几(0到6,0是星期天)
- (int)dayOfYear;//返回一年中得第几天(1-366)
- (int)hourOfDay;//返回接收时的小时值(0到23)
- (int)minuteOfHour;//返回接收时的分钟值(0-59)
- (int)monthOfYear;//(1-12)
- (NSDate *)laterDate:(NSDate*)anotherDate;//此方法继承自NSDate,将接收者和anotherDate比较,返回二者中靠后的那个
- (NSTimeInterVal)timeIntervalSinceDate:(NSDate*)anotherDate;//此方法计算接收时与anotherDate之间以秒计算的时间差
- (void)setCalendarFormat:(NSString *)formart;//设置接收者的默认日历格式。日历格式是由一个包含日期转换说明的字符串,以下图:

编写Initializers(初始化器)、带参数的初始化器
建立initializer的规范:
若是父类的initializer够用,不要建立本身的initializer
若是要建立本身的initializer,必定要重载父类的designated initializer
若是建立了多个initializer,让其中一个作真正的初始化工做-designated initializer,其余的都调用它
你的designated initializer将要调用父类的designated initializer

总有一天你会遇到这种状况:你的类必需要有一个参数来进行初始化,那么你能够重载父类的designated initializer来抛出一个异常:

- (id)init
{
    [self dealloc];
    @throw [NSException exceptionWithName:@"BNRBadInitCall" reason:@"Initialize Lawsuit with initWithDefendant:" userInfo:nil];

    return nil;
}

8.调试器
NSAssert(theDate != nil, @"Argument must be non-nil");

9.消息机制的工做原理
类就像一个C结构体。NSObject声明了一个成员变量叫isa。由于NSObject是整个类继承树的根类,因此全部对象都会有一个isa指针,指向建立对象的类结构。而该isa变量指向该对象的类。类结构中包含了类定义的成员变量的名字和类型,以及实现了哪些方法。还包含一个指针,指向本身的父类结构

 

第四章:内存管理

1.Apple的两套解决方案:retain、垃圾收集器(iOS中是没有的)
若是使用垃圾收集器,当再也不须要某个对象时,就不要再指向它了。能够指向nil

2.实现dealloc
当retain计数为1时调用release,对象的dealloc方法将被调用。在dealloc方法中,你必须释放本身所retain的对象而且调用父类的dealloc方法

3.建立自动释放对象

- (NSString *)description
{
    NSString *result;
    result = [[NSString alloc] initWithFormat:@"%@", firstname];
    return result;
}

上面的代码有个内存泄露。alloc方法建立一个string对象,它的retain计数为1.在其余对象调用description方法获得string对象后,它会retain该string对象,此时string对象的retain计数为2。

- (NSString *)description
{
    NSString *result;
    result = [[NSString alloc] initWithFormat:@"%@", firstname];
    //[result release];//错误的解决
[result autorelease];//正确地解决方式
return result; }

错误的解决代码将不能正常工做。当调用release方法时,string对象的retain计数变为0,因此string对象将被释放。返回的是一个已经被释放的对象。
在没有启用垃圾收集器时,使用NSAutoreleasePool来实现它。
给对象发送autorelease消息后,对象将被添加到当前的自动释放池中。当自动释放池被释放时,首先会给池中得对象发送release消息。换句话说,当给一个对象发送autorelease消息时,就表示未来会给该对象发送release消息。
一般在Cocoa程序中,在接收事件时会建立自动释放池,在处理完事件后释放自动释放池。这样除非在中间对对象进行了retain,不然,在一个事件处理完后,全部的autorelease对象将被释放。

4.Release相关规则
使用alloc、new、copy或mutaleCopy建立对象,其retain计数为1,而且不会添加到自动释放池中
当使用任何方法获得一个对象,假定这个对象的计数为1,或者已经添加到了当前的自动释放池中,若是不但愿它随着当前的自动释放池一块儿被释放,必定要调用retain方法

由于咱们常常要使用,但又不但愿retain对象,因此不少的类都提供了一些类方法来返回自动释放对象。如NSString的stringWithFormat。因此上面代码可简单修改成:

- (NSString *)description
{
    NSString *result;
    result = [[NSString alloc] stringWithFormat:@"%@", firstname];
    return result;
}

 

5.临时对象:注意,在Cocoa程序中,对象要等到事件处理完成后才被自动释放,这样在中间必定会有临时对象存在。如:

//将一个NSString对象的序列,把其中的字符串对象变成大写,并链接一个字符串对象返回
- (NSString *)concatenatedAndAllCaps:(NSArray *)myArray
{
    int i;
    NSString *sum = @"";
    NSString *upper;
    for (i = 0; i < [myArray count]; i++) {
        upper = [[myArray objectAtIndex:i] uppercaseString];
        sum = [NSString stringWithFormat:@"%@%@", sum, upper];
    }
    return sum;
}

这个方法中,假定有13个字符串,你就建立了26个自动释放对象:13个uppercaseString,13个stringWithFormat:。除了返回的一个字符串对象可能被retain,其余的都在事件处理完成后释放。

6.Accessor方法
若是成员变量不是指针类型,accessor方法很简单:

- (int)foo{
    return foo;
}
- (int)setFoo:(int)x{
    foo = x;
}

当foo是指向其余对象的指针 ,在setter方法中,必需要retain新设置的对象,同时release原来的对象

//三种习惯用法
- (void)setFoo:(NSCalendarDate *)x{
    [x retain];
    [foo release];
    foo = x;
}//评价:若是它们指向同一个对象,retain和release都是多余的
- (void)setFoo:(NSCalendarDate *)x{
    if (foo != x){
        [foo release];
        foo = [x retain];
    }
}//评价:只有当foo和x指向不一样对象时,才会去改变。必须指向一次额外的if语句
- (void)setFoo:(NSCalendarDate *)x{
    [foo autorelease];
    foo = [x retain];
}//评价:若是存在retain计数的使用错误,那么只有当事件结束后才出现,这样不利于调试查找错误,并且,自动释放会影响必定得性能

getter方法和非指针类型同样

- (NSCalendarDate *)foo{
    return foo;
}

注意不要将方法命名为getFoo(像java同样)。在OC的编程习惯中,使用get做为前缀,表示须要拷贝对象内部的数据,如:
[myColor getRed:&r green:&g blue:&b alpha:&a];//&返回变量的地址

 

第五章:Target/Action

1.NSButton、NSSlider、NSTextView、NSColorWell等控件都是NSControl的子类。每一个控件都包含target和action。target是一个指向其余对象的指针。action是会发给target的message(selector)

当用户和控件交互时,就会给它们的target发送action消息。例如,点击一个按钮,将会给它的target发送action消息

action方法接收一个参数:发送者。该参数可让接收者知道是哪个控件发送了这个action消息

2.经常使用的NSControl子类
NSButton:
- (void)setEnabled:(BOOL)yn;//激活按钮。非激活的按钮是灰色的
- (int)state;//若是按钮是on状态,返回NSOnState(1),为off状态时,返回NSOffState(0)
- (void)setState:(int)aState;

NSSlider:
- (void)setFloatValue:(float)x;//移动滚动条到位置x
- (float)floatValue;//获得当前滚动条的位置

NSTextField:让用户输入单行文本
- (NSString *)stringValue;
- (void)setStringValue:(NSString *)aString;//这两个方法用来获取和设置文本框中的文本
- (NSObject *)objectValue;
- (void)setObjectValue:(NSObject *)obj;//获取和设置文本框内容数据的任意类型的对象

3.经过代码来设置target:
控件的action是一个selector:
- (void)setAction:(SEL):aSelector;
如何获取一个selector?使用Objective-c编译器指令@selector告诉编译器来查找一个selector。例如,要设置一个按钮的action为drawMickey:,能够:

SEL mySelector;
mySelector = @selector(drawMickey:);
[myButton setAction:mySelector];

若是要在运行时查找selector,使用NSSelectorFromString()函数:

SEL mySelector;
mySelector = NSSelectorFromString(@"drawMickey:");
[myButton setTarget:someObjectWithADrawMickeyMethod];
[myButton setAction:mySelector];

 

第六章:Helper对象

1.委托
Cococa框架不少类都有一个叫delegate的实例变量。可让这个变量指向一个helper对象

2.对象委托。委托是一个设计模式,下面是AppKit框架中有delegate outlet的一些类:
NSAlert、NSAnimation、NSApplication、NSBrowser、NSDatePicker、NSDrawer、NSFontManager、NSImage、NSLayoutManager、NSMatrix、NSMenu、NSPathControl、NSRuleEditor、NSSavePanel、NSSound、NSSpeechRecognizer、NSSpeechSynthesizer、NSSplitView、NSTableView、NSText、NSTextField、NSTextStorage、NSTextView、NSTokenField、NSToolbar、NSWindow

3.委托是如何工做的
NSObject有下面这个方法:
- (BOOL)respondsToSelector:(SEL)aSelector;
所以每一个对象都有这个方法。若是该对象有一个叫aSelector得方法,它就会返回YES
注意,只有委托对象实现了委托方法,它才会收到消息。若是没有实现,则执行默认动做(respondsToSelector的返回结果一般会缓存在delegate outlet的对象中,性能会比较好)
若是要观察是否存在某个委托方法的检查过程,能够在委托对象中重载respondsToSelector方法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    NSString *methodName = NSStringFromSelector(aSelector);
    NSLog(@"respondsToSelector:%@", methodName);
    return [super respondsToSelector:aSelector];
}

4.建立一个委托

 

第七章: Key-Value Coding;Key-Value Observing

1.KVC:Key-value coding 机制容许经过变量名设置(set)以及获取(get)变量值。变量名只是个字符串,但咱们通常称之为key。
所以若是类Student有一个firstName的变量,类型为NSString:

@interface Student:NSObject
{
    NSString *firstName;  
}
...
@end

就能够像下面这样设置和获取Student实例的firstName

Student *s = [[Student alloc] init];
[s setValue:@"Larry" forKey:@"firstName"];
//获取
NSString *x = [s valueForKey:@"firstName"];

setValue:forKey:和valueForKey:的方法是在NSObject中定义的

2.key-value coding方法只能处理对象,因此不能传入int,而要是NSNumber,可是它在赋值时会自动把NSNumber转换成其余基础数据结构,如int

3.绑定
在Cocoa中,不少图形对象都有bindings。当你绑定一个key,如fido,到一个图形对象的属性上(好比它的值或者它的颜色),视图会自动保持它们之间的同步(使用key-value coding保持同步)。

4.Key-Value Observing
当fido值改变而对象不变的时候会发生什么?图形对象怎么知道它有一个新值?
当图形对象(如文本框)建立后,它会告诉AppController它正在观察fido key(view观察key。key改变,controller通知view)。一旦fido的值被存取方法或者key-value coding改变,AppController就会给图形对象发生消息,通知它fido的值被修改了。

5.让Keys可被观察
若是直接修改变量的值,必需要显示触发并通知观察者:

- (IBAction)incrementFido:(id)sender
{
    [self willChangeValueForKey:@"fido"];
    fido++;
    [self didChangeValueForKey:@"fido"];
}

6.Properties和它们的Attributes
OC 2.0后使用以下一个property声明来替代fido和setFido方法:

@interface AppController:NSObject{
    int fido;
}
@property(readwrite, assign) int fido;
@end//这代码等价于声明setFido:和fido方法

在m文件中,用@synthesize来实现存取方法:@synthesize fido

7.Property的Attributes
@property (attributes) type name;
attributes包括readwrite(默认)、readonly(只有获取方法,没有设置方法)、assign、retain、copy:
assign:默认。建立一个简单地赋值语句。assign不保留新值,若是你处理的object类型,而且没有使用垃圾收集器,不要使用assign
retain:释放旧值,并retain新值。这个属性仅针对Objective-c对象类型时使用。若是使用垃圾收集器,assign和retain等价
copy:建立新值的拷贝,并让变量等于这个拷贝。这个属性经常使用在property是字符串的时候

attributes还能够包括nonatomic。若是程序是多线程的,设置方法时应该是原子的(atomic)。若是应用程序没有使用垃圾收集器,将会使用一个锁来确保每次只有一个线程执行该设置方法。建立和使用锁须要额外开销。

8.Key Paths
对象之间的关系常常是网状的。如一我的有一个配偶,配偶有一部踏板车,踏板车有一个型号:

为获得某我的的配偶的踏板车的型号,可使用key path:

NSString *mn;
mn = [selectedPerson valueForKeyPath:@"spouse.scooter.modelName"];

咱们能够说spouse以及scooter和Person类有关联,而modelName是Scooter类的attribute
你甚至能够在key path中使用操做符。例如,若是有一个Person的对象数组,能够经过使用key path来得到他们的平均expectedRaise:

NSNumber *theAverage;
theAverage = [employees valueForKeyPath:@"@avg.expectedRaise"];

下面是经常使用的操做符:
@avg、@count、@max、@min、@sum

了解了key path,如今就能够经过编程建立binding。若是有一个文本框里要显示一个array controller的arranged objects的平均加薪指望值,能够像下面只有建立一个绑定:

[textField bind:@"value" toObject:employeeController withKeyPath:@"arrangeObjects.@avg.expectedRaise" options:nil];
//解除绑定
[textField unbind:@"value"];

固然,在IB建立绑定更容易

9.Key-value Observing

文本框是如何成为AppController对象的fido key的观察者的?当从nib中唤醒文本框时,它将本身添加为观察者。若是想成为一个key的观察者,代码应该是这样的:

[theAppController addObserver:self forKeyPath:@"fido" options:NSKeyValueObservingOld context:somePointer];

这个方法是在NSObject中定义的。它就像是说:”嗨,当fido值改变的时候,给我发个消息吧“。option和context决定当fido值改变的适合,应该将那些额外的数据随消息一块儿发送出去。
触发方法以下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context
{
   .......
}

在这个例子宏,keyPath应该是@"fido",object应该是AppController,context则成为一个观察者时的上下文的指针,change字典是一个key-value对集合,保存fido的旧值和/或其新值

 

第八章:NSArrayController

1.MVC设计模式
Model:模型类型描述你的数据。View:视图类是GUI的一部分,是通用类。Controller:控制器类,负责控制整个应用的流程

2.NSController
NSController是一个抽象类。NSObjectController是它的子类,它的content是一个对象。NSArrayController的content则是一个对象数组

 

第九章:NSUndoManger

1.NSUndoManager能够给应用程序增长撤销功能。当添加、编辑以及删除对象时,undo manager(撤销管理器)会记录下undo这些修改须要的全部信息;而当撤销时,undo manager则会记录下redo这些修改的全部消息。这个机制使用两个NSInvocation对象栈

2.NSInvocation
把消息(包括selector(选择器)、接受者以及全部的参数)包装成一个对象,这样调用起来会很方便。这个对象就是NSInvocation的实例
invocation的最佳应用场合是消息转发。当一个对象收到一条它不理解的消息的时候,消息发送机制会在报错以前检查对象是否实现了下面这个方法:
- (void)forwardInvocation:(NSInvocation *)x;
若是对象有这个方法,消息就会被打包成NSInvocation对象,而后调用forwardInvocation:方法

3.假如用户打开RaiseMan文档并作了3处修改:
插入一条记录;把姓名从“New Employee”修改成“Rex Fido”; 把raise从0修改成20
每次修改完成后,你的控制器就会添加一个能够撤销本次修改的invocation到撤销栈中。下图显示了在作完这3次修改后,撤销栈的状态

若是用户点击Undo菜单,就从堆栈中取出第一个invocation并调用它。每次当一个元素从撤销栈中弹出并被调用的时候,undo操做的逆操做会被添加到redo栈中

撤销管理器的运做很是巧妙:当用户作修改的时候,undo invocation会被添加到undo栈中,当用户撤销修改的时候,undo invocation回到redo栈中。而当用户重作修改时,undo invocation又回到undo栈上。这些任务会被自动处理,你惟一要作的工做就是提供对应的逆操做,添加到撤销管理器中

假设你写了一个方法叫makeItHotter以及这个方法的逆方法makeItColder,而后就能够像下面这样实现撤销功能:

- (void)makeItHotter
{
    temperature = temperature + 10;
    [[undoManager prepareWithInvocationTarget:self] makeItColder];
    [self showTheChangesToTheTemperature];
}

prepareWithInvocationTarget:方法记下了target并返回撤销管理器自己,而后,撤销管理器重载了forwardInvocation方法,这样它就把makeIteColder方法的invovation添加到undo栈中
为了完成这个例子,还必须实现:

- (void)makeItColder
{
    temperature = temperature - 10;
    [[undoManager prepareWithInvocationTarget:self] makeItHotter];
    [self showTheChangesToTheTemperature];
}

每一栈的invocation是按组放置的。默认的,单个事件涉及的全部invocation放在同一个组里。所以,若是一个用户操做(单个事件)致使多个对象发送变换,只要点击一下撤销菜单,全部的改变都会被撤销

如何得到一个撤销管理器?能够显示建立一个。注意:NSDocument的实例已经有一个它本身的撤销管理器了
NSUndoManager *undo = [self undoManager];

4.Key-value Observing
kvc是一种经过变量名来读取及设置变量的方法,kvo容许当值发生变化时通知你。

为了可以撤销编辑,当对象的变量的值发送改变时,须要通知文档对象。使用addObserver.....方法

插入后开始编辑

5.窗口和撤销管理器
视图能够添加编辑到撤销管理器。如,NSTextView能够把用户所作的每个文字修改放到撤销管理器中。能够在IB中打开它
文本视图如何知道使用哪一个撤销管理器?首先,询问它的委托对象,NSTextView的委托对象能够实现这个方法:
- (NSUndoManager *)undoManagerForTextView:(NSTextView *)tv;

而后询问它的窗口,针对这个目的,NSWindow有一个方法:
- (NSUndoManager *)undoManager;
窗口的委托对象能够经过实现下面的方法来给窗口经过一个撤销管理器
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;

 

第十章:Archiving

1.面向对象的程序在运行的时候,会建立一个复杂的对象图。常常会要以二进制流的方法序列化这个对象图,这个过程叫archiving。二进制流能够经过网络发送或者写入文件中。(Java中称这个过程为serialization,而不是archiving)

当你要从二进制流中重修建立对象图时,能够unarchive它。例如,当应用程序开始运行的时候,它会从Interface Builder建立的nib文件中unarchive对象
尽管对象有实例变量和方法,但只有实例变量和类名会被archive。换句话说,只有数据,而不是代码,会被存档。
所以,若是一个应用archive了一个对象,而另外一个应用unarchive了这个对象,这两个应用必须有这个类的相关代码。
例如,在nib文件中,你使用了AppKit框架的NSWindow和NSButton类。若是没有连接AppKit框架,你的应用就不能为存档中得NSWindow喝NSButton类建立实例

2.NSCoder和NSCoding

NSCoding是一个protocol,有下面2个方法:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;

NSCoder是一个抽象的二进制流。你能够把数据写入coder或者从coder中读取数据。对象的initWithCoder:方法会从coder中读取数据,并把数据保存到它相应地实例变量中。对象的encodeWithCoder:方法会读取实例变量,并把这些数据写入到coder中去。
NSCoder是一个抽象类。抽象类不能被实例化,它只提供了一些想让子类继承的方法:
NSKeyedUnarchiver从二进制流中读取对象;NSKeyedArchiver把对象写到二进制流中

编码

NSCoder的经常使用方法:
- (void)encodeObject:(id)anObject forKey:(NSString *)aKey;
这个方法把anObject写到coder中,让它和key aKey关联,还会调用anObject的encodeWithCoder:方法

对于每种通用的C primitive types(原始类型,如int和float),NSCoder都有一个encode方法:
- (void)encodeBool:(BOOL)boolv forKey:(NSString *)key;
- (void)encodeDouble:(double)realv forKey:(NSString *)key;
- (void)encodeFloat:(float)realv forKey:(NSString *)key;
- (void)encodeInt:(int)intv forKey:(NSString *)key;

添加下面的方法到Person.m,给Person类增长encoding能力:

- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeObject:personName forKey:@"personName"];
    [coder encodeFloat:expectdRaise forKey:@"expectdRaise"];
}

NSString实现了NSCoding protocol,因此personName知道如何本身编码
全部经常使用的AppKit和Foundation类都实现了NSCoding protocol,除了NSObject。由于Person继承自NSObject,它不会调用[super encodeWithCoder:coder]。
若是Person的父类实现了NSCoding protocol,方法可能会是下面这样的:

- (void)encodeWithCoder:(NSCoder *)coder
{
    [super encodeWithCoder:coder];
    [coder encodeObject:personName forKey:@"personName"];
    [coder encodeFloat:expectdRaise forKey:@"expectdRaise"];  
}

调用父类的encodeWithCoder:方法给父类提供一个机会,让它把变量写到coder中,所以,继承树上的每一个类只负责把它本身的实例变量——而不是它的父类的实例变量——写到coder中

解码

从coder中解码数据时,会使用相似下面的方法:
- (id)decodeObjectForKey:(NSString *)aKey;
- (BOOL)decodeBoolForKey:(NSString *)aKey;
- (double/float/int)......

若是数据流中没有某个key的数据,那么获得的结果将为空。如为float类型,则返回0.0,若是是对象,则返回nil

给person类解码,添加下面代码到Person.m中:

- (id)initWithCoder:(NSCoder *)coder
{
    [super init];
    personName = [[coder decodeObjectForKey:@"personName"] retain];
    expectdRaise = [coder decodeFloatForKey:@"expectdRaise"];
    return self;
}

再次强调,不能调用Person父类的initWithCoder:的实现,由于NSObject没有这个方法。若是Person父类已经实现了NSCoding protocol,方法就应该是下面这样:

- (id)initWithCoder:(NSCoder *)coder
{
    [super initWithCoder:coder];
    personName....
    ....
}

第三章讲到designated initializer负责全部的工做并调用父类的designated initializer。全部其余的initializer调用designated initializer。Person已经有一个init方法,而且它是designated initializer,但这个新的initializer却没有调用它。这是对的,initWithCoder:是initializer规则的一个例外

3.文档架构
多文档应用程序之间有不少共同点。它们都能建立新文档,打开已有文档,保存或打印当前文档,以及当用户关闭窗口或者退出程序的时候,提醒用户保存已修改的文档。
Apple提供了3个类——NSDocumentController、NSDocument以及NSWindowController 帮助处理这些细节。这3个类构成了document architecture(文档架构)
当应用程序启动的时候,它会从Info.plist中读取信息,了解它处理的文件类型。若是发现这是一个document-based(基于文档)的应用程序,它就会建立一个NSDocumentController。你不多用到它,它潜藏在后台,为你打理不少细节问题。例如,当你在菜单中选择新建或者保存所有时,document controller就会处理这些请求。若是必须给document controller发送消息,应该:

NSDocumentController *dc = [NSDocumentController sharedDocumentController];

文档控制器有一个文档对象的数组对应每个打开的文档

文档对象是NSDocument子类的实例。对多数应用而已,只须要扩展NSDocument来作想作的事情,不须要操心NSDocumentController或NSWindowController

菜单Save、Save As、Save All和Close是各不相同的,但它们处理的是相同的问题:把模型存入文件或者文件包中(文件包是一个目录,但对用户而言,看上去像是一个文件)。要处理这些菜单项,你的NSDocument子类必须实现下面3个方法之一:
- (NSData *)dataOfType:(NSString *)aType error:(NSError *)e;//文档对象把模型做为一个NSData对象存入文件中。NSData本质上就是一个字节缓冲区。在一个基于文档的应用程序中,这是最容易,也是最广泛的保存内容的方法
- (NSFileWrapper *)fileWrapperOfType:(NSString*)aType error:(NSError *)e;//文档对象以NSFileWrapper对象的格式返回它的模型。它会根据用户的选择,把本身写到文件系统的某个位置
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError**)outError;//文档对象获得一个URL地址以及类型,而后负责把数据存到这个URL地址(URL通常只是文件系统中的一个文件)。outError:若是方法不能完成任务,它就建立一个NSError,并把该error指针放在指定的位置

文档的载入:Open、Open Recent、Revert To Saved:
- (BOOL)readFromData:(NSData*)data ofType:(NSString*)typeName error:(NSError **)outError;
- (BOOL)readFromFileWrapper:(NSFileWrapper*)fileWrapper ofType:(NSString*)typeName error:(NSError*)outError;
- (BOOL)readFromURL:(NSURL*)absoluteURL ofType:(NSString*)typeName error:(NSError**)outError;

当咱们打开一个文件时,首先读取文档文件,再读取nib文件。所以,在文件载入完成以前,不能发送消息给用户界面对象(由于他们还不存在)。在nib载入以后,文档对象会收到下面的消息:
- (void)windowControllerDidLoadNib:(NSWindowController *)x;

4.保存和NSKeyedArchiver
+ (NSData *)archivedDataWithRootObject:(id)rootObject;//此方法把对象存档到NSData对象的字节缓冲区中

5.载入和NSKeyedUnarchive:
+ (id)unarchiveObjectWithData:(NSData*)data;

6.防止死循环
若是对象A致使对象B被编码,对象B致使对象C被编码,而后对象C又致使对象A再次被编码,这就是死循环。设计NSKeyedArchiver的时候已经考虑这种状况:
当一个对象被编码的时候,一个惟一的token也会被放置到数据流中。一旦被存档,对象就会被添加到一个已编码对象表对应的那个token下。当要再次编码同一个对象时,NSKeyedArchiver只是简单地在流中加入token。

当从流中解码对象的时候,NSKeyedUnarchiver会同时把对象和token放到表格中。若是它找到一个token没有相对应的数据,unarchiver知道到表格中去找对象,而不是新建一个实例

- (void)encodeConditionalObject:(id)anObject forKey:(NSString*)aKey;
这个方法在这种状况下使用:对象A有一个指针指向对象B,但对象A并不真的关心对象B是否已经存档;若是另一个对象已经存档了B,A会把B的token放置到流中。但若是B没有被其余对象存档,它会被像nil同样对待

7.统一类型标识符
universal type identifiers(UTIs)。一个UTI就是一个标记文件类型的字符串。UTI具备层次结构

 

第十一章:Core Data基本原理

1.在运行时,程序读取模型文件来生成一个NSManagedObjectModel对象。
模型使用了不一样的术语。类称为实体,成员变量称为property。模型包含了两张property:attributes和relationships。attribute保存简单数据类型,如字符串、日期、数值

2.Core Data是怎么工做的
下图为对象关系图

NSPersistentDocument读取建立好的数据模型来生成一个NSManagedObjectModel对象。在本例中,managed object model有一个NSEntityDescription来描述Car实体。实体描述中包含了多个NSAttributeDescription对象
一旦有了模型,persistent document建立一个NSPersistentStoreCoordinator对象和一个NSManagedObjectContext对象。NSManagedObjectContext对象会从数据模型中取得NSManagedObject对象。当这些managed objected加载到内存的时候,managed object context就会监测这些对象

 

第十二章:Nib文件和NSWindowController

1.NSPanel继承NSWindow

2.File's Owner
当一个程序运行了一段时间后,须要加载一个新的nib文件,那么以前已经存在的对象就须要一些链接来访问这个新加载的nib文件中的对象。File's Owner提供了这样的链接。
在nib文件中,File's Owner其实就是一个已经存在的对象的占位符。加载nib文件的对象须要提供nib的全部者对象。全部者将取代File's Owner的位置

3.NSBundle
NSBundle是一个目录,其中包含了程序会使用到的资源,这些资源包含图像、声音、编译好的代码、nib文件(用户常常也把bundle称为插件)。NSBundle类来处理bundle

你的程序自己就是一个bundle。使用下面的代码获得main bundle:
NSBundle *bundle = [NSBundle mainBundle];

若是你须要其余目录的资源,能够指定路径来取得bundle:
NSBundle *goodBundle = [NSBundle bundleWithPath:@"~/.myApp/Good.bundle"];
一旦有了NSBundle对象,那么就能够访问它里面的资源了:
NSString *path = [goodBundle pathForImageResource:@"Mom"];//后缀名是可选的
NSImage *momPhoto = [[NSImage alloc] initWithContentsOfFile:path];

bundle能够包含一个库,若是从bundle中获取一个类,bundle会链接库,并经过名字查找这个类:
Class newClass = [goodBundle classNamed:@"Rover"];
id newInstance = [[newClass alloc] init];
若是不知道类的名字,也能够查找主类:
Class aClass = [goodBundle principalClass];
id anInstance = [[aClass alloc] init];

不经过NSWindowController,能够直接使用NSBundle加载nib文件:
BOOL successful = [NSBundle loadNibNamed:@"About" owner:someObject];//owner:指定一个对象做为nib的File's Owner

 

第十三章:User Default

1.经过NSUserDefaults类来注册程序的出厂设置,保存用户偏好设置,以及读取以前保存的用户偏好设置
通常在用户Home目录 ~/Library/Preferences 中能够找到数据库文件。通常为property list 格式

2.NSDictionary和NSMutableDictionary
id anObject = [dictionary objectForKey:@"foo"];//若是字典中没有对应的键,返回nil
字典使用哈希表来实现,查找速度很快。
NSDictionary经常使用方法:
- (NSArray *)allKeys;
- (unsigned)count;
- (id)objectForKey:(NSString *)aKey;
- (NSEnumerator *)keyEnumerator;//能够用这个方法来迭代出集合中的全部成员。这个方法是从一个字典中获得全部键的迭代器

NSEnumerator *e = [myDict keyEnumerator];
for (NSString *s in e){
    NSLog(@"key is %@, value is %@", s, [myDict objectForKey:s]);
}

NSMutableDictionary
+ (id)dictionary;//建立一个空得字典
- (void)removeObjectForKey:(NSString *)aKey;
- (void)setObject:(id)anObject forKey:(NSString *)aKey;//使用aKey和anObject组成一条记录,添加到字典中。在添加以前,将会给anobject发送retain消息。若是aKey已经存在于字典中,那么会移除原来对应的值对象,使用新的anobject代替,同时给原来的值对象发送release消息

3.NSUserDefaults
程序每次启动时,首先要加载出厂defaults,这个过程叫:registering defaults。注册完成后,将使用用户defaults配置用户所需,这个过程叫:reading and using the defaults。用户defaults数据库中得数据将会自动从文件系统中读取。
你也有可能建立一个首选项面板来让用户设置defaults,对defaults对象的改变会自动写入文件系统中。这个过程叫:setting the defaults

NSUserDefaults类的经常使用方法:
+ (NSUserDefaults *)standardUserDefaults;//返回共享的defaults对象
-  (void)registerDefaults:(NSDictionary *)dictionary;//注册程序的defaults
//修改和保存defaults
-  (void)setBool:(BOOL)value forKey:(NSString*)defaultName;
-  (void)setFloat/Integer/Object.....
//读取defaults
- (BOOL)boolForKey:(NSString*)defaultName;
- (float)floatForKey:(NSString*)defaultName;
- (int/id)integerForKey/objectForKey.....
//删除用户偏好设置,恢复出厂设置
- (void)removeObjectForKey:(NSString *)defaultName;

4.不一样类型的defaults的优先级
这些优先级称之为domains,下面列举了可使用的domains,优先级从高到低:
Arguments:经过命令行传递。大部分人都是经过双击程序图标来运行程序,而不是使用命令行,因此不多会用
Application:来自于用户defaults数据库
Global:用户对于整个系统的设定
Language:基于用户所选语言
Registered defaults:程序出厂defaults

5.设置程序的标识符
~/Library/Preferences中为程序建立的property list文件叫什么名字?默认名字为程序的标识符。如程序的标识符为:com.bignerdranch.RaiseMain,文件名则为:com.bignerdranch.RaiseMain.plist

6.命名Defaults中的键
为了保证使用同样的键名,可使用C的预编译命令#define,不过Cocoa程序员通常都选择全局变量来实现:
在.h文件的#import语句后添加:
extern NSString * const BNRTableBgColorKey;
extern NSString * const BNREmptyDocKey;
在.m文件中定义这些变量,将定义设置放在#import以后,@implementation以前
NSString * const BNRTableBgColorKey = @"TableBackgroundColor";
NSString * const BNREmptyDocKey = @"EmptyDocumentFlag";//BNR是全局变量前缀,为了和其余公司或组织的全局变量区分开来

7.注册Defaults
在接受其余消息前,每一个类首先都会接受initialize消息。重载AppController.m的initialize方法,来确保先注册你的defaults:

+ (void)initialize
{
    //建立一个字典
    NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];
    //封包颜色对象
    NSData *colorAsData = [NSKeyedArchiver archivedDataWithRootObject:[NSColor yellowColor]];
    //将defaults放置在字典中
    [defaultValues setObject:colorsAsData forKey:BNRTableBgColorKey];
    [defaultValues setObject:[NSNumber numberWithBool:YES]];

    //注册defaults字典
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];
    NSLog(@"registered defaults:%@", defaultValues);
}

读取defaults:

- (NSColor *)tableBgColor
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *colorAsData = [defaults objectForKey:BNRTableBgColorKey];
    return [NSKeyedUnarchiver unarchiveObjectWithData:colorAsData];
}
- (BOOL)emptyDoc
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return [defaults boolForKey:BNREmptyDocKey];
}

8.NSUserDefaultsController
有时但愿可以绑定NSUserDefaults对象的值,NSUserDefaultsController类能够作这样的时期。程序中全部nib共用一个共享的NSUserDefaultsController

9.使用Command line来读写Defaults
用户defaults存放在~/Library/Preferences/中,你可使用defaults命令行工具来编辑它们。例如查看XCode的defaults,在Terminal中:
cd Library/Preferences
defaults read com.apple.dt.Xcode

一样,你也能够修改defaults。输入下面的命令,修改NSOpenPanel打开的XCode默认的目录为/Users
defaults write com.apple.Xcode NSNavLastRootDirectoryForOpen /Users

再试试这个:
defaults read com.bignerdranch.RaiseMan//com.bignerdranch.RaiseMan是用户建的应用程序的标识符

查看全局的defaults:
defaults read NSGlobalDomain

 

第十四章:使用Notifications

1.每个运行的程序都有一个NSNotificationCenter的成员变量,它的功能相似公告栏
对象注册关注某个肯定的notifications(若是有人捡到一只小狗,就告诉我)。咱们把这些注册对象叫作observer。其余的一些对象会给center发送notifications(我捡到了一只小狗)。center将该notifications转发给全部注册对该对象感兴趣的对象。把这些发送notifications的对象叫作poster

注意:notification center容许同一个程序中的不一样对象进行通信,但它不能跨越不一样的程序。不一样于IPC(进程间通讯)

2.Notification 和 NotificationCenter
Notification对象很是简单,一个notification就像是poster要提供给observer的信息包裹同样。notification对象有两个重要的成员变量:name和object。
通常object都是指向poster的指针(为了让observer在接受到notification时能够回调poster),因此notification有两个方法:
- (NSString *)name;
- (id)object;

NotificationCenter是这个架构的大脑,容许你注册observer对象,发送notification,撤销observer对象。经常使用方法:
+ (NotificationCenter *)defaultCenter;//返回notification center
- (void)addObserver:(id)anObserver selector:(SELL)aSelector name:(NSString*)notificationName object:(id)anObject;//注意:notification center没有retain这个observer
//注册anObserver对象,接收名字为notificationName,发送者为anObject 的notification。
//当anObject发送名字为notificationName的notification时,将会调用anObserver的aSelector方法,参数为该notification
//a.若是notificationName为nil,那么notification center将anObject发送的全部notification都转发给anObject
//b.若是anObject为nil,那么notification center将全部名字为notificationName的notification转发给observer

- (void)postNotification:(NSNotification *)notification;//发送notification至notification center
- (void)postNotificationName:(NSString*)aName object:(id)anObject;//建立并发送一个notification
- (void)removeObserver:(id)observer;//从observer链中移除observer

3.UserInfo字典:若是但愿notification对象传递更多地信息,可使用user info字典。notification对象有一个变量叫userInfo,是一个NSDictionary对象,用来存放用户但愿随着notification一块儿传递到observer的其余信息
- (void)postNotificationName:(NSString*)aName object:(id)anObject userInfo:(NSDictionary *)dic;

4.Delegates和Notifications

 

第十五章:使用Alert Panels
1.Alert Panel是经过一个C函数NSRunAlertPanel()来实现的:
int NSRunAlertPanel(NSString *title, NSString *msg, NSString *defaultButton, NSString *alternateButton, NSString *otherButton,...);

 

第十六章:本地化

1.nib文件的本地化
在XCode中,选中nob文件,打开它的Info Panel,点击Add Localization按钮

2.字符串表
能够为每个语言版本建立多个字符串表。一个字符串表就是一个后缀名为.strings的文件。例:若是有个查找面板,能够建立不一样语言版本的find.strings文件来本地化一个查找对话框
字符串就是键值对的集合。键和值都用双引号包括,一组键值对以分号结束,如:"Key1" = "Value1"; "Key2" = "Value2";
经过NSBundle查找获得一个键所对应的值:
NSBundle *main = [NSBundle mainBundle];
NSString *aString = [main localizedStringForKey:@"Key1" value:@"DefaultValue1" table:"Find"];
上面的代码会在Find.strings文件中查找“Key1”所对应的值。若是程序没有提供与用户设定的语言相对应的本地化资源,那么系统就会使用用户设定的所选语言对应的本地化资源。若是最后都没找到,那么将返回“DefaultValue1”。若是不提供字符串表的名字(这里是Find),系统将使用Localizebale.strings字符串表。大部分程序只为每一个语言版本提供一个字符串表文件-Localizable.strings

3.建立字符串表
建立一个空文件并命名为Localizable.strings,编辑该文件,添加以下文本:
"DELETE" = "Delete";
"SURE_DELETE" = "Do you really want to delete %d people?";
"CANCEL" =  "Cancel";
建立该字符串表文件的中文版本。选中Localizable.strings文件,打开它的Info面板,建立中文本地化版本:
"DELETE" = "删除";
"SURE_DELETE" = "你真的要删除 %d 吗?";
"CANCEL" =  "取消";

4.使用字符串表。当只有一个字符串表时,可使用以下代码:
NSString *deleteString;
deleteString = [[NSBundle mainBundle] localizedStringForKey:@"DELETE" value:@"Delete?" table:nil];

更方便的,在h文件中定义宏:
#define NSLocalizedString(key, comment) [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

使用:
NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"DELETE", @"Delete") defaultButton:NSLocalizedString("DELETE", @"Delete") .........

5.ibtool——自动化工具,帮你将翻译字符串贴到nib文件中

在终端运行ibtool命令,它能够列举一个nib文件中的类或对象,也能够把其中的本地化字符串抽取出来保存到一个plist文件中。下面的例子是将English.lproj/MyDocument.nib文件中的本地化字符串抽取到文件Doc.strings文件中:
> cd RaiseMan/English.lproj
> ibtool --generate-stringsfile Doc.strings MyDocument.nib

Doc.strings文件以下:
/* Class="NSTableColumn";headerCell.title="Name";ObjectID="100026";*/
"100026.headerCell.title"="Name";
你能够建立Spanish版本的nib文件。先生成Spanish版本的Doc.strings,以下:
/*Class="NSTableColumn";headerCell.title="Name";ObjectID="100026";*/
"100026.headerCell.title"="Nombre";

使用字符串生成Spanish版本的nib:
> mkdir ../Spanish.lproj
> ibtool --strings-file Doc.strings
    --write ../Spanish.lproj/MyDocument.nib MyDocument.nib

能够输入以下man ibtool命令来得到ibtool的帮助:
> man ibtool

5.格式化字符串中符号的顺序

将文本从一种语言翻译成另一种语言时,随着文字的改变,文字的顺序也会改变。例如,在一种语言中,文本多是这样:“Ted wants a scooter”,而在另外一种语言顺序多是:“A scooter is what Ted wants”,假如你使用下面的格式来本地化这个字符串:
NSString * theFormat = NSLocalizedString(@"WANTS", @"%@ wants a %@");
x = [NSString stringWithFormat:theFormat, @"Ted", @"Scooter"];

对于第一种语言,下面能够正常工做:
"WANTS" = "%@ wants a %@";

对于第二种语言,就必须调整它们的顺序。可使用一个数字和一个美圆符号:
"WANTS" = "A %2$@ is what %1$@ wants";

 

第十七章:自定义视图

1.程序中全部的可视对象要么是窗口(NSWindwo),要么是视图(NSView)。窗口是NSWindow的实例,注意窗口不是NSView的子类

2.View的层次
View是按必定层次关系组织的(以下图)。窗口包含了一个叫 content view 的view。该view填满了整个窗口内部区域。一般content view能够包含本身的子view。这些子view也能够有本身的子view。一个view知道本身的父view和子view,也知道本身所属的窗口。
NSView相关方法:
- (NSView *)superView;
- (NSView *)subviews;
- (NSWindow *)window;

下面五种类型的view一般包含子view:
a.窗口的content视图
b.NSBox:box中的内容就是它的子view
c.NSScrollView:scroll view中显示view就是它的子view。scroll bar也是它的子view
d.NSSplitView:split view中的view就是它的子view
e.NSTabView:当用户点选不一样的tab时,交替切换不一样的子view

3.让View绘制本身
drawRect:方法——当一个view要刷新本身时,view将会收到此消息。整个方法是自动调用的。若是要一个view重画,能够调用:
[view setNeedsDisplay:YES];//该方法将myView设置成”脏“的。在当前事件处理结束后,这个view将被重画
此方法将触发view整个可见区域的重画。若是要触发view某个指定区域进行重画,能够用setNeedsDisplayInRect:代替

在调用drawRect:以前,系统会对这个view进行locks focus。每个view都有本身的graphic context—包含了view的坐标系统、当前颜色、当前字体以及剪裁区域等。当view被locks focus后,它的graphic context将被激活,而当unlock focus后,它的graphic context将再也不是激活状态。任什么时候候绘制命令都是在当前激活的graphic context上进行的

可使用NSBezierPath来绘制线条、圆形、曲线和矩形,可使用NSImage来在view上绘制合成图像:

//将整个view绘制成一个绿色的矩形
- (void)drawRect:(NSRect)rect
{
    NSRect bounds = [self bounds];
    [[NSColor greenColor] set];
    [NSBezierPath fillRect:bounds];
}

由于性能缘由,Object-c不多用到结构。可能用到一些Cocoa结构:NSSize、NSpoint、NSRect、NSRange(描述区间)、NSDecimal(描述数字精度)和NSAffineTransformStruct(描述图形线性变换)等

4.使用NSBezierPath绘制
绘制随机点间的线条

#import <Cocoa/Cocoa.h>
@interface StretchView : NSView
{
    NSBezierPath *path;
}
- (NSPoint)randomPoint;
@end
-----------------------------------------
#import "StretchView.h"
@implementation StretchView
//此方法会在view对象建立时自动调用
-(id)initWithFrame:(NSRect)rect
{
    if(![super initWithFrame:rect])
        return nil;
    //产生一个随机数生成器,设定随机数种子
    srandom(time(NULL));
    //建立一个path对象
    path = [[NSBezierPath alloc] init];
    [path setLineWidth:3.0];
    NSPoint p = [self randomPoint];
    [path moveToPoint:p];
    for(int i=0; i<15; i++){
        p = [self randomPoint];
        [path lineToPoint:p];
    }
    [path closePath];
    return self;
}
//randomPoint returns a random point inside the view
- (NSPoint)randomPoint
{
    NSPoint result;
    NSRect r = [self bounds];
    result.x = r.origin.x + random() % (int)r.size.width;
    result.y = r.origin.y + random() % (int)r.size.height;
    return result;
}

- (void)drawRect:(NSRect)rect
{
    NSRect bounds = [self bounds];
    //使用绿色填充视图
    [[NScolor greenColor] set];
    [NSBezierPath fillRect:bounds];
    //使用白色绘制path
    [[NSColor whiteColor] set];
    [path stroke];
}
@end

5.NSScrollView
scroll view由3个部分组成:document视图、content视图和scroll bar

6.单元格
NSControl从NSView继承而来。由于view有本身的graphics context,这让view成为一个庞大、高价的对象。
为了提升效率,将NSButton的大脑移到另一个类(再也不是view类),并建立一个大的view(叫NSMatrix),这个大脑就叫作NSButtonCell。

到最后,NSButton就是一个view再加上它的大脑NSButtonCell。button单元格作了全部的事情,而NSButton只是在窗口上申请了一块绘制区域

一样地,NSSlider就是包含了NSSliderCell的view,NSTextField就是一个包含了NSTextFieldCell的view。NSColorWell的差异是它没有单元格

7.isFlippedPDF和PostScript用得是标准的笛卡尔坐标系统,Quartz使用了一样地模型,坐标原点为view的左下点对于有些类型的绘制而言,若是原点在左上方,向下移动页面时y增长,数学计算会更容易。这时,咱们称该view为flipped的重载方法isFlipped,返回YES来翻转一个view:-(BOOL)isFlipped{    return YES;}

相关文章
相关标签/搜索