类别、类扩展、协议与委托
类别 Category
类别是为已存在的类添加新的方法,以及将实现代码组织到不同模块中,便于管理和协作开发。
创建与使用类别
在 Xcode 8 中,创建一个类别的方法是新建一个 objective-c 文件,选择文件类型为 category,然后选择一个被扩展的类,并创建该文件。就会生成一个以原类名 + 类别名为名称的代码单元。
然后在单元中加入新的方法。
#import <Foundation/Foundation.h>
@interface NSString (LengthOfStr)
-(NSUInteger)length;
@end
@implementation NSString (LengthOfStr)
-(NSUInteger *)length
{
NSUInteger *length = [self length];
return length;
}
@end
这样就可以像调用原生的方法一样调用类别中的方法了,当然前提是引入了正确的类别文件。
int main(int argc, const char * argv[])
{
NSUInteger *length = [@"hello" length];
NSLog(@"%u", length);
}
类别的特点
- 类别不能添加新的实例变量
- 类别中的方法如果与现有方法重名,类别具有更高优先级,这并不表示类别的方法覆盖了现有的方法,而是在方法列表中类别方法更靠前,因此如果手动选择方法列表,还是可以调用现有方法的。而如果有多个类名采用了同样的名称,则调用方法时按照编译顺序执行,最后一个参与编译的方法会被执行。因此建议在创建类别时采用命名前缀避免这一问题。
- category 中也可以添加属性,只不过 @property 只会生成 setter 和 getter 的声明,不会生成 setter 和 getter 的实现以及成员变量
类扩展 Extension
类扩展又称为匿名分类,与 category 显著的区别就在与它不需要命名。类扩展一般放在 .m 文件首部,也可以放在头文件里,或是直接新建一个文件,其主要作用是将部分信息隐藏。
- 类扩展可以在包含源代码的类中使用它,意味着无法对系统文件进行扩展。
- 类扩展可以添加实例变量,修改属性的只读权限为可读写权限,但不能讲一个可读写权限在类扩展中声明为只读权限。
- 创建数量不限。
@interface Animal()
@property(retain) NSString *myTest;
@property(retain, readwrite) NSString *age;
-(NSString *)sport;
@end
虽然 Cocoa 没有真正的私有方法和私有属性,也就是说如果预先知道方法的名称,即使方法不是“公开”的,也可以调用这个方法,但是编译器会产生错误提示,而在 iOS 应用程序里调用这类私有变量和方法的行为会被拒绝上架。
协议 Protocol
协议类似于 Java 中的接口,是一种约定,由类显式地采用协议。
协议中可以创建方法和属性。
@protocol ManagerProtocol <NSObject>
@property(nonatomic, strong) NSString *title;
@optional
-(void)printTitle;
@required
-(void)payoff:(NSString *)staffName;
@end
@optional 是可选实现方法,@required 是必须实现方法。对于可选实现方法,如果调用时发现没有实现会报错。
采用协议是在类的声明中用尖括号括起来,可以采用多个协议,然后在定义文件中实现方法和创建属性的存取方法。
@interface Manager : Staff<ManagerProtocol>
@end
#import <Foundation/Foundation.h>
#import "Manager.h"
@implementation Manager
@synthesize title;
-(void)printTitle
{
NSLog(@"%@", [self title]);
}
-(void)payoff:(NSString *)staffName
{
NSLog(@"payoff %@", staffName);
}
@end
委托
委托通常与协议一同使用。委托是指某个对象 A 需要借助对象 B 的某个方法来实现功能时,就将对象 B 设置为对象 A 的委托对象,当对象 A 需要执行这一操作时就给对象 B 发送消息。至于如何得知对象 B 可以执行这一功能,则需要协议来约定,即对象 A 设定某一个协议,而对象 B 实现这一个协议,用来说明自己有这一能力,从而可以让 A 安全地设置 B 为委托对象。
如果没有协议进行约定,就需要一个方法来判断委托对象是否有此方法,这里用到了相应选择器 @selector
[manager respondsToSelector: @selector(setTitle:)]
这个表达式可以检测对象 manager 是否含有 setTitle 这个方法,如果有则返回 YES,如果没有则返回 NO。
@interface Boss: NSObject
@property(strong, nonatomic) id<ManagerProtocol> delegateManager;
-(void)payoff:(NSString *)name;
@end
@implementation Boss
-(void)setDelegateManager:(id<ManagerProtocol>)newDelegateManager
{
_delegateManager = newDelegateManager;
}
-(void)payoff:(NSString *)name
{
[[self delegateManager] payoff:name];
}
@end
这样当 boss 对象需要调用 payoff 时其实是向委托对象 manager 发送消息。
int main(int argc, const char * argv[])
{
Boss *boss = [Boss new];
Manager *manager = [Manager new];
[boss setDelegateManager:manager];
[boss payoff:@"Yasic"];
}
这里注意如果复写了 setter 方法,则应该在设置实例变量时用实例变量进行赋值,否则会陷入递归调用中从而崩溃。
非正式协议
创建一个 NSObject 的 category 被称作 “非正式协议”,在类别中定义的方法任何对象都可以选择性实现,从而在委托对象中直接调用,但是这样的非正式协议的方法是可选实现的,必须检测委托对象是否真的会对某个消息进行反应才可以安全地调用。
网友评论