码迷,mamicode.com
首页 > 其他好文 > 详细

Objective-c 05 类别 类扩展 委托 非正式协议 协议 代码块 并发性 队列

时间:2015-11-30 17:29:49      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:

类别 
为已经存在的类添加行为时,通常采用创建子类的方法,不过有时子类并不方便, 比如:创建NSString的子类,但是NSString实际上只是一个类簇的表面形式。因而为这样的类创建子类会非常困难。在其他情况下, 也许可以创建它的子类,但是用到的工具集和库无法帮你处理新类的对象的。例如:当使用stringWithFormat:类方法生成新字符串时,你创建的 NSString类的新子类就无法返回。 
 
 
利用Objective-C的动态运行时分配机制,可以为现有的类添加新的方法。这些新的方法在Objective-C里被称为类别(category). 
 
 
类别是一种为现有的类添加新方法的方式。 
 
 
程序员总是习惯把类别代码放在独立的文件中,通常会以“类名称+类别名称” 的风格命名。 
 
 
开始创建类别: 
打开项目,选择想让文件出现的群组,接下来可以选择File->New->New File,在新窗口的左侧选择Cocoa并在右侧选择Objective-C category。然后按照你要创建的类别进行填写并创建。 
 
 
@interface部分 
类别的声明看起来非常像类的声明: 
@interface NSString (NumberConvenience) 
-(NSNumber *) lengthAsNumber; 
@end // NumberConvenience 
 
 
该声明具有两个非常有趣的特点: 
1、类的名称后面是位于括号中的新名称,这意味着类别叫做NumberConvenience,而且它是添加给NSString类的。只要保证类别名称唯一,可以向一个类中添加任意数量的类别。 
2、其次,可以指定想要添加类别的类(如NSString)和类别的名称(如NumberConvenience),还可以列出要添加的方法,最后以@end结束。由于不能添加新的实例变量,因此这里不会像类声明那样包含实例变量的声明部分。 
 
 
可以在类别中添加属性,但是不能添加实例变量,而且属性必须是@dynamic类别的。添加属性的好处在于你可以通过点表达式来访问setter和getter方法。 
 
 
@implementation部分 
 
 
@implementation NSString (NumberConvenience) 
-(NSNumber *) lengthAsNumber 

     NSUInteger length = [self length]; 
     return ([NSNumber numberWithUnsignedInteger: length]); 
} // lengthAsNumber 
@end // NumberConvenience 
与类别的@interface部分相似,@implementation部分也包含类名、类别名以及新方法的实现。 
 
 
[self length]来获取字符串的长度值。向该字符串发送lengthAsNumber消息,就会创建出一个包含了长度值的新NSNumber对象。 
 
 
对于这段代码的内存管理,由于numberWithUnsignedInt不属于alloc、copy、new方法,因此它返回的对象可能是保留计数器的值为1并且已经被设置为自动释放了。在当前活动的自动释放池被销毁时,我们创建的这个NSNumber对象也会被清理。 
 
 
main代码: 
int main(int argc, const char* argv[]) 

     @autoreleasepool 
     { 
          NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
 
          [dict setObject: [@"hello" lengthAsNumber] forKey: @"hello"]; 
          [dict setObject: [@"iLikeFish" lengthAsNumber] forKey: @"iLikeFish"]; 
          [dict setObject: [@"Once upon a time" lengthAsNumber] forKey: @"Once upon a time"]; 
          NSLog(@"%@", dict); 
     } 
     return 0; 

 
 
#import "NSString+NumberConience.h" 
向main()函数导入头文件,这样才能知道我们拥有这个为NSString类而定义的方法。 
@autoreleasepool 
     { 
--创建一个自动释放池,这是启用了ARC的方法。最后末尾要加上花括号。 
 
 
[dict setObject: [@"hello" lengthAsNumber] forKey: @"hello"]; 
请记住@"字符串"这种形式的字符串实际上就是地地道道的NSString对象。因为我们已经为NSString类创建了这个类别,所以任何字符串(包括像这样的字面量类型)都能响应lengthAsNumber消息。 
 
 
技术分享 
 
 
类别的缺陷 
 
 
类别有两个局限性: 
1、无法向类中添加新的实例变量。类别没有空间容纳实例变量。 
2、就是名称冲突。也即是类别中的方法与现有的方法重名。当发生名称冲突时,类别具有更高的优先级。你的类别方法将完全取代初始化方法,导致初始化方法不再可用。可以在类别方法中添加一个前缀,以确保不会发生名称冲突。 
技术分享 
 
 
 
 
类别的优势: 
在Cocoa中,类别主要有三个用途:  
1、将类的实现代码分散到多个不同文件或框架中。 
2、创建对私有方法的前向引用。 
3、以及向对象添加非正式协议(informal protocol). 
 
 
一个特殊的类别,它有一个特殊的名称:类扩展(class extension) 
这个类别的特点之一就是不需要名字。之前都要为类别命名并且会在定义@interface部分的时候使用这个名字,而这个特殊的类扩展类别不需要命名,其特点如下: 
1、不需要名字 
2、可以在包含你的源代码的类(也就是自己的类)中使用它。 
3、可以添加实例变量 
4、将只读权限改成可读写的权限。 
5、创建数量不限。 
 
 
Things的类: 
@interface Things : NSObject 
@property (assign) NSInteger thing1; 
@property (readonly, assign) NSInteger thing2; 
 
 
-(void) resetAllValues; 
@end 
 
 
类扩展后的代码: 
@interface Things () 

     NSInteger thing4; 

@property (readwrite, assign) NSInteger thing2; 
@property (assign) NSInteger thing3; 
 
 
@end 
 
 
这看起来就像定义一个类,只不过这里没有继承的父类。我们所做的基本上就是获取Things类,并通过添加私有属性和方法来扩展它。这就是它被称为类扩展的原因。 
 
 
对 于thing2属性,我们在类扩展中改变了它的读写权限。标记为readwrite,这样编译器就会生成setter方法了,不过它是只能在这个类中访问 的私有方法,在公共的接口则只有getter方法。同样也添加了私有属性thing3,只可以在这个类内部使用。thing4也是私有的。 
 
 
这段代码的内存管理:因为使用了ARC,所以不需要释放对象。没有内存泄露问题。 
技术分享 
可以拥有多个类扩展类别,不过这样会引发很难察觉的bug,所以理智使用。 
 
 
使用类别分散实现代码: 
如果想将大型的单个类分散到多个不同的.m文件中,可以使用类别。 
 
 
在项目中使用类别: 
CategoryThing.h 
#import <Foundaton/Foundation.h> 
@interface CategoryThing : NSObject 

     NSInteger thing1; 
     NSInteger thing2; 
     NSInteger thing3; 

@end // CategoryThing 
 
 
类声明后是3个类别声明,将这些实现代码放入不同的文件中。 
 
 
@interface CategoryThing (Thing1) 
-(void)setThing1:(NSInteger)thing1; 
-(NSInteger)thing1; 
@end // CategoryThing (Thing1) 
 
 
@interface CategoryThing (Thing2) 
-(void)setThing2:(NSInteger)thing2; 
-(NSInteger)thing2; 
@end // CategoryThing (Thing2) 
 
 
@interface CategoryThing (Thing3) 
-(void)setThing3:(NSInteger)thing3; 
-(NSInteger)thing3; 
@end // CategoryThing (Thing3) 
 
 
以上就是CategoryThing.h的代码。 
 
 
CategoryThing.m: 
#import "CategoryThing.h" 
@implementation CategoryThing 
-(NSString *) description 

     NSString *desc; 
     desc = [NSString stringWithFormat: @"%d %d %d", thing1, thing2, thing3]; 
     return (desc); 
} // description 
@end // CategoryThing 
 
 
看下内存管理: 
description方法能够处理好内存管理释放,因为stringWithFormat不同alloc、copy和new,所以可以假设它返回的对象的保留计数器的值为1且已经被设置为自动释放。在当前的自动释放池被销毁时,该对象将被清理。 
 
 
各个类别的实现代码: 
Thing1.m: 
#import "CategoryThing.h" 
 
 
@implementation CategoryThing (Thing1) 
-(void)setThing1: (NSInteger) t1 

     thing1 = t1; 
} // setThing1 
 
 
-(NSInteger) thing1 

     return (thing1); 
} // thing1 
 
 
@end // CategoryThing 
 
 
类别可以访问其继承的类的实例变量。类别的方法具有更高的优先级。 
 
 
Thing2.m的内容与Thing1.m非常相似: 
#import "CategoryThing.h" 
 
 
@implementation CategoryThing (Thing2) 
-(void)setThing2: (NSInteger) t2 

     thing2 = t2; 
} // setThing2 
 
 
-(NSInteger) thing2 

     return (thing2); 
} // thing2 
 
 
@end // CategoryThing 
 
 
main.m文件的main()函数: 
首先导入: 
#import "CategoryThing.h" 
 
 
int main (int argc, const char* argv[]) 

     @autoreleasepool { 
          CategoryThing *thing = [[CategoryThing alloc] init]; 
          [thing setThing1: 5]; 
          [thing setThing2: 23]; 
          [thing setThing3: 42]; 
          NSLog(@"Things are %@ ", thing); 
     } 
     return (0); 
} // main 
 
 
 @autoreleasepool {--该自动释放池将用于存放NSLog()函数调用的description方法所返回的会自动释放的字符串。 
 CategoryThing *thing = [[CategoryThing alloc] init]; 
在创建新对象时对内存管理进行分析:因为这里使用的是alloc方法,该thing对象的保留计数器的值为1,它未被放入自动释放池中.因为项目使用了ARC特性,所以不需要担心在哪里添加release语句.编译器会在正确的位置帮我们添加好. 
 
 
类别不仅可以将一个类的实现代码分散到多个不同的源文件中,还可以分散到多个不同的框架中. 
通过类别创建前向引用 
编译器发现你调用对象的某个方法,但是却没有找到该方法的声明或定义,那么它将发出错误的报告.通常这种错误提示有益于你编程,但是如果你的某些方法的实现使用了在类的@interface部分未列出的方法,编译器的这种警惕性会带来一些问题. 
 
 
这个问题可以通过类别来解决,只要在类别中声明一个方法,编译器就会表示:"该方法已经存在,如果遇到编程人员使用该方法,我不会提出警告".实际上,如果你不想实现这个方法的话,放着不管就可以了. 
 
 
常用的策略是将类别置于实现文件的最前面. 
 
 
假 设Car类有一个名为rotateTires的方法.我们可以使用另一个名为moveTireFromPosition:toPosition:的方法来 实现rotateTires方法。第二个方法是实现细节,而不是将其放在Car类的公共接口中。通过在类别中声明 moveTireFromPosition:toPosition:方法,rotateTires方法可以使用它,但不会引发编译器产生警告: 
该类别如下: 
 
 
@interface Car (Private) 
-(void) moveTireFromPosition: (int) pos1 toPosition: (int) pos2; 
@end // Private 
 
 
当 你实现moveTireFromPosition:toPosition:方法时,它不一定要出现在@implementation Car(PrivateMethods)代码块中,不过如果没有的话,编译器会给你一个警告,所以良好的习惯是把它们放入各自的 @implementation中。 
 
 
非正式协议和委托类别 
 
 
Cocoa中的类经常会使用一种名为委托(delegate)的技术,委托是一种对象,由另一个类请求执行某些工作。 
 
 
最常见的情况是:编写委托对象并且将其提供给其他一些对象,通常是Cocoa框架中的对象。通过实现特定的方法,你可以控制Cocoa中对象的行为。 
 
 
iTunesFinder项目: 
#import <Foundation/Foundation.h> 
#import "ITunesFinder.h" 
 
int main(int argc, const char * argv[]) 

    @autoreleasepool 
    { 
        NSNetServiceBrowser *browser = [[NSNetServiceBrowser alloc] init]; 
        
        ITunesFinder *finder = [[ITunesFinder alloc] init]; 
        
        [browser setDelegate:finder]; 
         //告诉网络浏览器使用ITunesFinder类的对象作为委托对象。 
        [browser searchForServicesOfType:@"_daap._tcp" 
                                inDomain:@"local."]; 
        
        NSLog (@"begun browsing"); 
        
        [[NSRunLoop currentRunLoop] run]; 
    } 
    return 0; 

-- 
字 符串“_daap._tcp”告诉了网络服务浏览器使用TCP网络协议去搜索DAAP(Digital Audio Access Protocol,数字音频访问协议)类型的服务。该语句可以找到由iTunes发布的库。域“local.”表示只在本地网络中搜索该服务。互联网编号 分配机构(Internet Assigned Numbers Authority, IANA)维护一个互联网协议族列表,该列表通常被映射为Bonjour服务名称。 
 
 
[[NSRunLoop currentRunLoop] run];-- run循环是一种Cocoa概念,它在等待某些事情发生之前一直处于阻塞状态,既不执行任何代码。 
在本示例中,它等待的事情是指网络服务浏览器发现了新的iTunes共享。 
 
 
除了监听网络流量以外,run循环还处理像等待用户事件(如按键或鼠标单击)之类的其他操作。事实上,run方法将一直保持运行而不会返回,因此其后的代码永远也不会被执行。不过我们还是留下了这段代码,以便浏览代码的人能够知道我们一直在进行适当的内存管理。 
因此,下面的清理代码实际上不会执行。 

    return 0; 
ITunesFinder委托的代码: 
#import <Foundation/Foundation.h> 
@interface ITunesFinder : NSObject <NSNetServiceBroserDelegate> 
@end // ITunesFinder 
 
 
请记住,并不一定要在@interface中声明方法。委托对象只需实现已经打算调用的方法。 
需要注意在NSObject后面使用了<NSNetServiceBroserDelegate>协议。它告诉编译器和其他对象,ITunesFinder类符合这个名称的协议并且实现了其方法。 
ITunesFinder.m: 
 
 
#import "ITunesFinder.h" 
 
@implementation ITunesFinder 
 
// 第一个委托方法: 
- (void) netServiceBrowser:(NSNetServiceBrowser *) b 
            didFindService:(NSNetService *) service 
                moreComing:(BOOL) moreComing 

    [service resolveWithTimeout:10]; 
 
    NSLog (@"found one! Name is %@", [service name]); 
 
} // didFindService 
当NSNetServiceBrowser对象发现一个新服务时,它将向委托对象发送 netServiceBrowser:didFindService:moreComing:消息。浏览器被作为第一个参数(与main函数中 browser变量的值相同)传递。如果有多个服务浏览器在同时进行搜索,你可以利用参数来分析哪个浏览器发现了新的服务。 
第二个参数传递的NSNetService类的对象,描述了被发现的服务。最后一个参数moreComing用于标记一批通知是否已经完成。 
 [service resolveWithTimeout:10]; 
--告诉Bonjour系统获取关于该服务的所有有趣的属性。[service name]为我们获取该共享的名称。 
 
 
// ITunesFinder类实现了第二个委托方法,该方法在某个网络服务消失时被调用。 
 
 
- (void) netServiceBrowser:(NSNetServiceBrowser *) b 
          didRemoveService:(NSNetService *) service 
                moreComing:(BOOL) moreComing 

    [service resolveWithTimeout:10]; 
 
    NSLog (@"lost one! Name is %@", [service name]); 
 
} // didRemoveService 
 
@end // ITunesFinder 
该方法与didFindService方法非常相似,只不过它是在某个服务不再可用时被调用。 
 
 
委托和类别: 
委托对象与类别的关系: 
委托强调类别的另一种应用:被发送给委托对象的方法可以声明为一个NSObject的类别。NSNetService委托方法的部分声明如下: 
@interface NSObject (NSNetServiceBrowserDelegateMethods) 
-(void) netServiceBrowserWillSearch: (NSNetServiceBroser *) browser; 
-(void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didFindService:(NSNetService *) service moreComing: (BOOL) moreComing; 
-(void) netServiceBrowserDidStopSearch: (NSNetServiceBrowser *) browser; 
-(void) netServiceBrowser: (NSNetServiceBrower *) browser didRemoveService: (NSNetService *) service moreComing: (BOOL) moreComing; 
@end 
通过将这些方法声明为NSObject的类别,NSNetServiceBrowser的实现可以将这些消息之一发送给任何对象,无论这些对象实际上属于哪个类。这也意味着,只要对象实现了委托方法,任何类的对象都可以成为委托对象。 
 
 
技术分享 
 
 
创建一个NSObject的类别称为“创建一个非正式协议”。  
计算机术语中的“协议”是一组管理通信的规则。 
非正式协议只是一种表达方式。它表示“这里有一些你可能希望实现的方法,你可以使用它们更好地完成工作”。 
 
 
当试图发送一个对象无法理解的消息时,NSNetServiceBrowser首先检查对象,询问其能否响应该选择器。如果该对象能够响应该选择器,NSNetServiceBrowser则给它发送消息。 
 
 
选择器(selector):只是一个方法名称,但它以Objective-C运行时使用的特殊方式编码,以快速执行查询。 
 
 
可以使用@selector()编译指令圆括号中的方法名称来指定选择器。 
如:Car类的setEngine:方法的选择器是: 
@selector(setEngine:) 
而Car类的setTire:atIndex:方法的选择器如下: 
@selector(setTIre:atIndex:) 
 
 
NSObject提供了一个名为respondsToSelector:的方法,该方法询问对象以确定其是否能够响应某个特定的消息。 
下面的代码段使用了respondToSelector:方法: 
Car *car = [[Car alloc] init]; 
if ([car respondsToSelector: @selector(setEngine:)]) 

     NSLog(@"yowzal"); 

这段代码将输出“yowzal”,因为Car类的对象确实能够响应应该setEngine:消息。 
 
 
ITunesFinder *finder = [[ITunesFinder alloc] init]; 
if ([finder respondsToSelector: @selector(setEngine:)]) 

     NSLog(@"yowzal"); 

这次未能输出“yowzal”,因为ITunesFinder类的对象没有setEngine:方法。 
 
 
为 了确定委托对象能否响应消息,NSNetServiceBrowser将调用 respondsToSelector:@selector(netServiceBrowser:didFindService:moreComing:). 如果该委托对象能够响应给定的消息,则浏览器向该对象发送此消息。否则,浏览器将暂时忽略该委托对象,继续正常运行。 
 
 
选择器可以被传递,可以作为方法的参数使用,甚至可以作为实例变量被存储。 
技术分享 
 
 
协议 
正式协议: 
与非正式协议一样,正式协议是包含了方法和属性的有名称列表。但与非正式协议不同的是,正式协议要求显式地采用。 
 
 
采用(adopt)协议的办法是在类的@interface声明中列出协议的名称。采用协议后,你的类就要遵守该协议,也意味着你承诺实现该协议的所有方法,否则编译器会生成警告来提醒你。 
 
 
技术分享 
 
 
通常一个协议只有少数几个需要实现的方法,你必须实现所有这些方法才能获得一系列有用的功能。因此一般来说正式协议的要求并不是负担。 
 
 
声明协议: 
由Cocoa声明的一个协议:NSCopying。如果采用了NSCopying协议,你的对象将会知道如何创建自身的副本。 
 
 
@protocal NSCopying 
-(id) copyWithZone: (NSZone *) zone; 
@end 
 
 
使用@protocal告诉编译器:下面将是一个新的正式协议。@protocal后面是协议名称,协议名称必须要唯一。 
 
 
也可以继承父协议,这点跟继承父类相似。 
@protocol MySuperDuberProtocal <MyParentProtocol> 
@end 
 
 
第一行代码表示MySuperDuperProtocol协议继承于MyParentProtocol协议,因此必须实现两个协议中所有需要实现的方法。 
 
 
通常使用NSObject作为根协议。请不要将其与NSObject类混淆。NSObject类符合NSObject协议,这意味着所有对象都符合NSObject协议。将NSObject协议作为你编写的协议的父协议是一个不错的方式。 
 
 
接下来是一个方法声明列表,所有采用了此协议的类都必须实现这些方法。 
 
 
Cocoa的NSCoding协议: 
 
 
@protocol NSCoding 
-(void) encodeWithCoder: (NSCoder *) encoder; 
-(id) initWithCoder: (NSCoder *) decoder; 
@end 
 
 
技术分享 
 
 
采用协议: 
要采用某个协议,可以在类的声明中列出该协议的名称,并用尖括号括起来。 
 
 
@interface Car : NSObject <NSCopying> 

     // instance variables 

// methods 
 
 
@end // Car 
 
 
如果Car类要同时采用NSCopying和NSCoding这两个协议,则其类声明将如下表示: 
 
 
@interface Car : NSObject <NSCopying, NSCoding> 

     // instance variables 

// methods  
@end // Car 
 
 
可以按任意顺序列出这些协议。 
采用了这些协议,就相当于给阅读该类声明的编程人员传递了一条信息,表明该类的对象可以完成两个非常重要的操作:一是能够对自身进行编码或解码, 二是能够创建自身的副本。 
 
 
采用NSCopying协议的项目: 
copy消息会告诉对象创建一个全新的对象,并让新对象与接收copy消息的原对象完全一样。 
 
 
技术分享 
 
 
复制Engine: 
 
 
@interface Engine:NSObject <NSCopying> 
@end // Engine 
 
 
因 为Engine类采用了NSCopying协议,所以我们必须实现copyWithZone:方法。zone是NSZone类的一个对象,指向一块可供分 配的内存区域。当向一个对象发送copy消息时,该copy消息在到达你的代码之前会被转换为copyWithZone:方法。 
 
 
Engine类的copyWithZone:方法的实现: 
implementation:  
-(id) copyWithZone: (NSZone *) zone 

     Engine *engineCopy; 
     engineCopy = [[[self class] allocWithZone: zone] init]; 
     return (engineCopy); 
} // copyWithZone 
 
 
copyWithZone: 方法的首要任务是获得self参数所属的类,然后向self对象所属的类发送allocWithZone:消息,以分配内存并创建一个该类 的新对象。最后,copyWithZone:方法给这个新对象发送allocWithZone:消息使其初始化。 
 
 
为什么要使用上面复杂的消息嵌套,尤其[self class]这个方法: 
+(id) allocWithZone: (NSZone *) zone; 
是一个类方法。我们需要将该消息发送给一个类,而不是一个实例变量。 
[Engine allocWithZone: zone] 
这 行代码适用于Engine类,但不适用与Engine类的子类。如果给Engine的子类Slant6类的对象发送copy消息,因为最后使用的是 Engine类的复制方法,所以这行代码最终会在Engine类的copyWithZone:方法中结束。而如果直接给Engine类发送 allocWithZone:消息,则将创建一个新的Engine类的对象,不是Slant6对象。假如给Slant6类增加了一些实例变量,情况会变得 更加扑朔迷离。这样做的话,Engine类的对象将无法容纳多余的变量,从而导致内存溢出错误。 
所以通过使用[self class], allocWithZone:消息将会被发送给正在接受copy消息的对象所属的类。如果self是一个Slant6的对象,则这里将创建一个Slant6类的新对象。如果将来添加一些新对象,新的对象也会被正确的复制。 
allocWithZone:方法的最后一行会返回新创建的对象。 
 
 
检查一下内存管理问题:copy方法应该会返回一个保留计数器的值为1的对象,且对象不会自动释放。但这个新对象是我们通过alloc方法获得的。因此这个方法的内存管理没有问题。 
 
 
复制Tire 
@interface Tire: NSObject <NSCopying> 
@property float pressure; 
@property float treadDepth; 
 
 
// -methods 
 
 
@end // Tire 
 
 
-(id) copyWithZone: (NSZone *) zone 

     Tire *tireCopy;  
     tireCopy = [[[self class] allocWithZone: zone] initWithPressure: pressure treadDepth: treadDepth]; 
     return (tireCopy); 
} // copyZone 
 
 
由 于在创建对象时必须调用init方法,所以我们可以方便地使用Tire类的initWithPressure:treadDepth:方法,将新tire 对象的pressure和treadDepth设置为我们正在复制的tire对象的值。该方法正好是Tire类的指定初始化函数,但并不是必须要使用指定 初始化函数来执行复制操作。只要你愿意,也可以使用简单的init方法和访问器方法来修改对象的属性。 
 
 
技术分享 
 
 
AllWeatherRadial类,接口内容不变: 
@interface AllWeatherRadial: Tire 
// ...properties 
// ... methods 
@end 
这里没有<NSCopying>,因为当AllWeatherRadial类继承于Tire类时,它便已经获得Tire类的所有属性,包括对NSCopying协议的遵守。 
 
 
重写copyWithZone:方法,确保子类的实例变量被复制。 
-(id) copyWithZone: (NSZone *) zone 

     AllWeatherRadial *tireCopy; 
     tireCopy = [super copyWithZone: zone]; 
     tireCopy.rainHandling = rainHandling; 
     tireCopy.snowHandling = snowHandling; 
     return (tireCopy); 
} // copyWithZone 
 
 
技术分享 
 
 
复制Car 
 
 
@interface Car : NSObject <NSCopying> 
// ... 
@end 
 
 
为遵守NSCopying协议,Car类必须实现原来的友元方法“copyWithZone:”: 
-(id) copyWithZone: (NSZone *) zone 

     Car *carCopy; 
     carCopy = [[[self class] allocWithZone: zone] init]; 
     carCopy.name = self.name; 
     Engine *engineCopy; 
     engineCopy = [[engine copy] autorelease]; 
     // 也可以通过release来释放,对于ios应用,苹果公司建议你直接使用release而不是autorelease自动释放,因为你无法知道什么时候自动释放池会释放保留计数器的值。 
 
 
     carCopy.engine = engineCopy; 
     for (int i = 0; i < 4; i++){ 
          Tire *tireCopy; 
          tireCopy = [[self tireAtIndex: i] copy]; 
          [tireCopy autorelease]; 
          [carCopy setTire: tireCopy atIndex: i]; 
     } 
     return (carCopy); 

main(): 
int main (int argc, const char* argv[]){ 
     @autoreleasepool 
     { 
          Car *car = [[Car alloc] init]; 
          car.name = @"Herbie"; 
          for (int i = 0; i < 4; i++){ 
               AllWeatherRadial *tire; 
               tire = [[AllWeatherRadial alloc] init]; 
               [car setTire: tire atIndex: i]; 
               [tire release]; 
          } 
          Slant6 *engine = [[Slant6 alloc] init]; 
          car.engine = engine; [engine release]; 
          [car print]; 
          Car *carCopy = [car copy]; 
          [carCopy print]; 
          [car release]; 
          [carCopy release]; 
     } 
     return (0); 

 
 
协议和数据类型: 
可以在使用的数据类型中为实例变量和方法参数指定协议名称。可以给Objective-C的编译器提供更多的信息,从而有助于检查代码中的错误。 
 
 
id类型表示一个可以指向任何类型的对象的指针,它是一个泛型对象类型。如果一个用尖括号括起来的协议名称跟随在id之后,则编译器将知道你会接受任意类型的对象,但前提是要遵守该协议。 
 
 
技术分享 
 
 
Objective-C 2.0的新特性: 
增加了两个新的协议修饰符:@optional和@required。 
指定了@optional关键字代码: 
@protocol BaseballPlayer 
-(void) drawHugeSalary; 
@optional 
-(void)slideHome; 
-(void)catchBall; 
-(void)throwBall; 
@required 
-(void)swingBat; 
@end // BaseballPlayer 
 
 
这段代码采用了BaseballPlayer协议并且必须实现-drawHugeSalary和-swingBat方法,而另外几个方法可以选择是否去实现。 
看起来非正式协议就可以实现这样的效果,但是上面可以在类声明和方法中明确表达我们的意图。 
 
 
委托方法: 
 
 
委托(delegation),它是一个经常与协议公用的特性。委托就是某个对象指定另一个对象处理某些特定任务的设计模式。 
 
 
代码块和并发性 
 
 
 
 
代码块对象(通常简称为“代码块”)是对C语言中函数的扩展。除了函数中的代码,代码块还包含变量绑定。代码块有时也被称为闭包(closure)。 
 
 
代码块包含两种类型的绑定:自动型和托管型。 
自动绑定(automatic binding)使用的是栈中的内存。 
托管绑定(managed binding)是通过堆创建的。 
 
 
因为代码块实际上是由C语言实现的,所以它们在各种以C作为基础的语言内都是有效的, 包括Objective-C、C++以及Objective-C++. 
 
 
代码块和函数指针: 
与函数指针类似,代码块具有以下特征: 
1、返回类型可以手动声明也可以由编译器推导。 
2、具有指定类型的参数列表; 
3、拥有名称。 
 
 
先声明一个函数指针: 
void (*my_func) (void); 
这是非常基础的函数指针,它没有参数和返回结果,只要把*(星号)替换成^(幂符号),就可以把它转换成一个代码块的定义了。比如: 
void (^my_block)(void); 
 
 
在声明代码块变量和代码块实现的开头位置都要使用幂操作符。与在函数中一样,代码块的代码要放在{}(花括号)中。 
 
 
int (^square_block)(int number) = ^(int number) {return (number * number);}; 
int result = square_block(5); 
printf("Result = %d\n", result); 
 
 
这个特别的代码块获取了一个整型参数并返回了这个数字的评分。等号前面是代码块的定义,而等号后面是实现内容。一般可以用如下关系来表示他们: 
<returntype> (^blockname)(list of arguments) = ^(arguments){body;}; 
编译器可以通过代码块的内容推导出返回类型,所以可省略它。如果代码块没有参数,也可以省略。简洁的: 
void (^theBlock)() = ^{printf("Hello Blocks!\n");}; 
 
 
使用代码块: 
代码块声明成了变量,所以可以向函数一样使用它: 
int result = square_block(5); 
幂符号只有在定义代码块的时候才需要使用它,调用时则不需要. 
 
 
代码块还有一个特性:可用来替换原先的函数:代码块可以访问与它相同的(本地)有效范围内声明的变量,也就是说代码块可以访问与它同时创建的有效变量。 
 
 
int value = 6; 
int (^multiply_block)(int number) = ^(int number) {return (value * number);}; 
int result = multiply_block(7); 
printf("Result = %d\n", result); 
 
 
直接使用代码块: 
使用代码块时通常不需要创建一个代码块变量,而是在代码中内联代码块的内容。通常,会需要一个将代码块作为参数的方法或函数。 
 
 
NSArray *array = [NSArray arrayWithObjects: @"Amir", @"Mishal", @"Irrum", @"Adam", nil]; 
NSLog(@"Unsorted Array %@", array); 
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(NSString *object1, NSString *object2){ 
     return [object1 compare:object2]; 
}]; 
 
 
NSLog(@"Sorted Array %@", sortedArray); 
 
 
使用typedef关键字: 
typedef double (^MKSampleMultiply2BlockRef) (double c, double d); 
 
 
这行语句定义了一个名称为MKSampleMultiply2BlockRef的代码块变量。包含了两个双浮点型参数并返回一个双浮点型数值。 
有了typedef,可以像下面这样使用这个变量: 
MKSampleMultiply2BlockRef multiply2 = ^(double c, double d) {return c * d;}; 
printf("%f, %f", multiply2(4, 5), multiply2(5, 2)); 
还可以定义其他一些不同类型的代码块变量: 
typedef void (^MKSampleVoidBlockRef)(void); 
typedef void (^MKSampleStringBlockRef)(NSString *); 
typedef double (^MKSampleMultiplyBlockRef)(void); 
 
 
代码块和变量: 
代码块被声明后会捕捉创建点时的状态。代码块可以访问函数用到的标准类型的变量: 
 
 
1、全局变量,包括在封闭范围内声明的本地静态变量。 
2、全局函数(明显不是真实的变量) 
3、封闭范围内的参数 
4、函数级别(即与代码块声明时相同的级别)的__block变量。它们是可以修改的变量。 
5、封闭范围内的非静态变量会被获取为常量 
6、Objective-C的实例变量。 
7、 代码块内部的本地变量。 
 
 
本地变量:就是与代码块在同一范围内声明的变量。 
typedef double (^MKSampleMultiplyBlockRef)(void); 
double a = 10, b = 20; 
 
 
MKSmapleMultiplyBlockRef multiply = ^(void){ return a * b;}; 
NSLog(@"%f", multiply()); 
a = 20; 
b = 50; 
NSLog(@"%f", multiply()); 
 
 
上面的代码中第一个输出会输出200,第二个NSLog也会输出200,而不是1000.因为变量是本地的,代码块会在定义时复制并保存它们的状态。所以NSLog只会输出200; 
 
 
全局变量 
在上面的本地变量示例中,我们说过变量与代码块拥有相同的有效范围,可以根据需要把变量标记为静态的(全局的) 
 
 
static double a = 10, b = 20; 
 
 
MKSmapleMultiplyBlockRef multiply = ^(void){ return a * b;}; 
NSLog(@"%f", multiply()); // 200 
a = 20; 
b = 50; 
NSLog(@"%f", multiply()); // 1000 
 
 
参数变量 
代码块中的参数变量与函数中的参数变量具有同样的作用。 
 
 
typedef double (^MKSampleMultiply2BlockRef)(double c, double d); 
MKSampleMultiply2BlockRef multiply2 = ^(double c, double d) {return c * d;}; 
NSLog(@"%f, %f", multipley2(4, 5), multiply2(5, 2)); 
 
 
__block变量 
本地变量会被代码块作为常量获取到。如果想要修改它们的值,必须将它们声明为可修改的,否则就会像下面的错误例子一样编译时出错: 
double c = 3; 
MKSmapleMultiplyBlockRef multiply = ^(double a, double b) { c = a * b;}; 
编译器将会警告这个错误: 
Varibale is not assignable (missing _block type specifier) 
 
 
想要修复这个编译错误,将变量c标记为_block。如下: 
__block double c = 3; 
MKSampleMultiplyBlockRef multiply = ^(double a, double b) { c = a * b; }; 
 
 
有些变量是无法声明为__block类型的。有两个限制: 
1、没有长度可变的数组; 
2、没有包含可变长度数组的结构体。 
 
 
代码块内部的本地变量 
这些变量与函数中的本地变量具有相同的作用: 
 
 
void (^MKSampleBlockRef) (void) = ^(void) { 
     double a = 4; 
     double c = 2; 
     NSLog(@"%f", a * c); 
}; 
MKSampleBlockRef(); 
 
 
Objective-C 变量 
 
 
代码块是Objective-C语言中的优秀公民,可以像使用其他对象一样使用。使用时会遇到的最大问题就是内存管理。以下规则能帮助你处理内存管理: 
1、如果引用了一个Objective-C对象,必须要保留它。 
2、如果通过引用访问了一个实例变量,要保留一次self(即执行方法的对象)。 
3、如果通过数值访问了一个实例变量,变量需要保留。 
 
 
对于第一个规则: 
NSString *string1 = ^{ 
     return [_theString stringByAppendingString: _theString]; 
}; 
 
 
_theString是声明了代码块的类里面的实例变量。因为在代码块中直接访问了实例变量,所以包含它的对象(self)需要保留。 
 
 
下一个例子: 
NSString *localObject = _theString; 
NSString *string2 = ^{ 
     return [localObject stringByAppendingString: localObject]; 
}; 
 
 
这个例子中,我们是间接访问:创建了一个指向实例变量的本地引用并在代码块里面使用。因此这次要保留的是localObject,而不是self。 
 
 
因为代码块是对象,可以向它发送任何与内存管理有关的消息。在C语言级别中,必须使用Block_copy()和Block_release()函数来适当地管理内存。 
 
 
MKSmapleVoidBlockRef block1 = ^{NSLog(@"Block1");}; 
block1(); 
 
 
MKSampleVoidBlockRef block2 = ^{ 
     NSLog(@"Block2"); 
}; 
block2(); 
Block_release(block2); 
 
 
block2 = Block_copy(block1); 
block2(); 
 
 
并发性: 
能够在同一时间执行多项任务的程序称为并发的(concurrent)程序。 
 
 
利用并发性最基础的方法是使用POSIX线程来处理程序的不同部分使其能够独立执行。POSIX线程拥有支持C语言和Objective-C的API。 
 
 
苹果公司引入了Grand Central Dispatch,称之为GCD。这个技术减少了不少线程管理的麻烦。想要使用GCD,需要提交代码块或函数作为线程来运行。 
 
 
GCD是一个系统级别(system-level)的技术,因此你可以在任意级别的代码中使用它。 
GCD决定需要多少线程并安排它们运行的进度。因为它是运行在系统级别上的,所以可以平衡应用程序所有内容的加载,这样可以提高计算机或设备的执行效率。 
 
 
同步: 
技术分享 
 
 
Objective-C提供了一个语言级别的(language-level)关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。 
@synchronized(theObject) 

    // Critical section 

它可以确保不同的线程会连续地访问临界区的代码。 
 
 
若定义了一个属性并且没有指定关键字nonatomic作为属性的特性,编译器会生成强制彼此互斥的getter和setter方法。 
编译器生成了@synchronize(mutex, atomic)语句来确保彼此互斥。这样设置代码和变量会产生一些消耗,它会比直接访问更慢一些。 
 
 
选择性能 
想 让一些代码在后台执行,NSObject也提供了方法。这些方法的名字中都有performSelector:,最简单的就是 performSelectorInBackground:withObject:,他能在后台执行一个方法。它通过一个线程来运行方法。定义这些方法时 必须遵从以下限制: 
1、这些方法运行在各自的线程里,因此必须为这些Cocoa对象创建一个自动释放池,而自动释放池是与主线程相关的。 
2、这些方法不能有返回值,并且要么没有参数,要么只有一个参数对象。换句话说,你只能使用以下代码格式中的一种: 
 
 
-(void)myMethod; 
-(void)myMethod:(id)myObject; 
记住这些限制,实现的代码如下: 
-(void)myBackgroundMethod 

     @autoreleasepool 
     { 
          NSLog(@"My Background Method"); 
     } 

或: 
-(void)myOtherBackgroundMethod:(id)myObject 

     @autoreleasepool 
     { 
          NSLog(@"My Background Method %@", myObject); 
     } 

 
 
在后台执行你的方法,调用performSelectorInBacktground:withObject:方法: 
[self performSelectorInBackground:@selector(myBackgoundMethod) withObject:nil]; 
[self performSelectorInBackground:@selector(myOhterBackgroundMethod:) withObject:arugmentObject]; 
 
 
当方法执行结束后,Objective-C运行时会特地清理并弃掉线程。 
 
 
调度队列: 
GCD可以使用调度队列(dispatch queue),它与线程很相似但使用起来更简单。只要写下你的代码,把它指派为一个队列,系统就会执行它,可以同步或异步执行任意代码。 
 
 
一共有以下3种类型的队列: 
1、连续队列:每个连续队列都会根据指派的顺序执行任务。可以按自己的想法创建任意数量的队列,它们会并行操作任务。 
2、并发队列:每个并发队列都能并发执行一个或多个任务。任务会根据指派到队列的顺序开始执行。你无法创建连续队列,只能从系统提供的3个队列内选择一个来使用。 
3、主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。 
 
 
连续队列 
有时有一连串任务需要按照一定的顺序执行,这时便可以使用连续队列。任务执行顺序为先入先出(FIFO):只要任务是异步提交的,队列会确保任务根据预定顺序执行。这些队列都是不会发生死锁的。 
 
 
技术分享 
 
 
连续队列的使用: 
dispath_queue_t  my_serial_queue; 
my_serial_queue = dispatch_queue_create("com.apress.MySerialQueue1", NULL); 
 
 
第一个参数是队列的名称,第二个负责提供队列的特性(现在用不到,所以必须是NULL)。当队列创建好之后,就可以给它指派任务。 
 
 
并发队列: 
并发调度队列适用于那些可以并行运行的任务。并发队列也遵从先入先出(FIFO)的规范且任务可以在前一个任务结束前就开始执行。 
一次所运行的任务数量是无法预测的,它会根据其他运行的任务在不同时间变化。所以每次你运行同一个程序,并发任务的数量可能会是不一样的。 
 
 
技术分享 
 
 
每个应用程序都有3种并发队列可以使用:高优先级(high)、默认优先级(default)和低优先级(low)。如果想要引用到它们,可以调用dispatch_get_global_queue方法: 
dispatch_queue_t myQueue; 
myQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
 
 
其他优先级的选项分别是DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORTY_LOW.第二个参数暂时都用0。 
因为它们都是全局的,所以无需为它们管理内存。不需要保留这些队列的引用,在需要的时候使用函数来访问就可以了。 
 
 
主队列 
使用dispatch_get_main_queue可以访问与应用程序主线程相关的连续队列。 
dispatch_queue_t main_queue = dispatch_get_current_queue(void); 
这个队列与主线程相关,必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。 
通常要以同步方式使用这个队列,提交多个任务并在它们操作完毕后执行一些动作。 
 
 
获取当前队列 
可以通过dispatch_get_current_queue()来找出当前运行的队列代码块。 
如果在代码块对象之外调用了这个函数,则返回主队列。 
 
 
dispatch_queue_t myQueue = dispatch_get_current_queue(); 
 
 
队列也要内存管理 
调度队列是引用计数对象。可以使用dispatch_retain()和dispatch_release()来修改队列的保留计数器的值。 
无法用在全局调度队列上。 
编写一个使用了垃圾回收的OS X应用程序,那么必须手动管理这些队列。 
 
 
队列的上下文: 
可以向调度对象(包括调度队列)指派全局数据上下文,可以在上下文中指派任意类型的数据。系统只能知道上下文包含了与队列有关的数据,上下文数据的内存管理需要程序员自己做。在需要它的时候分配内存并在队列销毁之前进行清理。 
 
 
在为上下文数据分配内存的时候,可以使用dispatch_set_context()和dispatch_get_context()函数。 
 
 
NSMutableDictionary *myContext = [[NSMutableDictionary alloc] initWithCapacity: 5]; 
[myContext setObject:@"My Context" forKey: @"title"]; 
[myContext setObject:[NSNumber numberWithInt: 0] forKey: @"value"]; 
dispatch_set_context(_serial_queue, (_bridge_retained void *)myContext); 
创建一个字典来存储上下文。也可以使用其他的指针类型。 
使用_bridge_retained来给myContext的保留计数器的值加1. 
 
 
清理函数: 
不用知道上下文对象在何时何地会被弃用。要清理上下文对象的数据,可以让对象在它弃用的时候调用一个函数,像dealloc函数。 
 
 
函数格式: 
void function_name(void *context); 
 
 
创建一个会在上下文对象弃用时调用的示例函数,通常被称为终结器(finalizer)函数。 
 
 
void myFinalizerFunction(void *context) 

     NSLog(@"myFinalizerFunction"); 
     NSMutableDictionary *theData = (_bridge_transfer NSMutableDictionary*)context; 
     [theData removeAllObjects]; 

_bridge_transfer关键字把对象的内存管理由全局释放池变换成了我们的函数。当函数结束时,ARC将会给它的保留计数器的值减1. 
 
 
在代码块里访问上下文内容: 
NSMutableDictionary *myContext = (_bridge NSMutableDictionary *) dispatch_get_context(dispatch_get_current_queue()); 
 
 
_bridge关键字告诉ARC我们不想自己来管理上下文的内存,而想交给系统来管理。 
 
 
添加任务 
有两种方式可以向队列中添加任务: 
同步:队列会一直等待前面任务结束。 
异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。 
 
 
可以选择向队列提交代码块或函数。一共4个调度函数,分别是代码块和函数各自的同步与异步方式。 
 
 
技术分享 
调度程序 
添加任务的最简单的方法是通过代码块。代码块必须是dispatch_block_t这样的类型,要定义为没有参数和返回值才行。 
 
 
typedef void (^dispatch_block_t)(void) 
先添加异步代码块。这个函数拥有两个参数,分别是队列和代码块。 
 
 
dispatch_async(_serial_queue, ^{NSLog(@"Serial Task 1");}); 
 
 
也可以定义一个代码块对象,而不是通过内联方式来创建: 
dispatch_block_t myBlock = ^{NSLog(@"My Predfined block"); }; 
dispatch_async(_serial_queue, myBlock); 
 
 
如果想要同步添加,请使用dispatch_sync函数。 
 
 
函数的标准原型: 
void function_name(void *argument) 
示例: 
void myDispatchFunction(void *argument) 

     NSLog(@"Serial Task %@", (_bridge NSNumber *) argument); 
     NSMutableDictionary *context = (_bridge NSMutableDictionary *) dispatch_get_context(dispatch_get_current_queue()); 
     NSNumber *value = [context objectForKey:@"value"]; 
     NSLog(@"value = %@", value); 

接下来需要向队列添加这个函数。调度函数拥有3个参数:队列、需要传递的任意上下文以及函数。如果没有信息要发送给函数,可以只传递一个NULL值。 
 
 
dispatch_async_f(_serial_queue, (_bridge void *)[NSNumber numberWithInt: 3], (dispatch_function_t)myDispatchFunction); 
 
 
队列创建完之后,就做好接受任务的准备了,当添加一个任务,队列就会安排好它的进度。 
如果想要以同步方式添加到队列中,调用dispatch_sync_f函数。 
如果想要暂停队列,调用dispatch_suspend()函数并传递队列名称。 
dispatch_suspend(_serial_queue); 
调用dispatch_resume()函数来重新启用。这些都是程序员自己来掌握的。 
dispatch_resume(_serial_queue); 
 
 
操作队列: 
有一些被称为操作(operation)的API,可以让队列在Objective-C层级上使用起来更加简单。 
 
 
想要使用操作,首先需要创建一个操作对象,然后将其指派给操作队列,并让队列执行它,3种创建操作的方式: 
1、NSInvocationOperation:如果你已经拥有一个可以完成工作的类,并且想要在队列上执行,可以尝试使用这种方法。 
2、NSBlockOperation:这有些像包含了需要执行代码块的dispatch_async函数。 
3、自定义的操作:如果你需要更灵活的操作类型,可以创建自己的自定义类型。你必须通过NSOperation子类来定义你的操作。 
 
 
创建调用操作(invocation operation) 
NSInvocationOperation会为执行任务的类调用选择器。如果拥有一个包含需要的方法的类,使用这种方式来创建会非常方便: 
@implementation MyCustomClass 
- (NSOperation *) operationWithData:(id)data 

     return [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(myWorkerMethod:) object:data]; 

 
 
// This is the method that does actual work 
-(void) myWorkerMethod: (id)data 

     NSLog(@"My Worker Method %@", data) 

 
 
@end 
 
 
一旦向队列添加了操作,任务即将执行时便会调用类里面的myWorkerMethod:方法。 
 
 
创建代码块操作(block operation) 
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock: ^{  
// Do your business 
}]; 
 
 
这些操作的代码块与我们在调度队列中使用的是同一种类型。 
通过addExecutionBlock:方法继续添加更多的代码块。 
代码块会根据队列的类型的方式运行。 
 
 
[blockOperation addExecutionBlock: ^{ 
// do your stuff 
}];  
 
 
向队列中添加操作: 
一旦创建了操作,就需要向队列中添加代码块。 
使用NSOperationQueue来取代之前使用的dispatch_queue_t函数,NSOperationQueue一般会并发执行操作。它具有相关性,因此如果某操作是基于其他操作的,它们会相应地执行。 
 
 
如果要确保你的操作时连续执行的,可以设置最大并发操作数为1,这样任务将会按照先入先出的规范执行。 
在向队列添加操作之前,需要某个方法来引用到那个队列。  
 
 
 
现在运行的队列: 
NSOperationQueue *currentQueue = [NSOperationQueue currentQueue]; 
 
 
或主队列: 
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; 
 
 
创建自己队列的代码: 
NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init]; 
 
 
使用addOperation:来添加操作: 
[theQueue addOperation:blockOperation]; 
也可以添加需要执行的代码块来代替操作对象: 
[theQueue addOperationWithBlock: ^{ 
NSLog(@"My Block"); 
}]; 
 
 
一旦向队列添加了操作,它就会被安排进度并执行。 
 
 
AppKit简介 
 
 
构建项目: 
技术分享 
技术分享 
技术分享 
技术分享 
技术分享 
技术分享 
 
 
技术分享 
 
 
创建委托文件的@interface部分 
 
 
使用Xcode中的Interface Builder编辑器来布局窗口内容,并在MSCDelegate与用户界面之间创建各种连接。 
 
 
Interface Builder也适用于布局iOS应用程序,因此无论你要为哪一个平台开发应用程序,都需要在Interface Builder上进行很多工作。 
 
 
MSCAppDelegate类委托的头文件: 
#import <Cocoa/Cocoa.h> 
@interface MSCDelegate : NSObject <NSApplicationDelegate> 
@property (assign) IBOutlet NSWindow *window; 
@end 
 
 
IBOutlet 不是一个真正的Objective-C关键字。但通常会在Interface Builder中使用它。在IBOutlet 那行代码的左边边栏有一个点。点击那个点就会直接跳转到Interface Builder中与其相关的对象上。 
 
 
另一个像关键字的IBAction.IBAction被定义为void的作用,这意味着这个在文件中声明的方法返回的类型将是void(也就是什么都不返回)。 
 
 
技术分享 
 
 
Interface Builder编辑器,双击MainMenu.xib文件的Interface Builder编辑器。有人会称其为IB。 
 
 
虽 然文件的扩展名为.xib,当我们仍称之为nib文件。nib是NeXT Interface Builder的首字母缩写,是Cocoa从NeXT公司沿袭下来的技术结晶。nib文件是包含了压缩(freeze-dried)对象的二进制文件, 而.xib文件是XML格式的nib文件。在编译时,.xib文件将会编辑为nib格式。 
 
 
通过库面板来编辑下面的画面: 
技术分享 
 
 
创建连接: 
连接输出口(IBOutlet) 
首先要告诉NSTextField类型的实例变量textField以及resultsField分别指向哪个控件。 
将会用到辅助编辑器,在Xcode窗口的右上角有三个标记为Editor的按钮。点击中间的按钮,就会将编辑器从中间垂直分成两部分,这就是辅助编辑器。 
 
 
技术分享 
 
 
连接操作(IBAction) 
技术分享 
技术分享 
 
 
技术分享 
 
 
应用程序委托的实现 
 
 
首先了解一下IBOutlet是如何工作的: 
加 载nib文件时(MainMenu.nib会在应用程序启动时自动加载, 你可以创建自己的nib文件并手动加载它们),存储在nib文件中的任何对象都会被重新创建。这意味着会在后台执行alloc和init方法。所以,当程 序启动时,会分配并初始化一个MSCAppDelegate实例。在执行init方法期间,所有IBAction实例变量都是nil。只有在生成了nib 文件中的所有对象(包括窗口、文本框以及按钮)后,所有连接才算完成。 
 
 
一旦建立了所有连接(也就是将NSTextField等对象的地址添加到MSCAppDelegate的实例变量中),就会向创建的每个对象发送消息awakeFromNib。需要注意,对象的创建和awakeFromNib消息的发送没有任何既定的顺序。 
 
 
一个常见的错误是试图在init方法中使用IBOutlet执行一些操作。由于所有实例变量都为nil,所有发送给它们的消息都不执行任何操作,所以在init中尝试任何操作都会无疾而终。 
 
 
技术分享 
 
 
init方法: 
-(id) init 

     if (nil != (self = [super init])) 
     { 
          NSLog(@"init: text %@ / results %@", _textField, _resultsField); 
     } 
     return self; 

 
 
在文本框中输入默认值为Enter text here,并将结果标签的文本内容预设为Result。 
awakeFromNib方法: 
- (void) awakeFromNib 

     NSLog (@"awake: text: %@ / results %@", _textField, _resultsField); 
     [_textField setStringValue: @"Enter text here"]; 
     [_resultsField setStringValue: @"Results"]; 

 
 
uppercase: 
-(IBAction) uppercase: (id) sender 

     NSString *original = [_textField stringValue]; 
     NSString *uppercase = [original uppercaseString]; 
     [_resultesField setStringValue: uppercase]; 
} // uppercase 
 
 
内存管理检查:是否每句代码都是正确的,是的。创建的两个新对象(原始字符串和大写形式的字符串)都来自与除alloc、copy和new以外的方法,所以它们都位于自动释放池中,并且到时将会被清除。 
用stringValue消息从_textField获取最初的字符串。 
setStringValue:负责复制或保留传入的字符串。该方法内部的操作我们无从知道,但是我们知道代码是符合内存管理的。 
 
 
lowercase: 
-(IBAction) lowercase: (id) sender { 
     NSString *original = [_textField stringValue]; 
     NSString *lowercase = [original lowercaseString]; 
     [_resultesField setStringValue:lowercase ]; 

运行项目后的界面: 
技术分享

Objective-c 05 类别 类扩展 委托 非正式协议 协议 代码块 并发性 队列

标签:

原文地址:http://www.cnblogs.com/hengshu/p/5007574.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!