深入理解Objective-C:Protocol(略带了解预编译)

来源:互联网 发布:淘宝上祛痘产品靠谱吗 编辑:程序博客网 时间:2024/04/29 00:56

1 、Protocol介绍

Protocol为进行网络中的数据交换而建立的规则、标准或约定。用于不同系统中实体间的通信。两个实体要想通信,必须有“同一种语言”,而且,对于通信内容,怎样通信和何时通信,都必须遵守一定的规定,这些规定就是协议。亦可简单地定义为:控制两实体间数据交换的一套规则。

对于iOS中的Protocol,大约可以理解为A让B做一件事情,做好了告诉A。

代码使用,稍微贴下代码,具体一步一步请自行去学习。本文不做解释。

1、首先创建一个Delegate

#import <Foundation/Foundation.h>@protocol WKProtocolDelegate <NSObject>@required- (void)startRun;@optional- (void)startThinking;@end

2、在VC中遵守协议 全程代码如下,

#import "ViewController.h"#import "WKProtocolDelegate.h"#import "ViewController2.h"@interface ViewController ()<WKProtocolDelegate>@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];    btn.frame = CGRectMake(100, 100, 100, 100);    btn.backgroundColor = [UIColor redColor];    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn];}- (void)click:(id)sender{    ViewController2 *vc = [[ViewController2 alloc] init];    vc.delegate = self;    [self.navigationController pushViewController:vc animated:YES];}- (void)startRun{    NSLog(@"%s",__func__);}//- (void)startThinking{// //    NSLog(@"%s",__func__);//    //}@end

3、在VC2 .h文件中,创建一个属性

#import <UIKit/UIKit.h>#import "WKProtocolDelegate.h"@interface ViewController2 : UIViewController@property (nonatomic,assign) id<WKProtocolDelegate> delegate;@end

4、然后调用。

全程.m文件如下:

#import "ViewController2.h"@interface ViewController2 ()@end@implementation ViewController2- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];    btn.frame = CGRectMake(100, 100, 100, 100);    btn.backgroundColor = [UIColor redColor];    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn];}- (void)click:(id)sender{    if ( [self.delegate respondsToSelector:@selector(startRun)] ) {        [self.delegate startRun];    }    if ( [self.delegate respondsToSelector:@selector(startThinking)] ) {        [self.delegate startThinking];    }}@end

虽然OC倡导使用protocol而不是block,但是本人很少用protocol,大都是通过block来完成了。
至于why?以后有机会进行讲解。

2、Protocol真面目

1、定义

在runtime层,protocol用结构体protocol_t(在objc-runtime-new.h中可以找到此定义)定义。

struct protocol_t : objc_object {    const char *mangledName;    struct protocol_list_t *protocols;    method_list_t *instanceMethods;    method_list_t *classMethods;    method_list_t *optionalInstanceMethods;    method_list_t *optionalClassMethods;    property_list_t *instanceProperties;    uint32_t size;   // sizeof(protocol_t)    uint32_t flags;    // Fields below this point are not always present on disk.    const char **_extendedMethodTypes;    const char *_demangledName;    property_list_t *_classProperties;    const char *demangledName();    const char *nameForLogging() {        return demangledName();    }    bool isFixedUp() const;    void setFixedUp();#   define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f))    bool hasExtendedMethodTypesField() const {        return HAS_FIELD(_extendedMethodTypes);    }    bool hasDemangledNameField() const {        return HAS_FIELD(_demangledName);    }    bool hasClassPropertiesField() const {        return HAS_FIELD(_classProperties);    }#   undef HAS_FIELD    const char **extendedMethodTypes() const {        return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil;    }    property_list_t *classProperties() const {        return hasClassPropertiesField() ? _classProperties : nil;    }};

2、真面目

通过protocol文件,查看Protocol是什么。

#import <Foundation/Foundation.h>@protocol WKProtocolDelegate <NSObject>@required- (void)startRun;@optional- (void)startThinking;@end

我们使用clang的命令去看看protocol到底会变成什么:
clang -rewrite-objc WKProtocolDelegate.h

wow~
WKProtocolDelegate.h.gch文件。

而且,我查找了资料,发下.gch文件无法正常打开。即使打开了,里面的内容全都是乱七八糟,完全不像正经打开的样子。 T _ T

如有小伙伴知道.gch文件如何打开,请告诉我一下。3Q

说到.gch…不得不说的一个词就是预编译。。。

3、预编译

预编译 传送门

所谓预编译头,就是把头文件事先编译成一种二进制的中间格式,供后续的编译 过程使用。

不要把这个中间格式与. o/.obj/.a/.lib的格式混淆,他们是截然不同的,所以预编译头文件的特性和目标文件也不同(尽管他们都属于某种中间文件) 。——但也有类似的地方的,比如,它们都是编译器之间不兼容的^_^,就是说你不能把VC生成的预编译头拿到GCC上去用。甚至扩展名都不一样,VC的是 大家都熟悉的. pch,而GCC的,是.gch——今天的主角。

为什么要使用预编译头?

再明确不过了,提高编译速度。为什么会提高编译速度?

这么说吧,你有两个文件a.cpp和b.cpp,都包含了同一个头文件 c.h。
那么正常的流程是:
1)将c.h和a.cpp合并,编译成a.o;
2)将c.h和b.cpp合并,编译成b.o;
3)最后将a.o和b.o链接成可执行文件。

过程很简单,浪费时间之处也一目了然:头文件c.h的内容实际上被解析了两遍。也许你要说,当然要两遍了,因为头文件几乎是不生成任何代码的,只有依附于 具体的.cpp文件才有意义。
正确,但那只是在代码执行过程中。但在代码编译的时候呢?编译器读入源代码,首先将其解析成为一种内部的表示方式。这个过程 与其所依附的.cpp文件并无关系,编译器接着可以读入.cpp文件并同样解析成内部表示,然后把两段内部表示拼接起来,再进行接下来的操作。
既然编译两 个.cpp文件都要先对c.h进行解析,那干嘛不把c.h解析好了保存成临时文件,用时读入,不就可以省了一次解析的时间了吗?——预编译头技术节省时间 的原理正在于此,尤其是在这样一个事实下:对源代码的“解析”这个步骤,确实是占了编译时间中很可观的一部分。

我看见你满是狐疑的脸:
预处理,就是编译之前的处理,合并.h和.cpp文件分明是预处理的步骤,而解析源代码是编译之中的步骤,先解析后合并?怎么 “预”处理反而跑到编译步骤之后了?这还叫“预”吗?——这个问题我们决定不深究了,毕竟现在的编译器早就混淆了预处理与编译的界限……毕竟,这么做是管 用的,对吗?

使用.gch文件

我们来看看结果。写一个C++的Hello world,使用cout输出一行字。包含了什么头文件?当然是iostream。这个头文件对于人们来说,绝对是熟视无睹级别的。然而使用它的时候,你 注意到编译器幕后的累累“罪行”了吗?是的,用 -H 参数编译一下这个Hello world吧!看看总共加载了多少个头文件?我的机器上,总共103个!

是的,你应该将它们做成一个.gch文件。如何做?如前所述,再简单不过:只要编译它就可以了:

g++ xxx.h

一句话,就是:把.h文件当成.cpp文件一样来编译。
这是最简单的,如果需要控制编译细节,比如常量定义之类,大可加上其它选项。运行之后,你会发现同个目录里生成了一个名叫xxx.h.gch的文件,这就是我们要的。

也许你和我一样,迫不及待地尝试g++ iostream了?呵呵,结果一定是和我一样的失败——在编译.gch的过程中,GCC并没有使用环境变量或 -I 选项来查找被编译的头文件,被编译的头文件必须在当前目录下。然而,被编译的头文件所进一步包含的其它头文件,却可以通过以上途径找到。简言之,就是把直接编译的那个头文件以类似对待.cpp文件的方式处理了。现在知道该如何编译iostream了吧?
对,在当前目录里建立一个头文件,起个随你喜欢的名 字,比如foo.h,在其里面写上:#include ,然后编译它:g++ foo.h。生成的foo.h.gch,就是我们要的了。其它文件需要用到iostream的,不要包含iostream,要包含foo.h。切记,不是 去包含foo.h.gch!

如果你用过VC,那么这个foo.h也许会让你找到一种似曾相识的感觉吧?对了,就相当于那个 stdafx.h !那么你也该记得,每个文件包含这个foo.h,都应该在文件一开始的地方,否则会出错。真的,终于找到了GCC中的stdafx.h,这种感觉几乎让人 热泪盈眶了^_^

那么接下来,照搬一些stdafx.h相关的注意事项吧,它们同样适用于.gch文件:
应该把那些不常修改的(首当其冲,当然是系统的)头文件放在预编译 头里,而那些属于你的程序的一部分的头文件,一般并不放在预编译头里,因为它们可能随时要被修改的。每修改一次就要重新生成预编译头,并没有速度优势可言,失去预编译头的意义了。

另外重要的注意事项是:如果你生成预编译头的时候用了一些选项,比如宏定义,那么使用这个预编译头的其它源代码文件,被编译的时候也要使用这些选项,否则会因为不匹配而编译失败。

对了,说了半天,从来没有正面讲过如何使用已经生成的预编译头。然而看到这里也该明白了,是的,很简单,只要包含其所对应的.h文件即可!比如你有个头文 件叫foo.h,另外有一大堆其它文件都包含了这个foo.h,原来没有使用预编译头技术,现在忽然想使用了,于是把foo.h编译成了 foo.h.gch。那其它文件要做怎样的修改?——什么都不用,一切照旧!聪明的GCC编译器在查找一个.h文件之前,会自动查找其目录里有没有对应 的.gch文件,如有,且可用,则用之;没有,才用到真正的.h头文件。——慢着,“如有,且可用”,什么叫“可用”?——就是指这个.gch格式要正 确,版本要兼容,而且如上所述,编译两者要用同样的选项。如果.gch不可用,编译器会给出一条警告,告诉我们:这个预编译头不能用!我只好用原有的.h 头文件啦!什么?你说看不到这个警告?——当然,要先打开 -Winvalid-pch 选项才行,其默认是关闭的。

用 -H 选项感受一下预编译头的清爽吧!再没有滚不完的头文件了,明显提高的速度,绝对会让你有种翻身解放的感觉,原来MinGW也可以和蜗牛般的速度说再见的。

笔者后记:有一次同事不小心生成了.gch,但是由于编译选项不一致,导致后面无法编译。小心地雷啊

优点

手动先编译下确实会提高运行速度。
但是不要轻易的手动编译,不然会出现下面的问题。

缺点:

但该文件有一个很不好的地方就是:

但我们修改了文件头的 .h 文件, gcc仍旧会编译原来的 .h.gch 文件, 不会载入新修改的文件头, 仍旧很容易造成错误 , 对于此的解决方案, 我们只需要将 Makefile文件 的 clean 目标项 修改为 rm -f .o .h.gch , 然后在重新编译就好…..

原创粉丝点击