其实我也是在看别的代码时无意中搜了一下initWithCoder这个方法,结果就引导 Designated Initializer那里去了。既然来了就弄懂吧,反正早晚都是要了解的,你看,这就是没有系统学习的方式,看到哪学到哪,不过这有一个好处,就是知道为什么缘由,不像有些教程那样,没有任何前戏直接就讲,我受不了。
在学些过程中主要参考了下面的两篇文章:
正确编写Designated Initializer的几个原则
How To: Objective C Initializer Patterns
这两篇文章解释的很清楚,尤其是how to那篇,还形象的画了调用的顺序图,看起来更加直观一些,还真是肯花时间啊,不过我想他画完图之后自己也会更加深刻了,那句话怎么说来着:送人玫瑰,自己手也香。所以我也来好好的总结一下,顺便加深自己的印象。
其实我要做的就是把他们在文章说的用代码验证一下。
我创建了一命令行的工程,在里面使用了xtrace,使用的目的是跟踪打印各个类的调用顺序。使用xtrace的方法就不在这里说明了,搜吧。
主要文件见下面:
//
// main.m
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/23/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TestInitView.h"
#import "SubTestInitView.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
[Xtrace includeMethods:@"^init"];
[Xtrace traceClass:[NSView class]];
[Xtrace traceClass:[TestInitView class]];
[Xtrace traceClass:[SubTestInitView class]];
[Xtrace traceClass:[NSObject class]];
TestInitView *tv = [[TestInitView alloc] initWithFrame:CGRectZero andName:@" "];
tv = tv;
TestInitView *tv2 = [[TestInitView alloc] initWithFrame:CGRectZero];
NSLog(@"Hello, World!");
NSLog(@"i am new %@.",[NSDate date]);
}
return 0;
}
//
// TestInitView.h
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/24/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface TestInitView : NSView
//Designated Initializer
- (instancetype)initWithFrame:(NSRect)frameRect andName:(NSString *)name;
@end
//
// TestInitView.m
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/24/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import "TestInitView.h"
@implementation TestInitView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
//Designated Initializer
- (instancetype)initWithFrame:(NSRect)frameRect andName:(NSString *)name
{
if(self == [super init])
//if(self == [super initWithFrame:frameRect])
{
//self.name = name;
}
return self;
}
/*
//Overriding super class Designated Initializer
- (id)initWithFrame:(NSRect)frameRect
{
return [self initWithFrame:frameRect andName:@" "];
}
*/
@end
//
// SubTestInitView.h
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/24/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import "TestInitView.h"
@interface SubTestInitView : TestInitView
@end
//
// SubTestInitView.m
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/24/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import "SubTestInitView.h"
@implementation SubTestInitView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
@end
此时此刻的文件实现目的是看看重新定义了子类的Designated Initiazlizer后,(1)没有override父类的Designated Initializer 和(2)没有在子类新定义的Designated Initializer中调用父类的Designated Initializer的情况下,使用子类Instance一个变量是什么样的顺序,原谅我在中文中加载了单词,原因是我也不知道该怎么翻译好。Instance的意思是说定义一个指定类型的变量,这个描述的话是在有点绕口,就当它在文中是个宏吧。
简单介绍一下这几个文件的关系,其实也能看出来TestInitView是继承于NSView的,在TestInitView.m中定义了一个新的Designated Initializer,但是却没有override父类的Designated Initializer,因为被我注释掉了。并且新定义 Designated Initializer也没有调用父类的Designated Initializer,它存在两个问题。
新定义的Designated Initializer是
//Designated Initializer
- (instancetype)initWithFrame:(NSRect)frameRect andName:(NSString *)name
{
if(self == [super init])
//if(self == [super initWithFrame:frameRect])
{
//self.name = name;
}
return self;
}
SubTestInitView在这时没有使用。
main.m中使用TestInitView 初始化了2个实例,但是使用的方法不一样。
TestInitView *tv = [[TestInitView alloc] initWithFrame:CGRectZero andName:@" "];
是企图通过使用自己Designated Initializer来初始化一个实例变量。
还有一个,它企图使用父类的Designated Initializer来实例化一个变量
TestInitView *tv2 = [[TestInitView alloc] initWithFrame:CGRectZero];
下面看看他的调用顺序,注意他们都是错误的。第1个实例化的顺序错了,第2个就更错了,缺少很多中间的过程。
2016-09-24 17:11:37.428 my-test-designate-initializer[2331:216606] Xtrace: Tracing NSObject will not trace all classes
| From: main
| [<TestInitView 0x10020fc00> initWithFrame:{{0, 0}, {0, 0}} andName:@" "]
| [<TestInitView 0x10020fc00>/NSView init]
| [<TestInitView 0x10020fc00>/NSView initWithFrame:{{0, 0}, {0, 0}}]
| [<TestInitView 0x10020fc00>/NSResponder init]
| -> <TestInitView 0x10020fc00> (init)
| -> <TestInitView 0x10020fc00> (initWithFrame:)
| -> <TestInitView 0x10020fc00> (init)
| -> <TestInitView 0x10020fc00> (initWithFrame:andName:)
| From: main
| [<TestInitView 0x10030caf0>/NSView initWithFrame:{{0, 0}, {0, 0}}]
| [<TestInitView 0x10030caf0>/NSResponder init]
| -> <TestInitView 0x10030caf0> (init)
| -> <TestInitView 0x10030caf0> (initWithFrame:)
2016-09-24 17:11:37.429 my-test-designate-initializer[2331:216606] Hello, World!
2016-09-24 17:11:37.431 my-test-designate-initializer[2331:216606] i am new 2016-09-24 09:11:37 +0000.
Program ended with exit code: 0
下面来奉上正确的代码。
首先需要修改TestInitView.m
1.在新定义的Designated Initializer中调用父类的Designated Initializer;
2.重新定义父类的Designated Initializer,并且调用1中新定义的Designated Initiazlier;
修改后的代码为:
//
// TestInitView.m
// my-test-designate-initializer
//
// Created by ChrisBluez on 9/24/16.
// Copyright © 2016 ChrisBluez. All rights reserved.
//
#import "TestInitView.h"
@implementation TestInitView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
//Designated Initializer
- (instancetype)initWithFrame:(NSRect)frameRect andName:(NSString *)name
{
//if(self == [super init])
if(self == [super initWithFrame:frameRect])
{
//self.name = name;
}
return self;
}
//Overriding super class Designated Initializer
- (id)initWithFrame:(NSRect)frameRect
{
return [self initWithFrame:frameRect andName:@" "];
}
@end
然后再重新运行,查看console中的打印结果
2016-09-24 17:20:19.942 my-test-designate-initializer[2356:223925] Xtrace: Tracing NSObject will not trace all classes
| From: main
| [<TestInitView 0x100200710> initWithFrame:{{0, 0}, {0, 0}} andName:@" "]
| [<TestInitView 0x100200710>/NSView initWithFrame:{{0, 0}, {0, 0}}]
| [<TestInitView 0x100200710>/NSResponder init]
| -> <TestInitView 0x100200710> (init)
| -> <TestInitView 0x100200710> (initWithFrame:)
| -> <TestInitView 0x100200710> (initWithFrame:andName:)
| From: main
| [<TestInitView 0x100207d60> initWithFrame:{{0, 0}, {0, 0}}]
| [<TestInitView 0x100207d60> initWithFrame:{{0, 0}, {0, 0}} andName:@" "]
| [<TestInitView 0x100207d60>/NSView initWithFrame:{{0, 0}, {0, 0}}]
| [<TestInitView 0x100207d60>/NSResponder init]
| -> <TestInitView 0x100207d60> (init)
| -> <TestInitView 0x100207d60> (initWithFrame:)
| -> <TestInitView 0x100207d60> (initWithFrame:andName:)
| -> <TestInitView 0x100207d60> (initWithFrame:)
2016-09-24 17:20:19.944 my-test-designate-initializer[2356:223925] Hello, World!
2016-09-24 17:20:19.946 my-test-designate-initializer[2356:223925] i am new 2016-09-24 09:20:19 +0000.
Program ended with exit code: 0
其中,NSResponder是NSView的父类,NSObject是NSResponder的父类。
对比两种结果就知道哪里有错误了。错误的实现Desigated Initializer和相关的Initializer带来的后果就是1.漏掉了某些期望被调用的Initializer,2调用顺序错误,3就是1和2都有。
最后不要脸的补充一下,这篇文章是目前我写的比较有技术含量的一篇,它集代码,运行结果,参考链接为一体。
再接再励。
我现在老丈人家,等会吃完饭跟媳妇回家再继续研究。
你们等着,我要学ios挣钱。