蘋果開發之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;}

相關文章
相關標籤/搜索