- 定义一个协议, 一个协议可以扩展子另一个协议 如果需要扩展多个协议中间使用逗号分隔
//定义一个协议
@protocol AnimalDelegate <NSObject, ***>
@required //必须实现的方法
-(void)eat;
@optional //可选实现的方法
-(void)run;
-(void)say;
-(void)sleep;
@end
- 和其他高级语言中接口不同的是协议中定义的方法不一定是必须实现的。 我们可以通过关键字进行@required和@optional进行设置,如果不设置则默认是@required(注意ObjC是弱语法,即使不实现必选方法编译运行也不会报错);
- 协议通过<>进行实现,一个类可以同时实现多个协议,中间通过逗号分隔;
- 协议的实现只能在类的声明上,不能放到类的实现上(也就是说必须写成@interface Person:NSObject而不能写成@implementation Person);
- 协议中不能定义属性、成员变量等,只能定义方法;
@protocol CAMediaTiming
/* The begin time of the object, in relation to its parent object, if
* applicable. Defaults to 0. */
@property CFTimeInterval beginTime;
/* The basic duration of the object. Defaults to 0. */
@property CFTimeInterval duration;
/* The rate of the layer. Used to scale parent time to local time, e.g.
* if rate is 2, local time progresses twice as fast as parent time.
* Defaults to 1. */
@property float speed;
/* Additional offset in active local time. i.e. to convert from parent
* time tp to active local time t: t = (tp - begin) * speed + offset.
* One use of this is to "pause" a layer by setting `speed' to zero and
* `offset' to a suitable value. Defaults to 0. */
@property CFTimeInterval timeOffset;
/* The repeat count of the object. May be fractional. Defaults to 0. */
@property float repeatCount;
/* The repeat duration of the object. Defaults to 0. */
@property CFTimeInterval repeatDuration;
/* When true, the object plays backwards after playing forwards. Defaults
* to NO. */
@property BOOL autoreverses;
/* Defines how the timed object behaves outside its active duration.
* Local time may be clamped to either end of the active duration, or
* the element may be removed from the presentation. The legal values
* are `backwards', `forwards', `both' and `removed'. Defaults to
* `removed'. */
@property(copy) NSString *fillMode;
@end
上面的协议是苹果的原生代码,但你会看到协议里面有很多@propety
,那这个不就和上面的协议中不能定义属性、成员变量等,只能定义方法;
有点相违背么?这就会产生以下几个问题:
@propety
是什么,干了什么? 对于.m 文件中的这个@synthesize
有什么作用?
@property与@synthesize是成对出现的,可以自动生成某个类成员变量的存取方法。在Xcode4.5以及以后的版本,@synthesize可以省略。编译器会自动帮你加上getter 和 setter 方法的实现,并且默认会去访问_XXX这个成员变量,如果找不到_XXX这个成员变量,会自动生成一个叫做 _XXX的私有成员变量。
---
@interface Student : NSObject
{
int age;
int no;
}
//当编译器遇到@property时,会自动展开成getter和setter的声明
@property int age;
@property int no;
@end
----
@implementation Student
//@synthesize 会自动生成getter和setter的实现
//@synthesize 默认会去访问age,no,height同名的变量,,
//如果找不到同名的变量,会在内部自动生成一个私有同名变量age,no
//因此Student.h 中的这几个变量也可以省略不写。
@synthesize age,no; // @synthesize age = _age, no = _no; // 生成两个私有变量 _age, _no
@end
- 如果我就自己也写一个这样的协议,然后使用它会不会有问题?
self.fillModel = @"test"; // unrecognized selector
//这只是在头文件中声明,编译器是不会自动生成实例变量的,也就是说你可以用一个对象去实现这个协议,但是你不自己存储的话实例变量的话,访问同样会崩溃.
// 如何去避免这种崩溃呢,下面的例子是一种方案,同时也证明了@synthesize的作用
@protocol ViewContDelegate <NSObject>
@property float repeatCount;
@end
@interface ViewController ()<ViewContDelegate>
@end
@implementation ViewController
@synthesize repeatCount = _repeatCount;
- (void)viewDidLoad {
[super viewDidLoad];
self.repeatCount = 1;
NSLog(@"%f -- %f", self.repeatCount, _repeatCount);
}
@end
- 在
类中
的声明@propety 和协议中
声明@propety 有区别么?
两则没有区别,都是申明了setter/getter. 而在类中的.m 在Xcode4.5以后版本@synthesize可以被忽略。而生成的变量的操作其实还是由@synthesize完成的。
- 对于category也有类似的规范,那么为什么使用关联对象可以对类添加属性,category实现的原理?
1. 这个技术并不是category的特性,而是runtime.h的一种应用。iOS运行时机制,简单来说,就是苹果给开发这提供的一套在运行时动态创建类、添加属性/方法.
OC把实例变量当做一种存储偏移量所用的"特殊变量",交由"类对象"保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。甚至可以在运行期间向类中新增实例变量,这就是ABI,有了这种稳固的ABI,我们就可以在“class-continuation 分类”或者实现文件中定义实例变量了。所以说,不一定要在接口中把全部的实例变量都声明好,可以将某些变量从接口的public区段移走,以便保护与类实现有关的内部信息
2. 有没有主动引入 Category 的头文件,Category 中的方法都会被添加进主类中。我们可以通过 - performSelector: 等方式对 Category 中的相应方法进行调用,之所以需要在调用的地方引入 Category 的头文件,只是为了“照顾”编译器同学的感受。
在文件 objc-runtime-new.mm 中找到以下函数:
void _read_images(header_info **hList, uint32_t hCount){}在这个函数中对 Category 做了如下处理:
1. 将 Category 和它的主类(或元类)注册到哈希表中;
2. 如果主类(或元类)已实现,那么重建它的方法列表。
Category 中的实例方法和属性被整合到主类中;而类方法则被整合到元类中。对协议的处理比较特殊,Category 中的协议被同时整合到了主类和元类中。
不管是哪种情况,最终都是通过调用 static void remethodizeClass(Class cls) 函数来重新整理类的数据的。这个函数的主要作用是将 Category 中的方法、属性和协议整合到类(主类或元类)中,更新类的数据字段 data() 中 method_lists(或 method_list) 、 properties 和 protocols 的值。进一步,我们通过 attachCategoryMethods 函数的源码可以找到真正处理 Category 方法的 attachMethodLists 函数:主要作用就是将类中的旧有方法和 Category 中新添加的方法整合成一个新的方法列表,并赋值给 method_lists 或 method_list 。通过探究这个处理过程,我们也印证了一个结论,那就是主类中的方法和 Category 中的方法在 runtime 看来并没有区别,它们是被同等对待的,都保存在主类的方法列表中。不过,类的方法列表字段有一点特殊,它的结构是联合体, method_lists 和 method_list 共用同一块内存地址。当 newCount 的个数大于 1 时,使用 method_lists 来保存 newLists ,并将方法列表的 标志位 置为 RW_METHOD_ARRAY ,此时类的方法列表字段是 method_list_t 类型的指针数组;否则,使用 method_list 来保存 newLists ,并将方法列表的 标志位 置空,此时类的方法列表字段是 method_list_t 类型的指针。
runtime 对 Category 中方法的处理过程并没有对 +load 方法进行什么特殊地处理。因此,严格意义上讲 Category 中的 +load 方法跟普通方法一样也会对主类中的 +load 方法造成覆盖,只不过 runtime 在自动调用主类和 Category 中的 +load 方法时是直接使用各自方法的指针进行调用的。所以才会使我们觉得主类和 Category 中的 +load 方法好像互不影响一样。因此,当我们手动给主类发送 +load 消息时,调用的一直会是分类中的 +load 方法
- 事实上在ObjC中协议的更多作用是用于约束一个类必须实现某些方法,而从面向对象的角度而言这个类跟接口并不一定存在某种自然关系,可能是两个完全不同意义上的事物,这种模式我们称之为代理模式(Delegation)。在Cocoa框架中大量采用这种模式实现数据和UI的分离,而且基本上所有的协议都是以Delegate结尾。
文章中的引用: