iOS 指定初始化器 NS_DESIGNATED_INITIALIZER

本文深入探讨了指定初始化器在Objective-C中的作用,特别是在自定义UIView时如何确保对象始终具有特定属性,如圆角。通过重写父类初始化器并调用自定义初始化器,文章详细解释了无论使用何种初始化方法,对象都将具备期望的功能。

这两天看了指定初始化器的相关知识后,发现项目中有部分初始化方法存在问题,在这里做个总结。

1. 指定初始化器是用来解决什么问题的?
  • 我们自定一个对象的时候一般都希望给一个对象添加新的功能、属性或设置。那么这些功能属性和设置应该在哪里添加呢?为了防止遗漏或者添加顺序不对的情况,我们选择在自定义的初始化方法中添加。但是,如果我们没有用自定义的初始化方法来创建自定义的对象会如何?
    例如:
    我们自定义一个CustomView继承于UIView 并且我们希望这个自定义View有一个圆角。
 //自定义 CustomView.h
 #import <UIKit/UIKit.h>
 
 @interface CustomView : UIView

- (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius;
@end


//自定义CustomView.m

#import "CustomView.h"

@implementation CustomView

- (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius {
    
    self = [super initWithFrame:frame];
    
    if (self) {
    	
    	self.layer.cornerRadius = cornerRadius;
    }
    
    return self;
}

如果通过 CustomView *cV = [[CustomView alloc] initWithFrame:CGRectMake(x,y,width,height) andCornerRadius:5];来实例化CustomView,是完全没有问题的,创建出一个圆角为5的view。
但是,你能确保你自己每次都能记得用这个初始化方法来实例化CustomView吗?一个月以后呢?一年以后呢?更不用说你这个CustomView可能提供给别人使用。如果别人直接用CustomView继承过来的初始化方法来实例化一个对象:

CustomView *cV = [[CustomView alloc] initWithFrame:CGRectMake(x,y,width,height)];

这样创建的View肯定是不会有圆角的。
那么,我们怎么才能确保只要是CustomView的对象一定都有一个圆角呢?

2. 指定初始化器
  • 很明显,只要我们确保无论怎么初始化,都会调用我们指定的初始化方法(也就是指定初始化器 - (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius;),就可以达到只要是CustomView的对象一定会有一个圆角。
  • 这是我们可以把CustomView初始化方法 - (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius;定义为指定初始化器 - (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius NS_DESIGNATED_INITIALIZER;(指定初始化器:无论你调用对象的哪一个初始化方法,最终这个初始化方法都会直接或间接的来调用指定初始化器来创建对象)
3.指定初始化器与父类的指定初始化器
  • 刚才说到CustomView *cV = [[CustomView alloc] initWithFrame:CGRectMake(x,y,width,height)];这种方式创建CustomView对象是不会有圆角的(initWithFrame:是父类的指定初始化器,也就是父类的任何初始化方法,内部最终还是会调用initWithFrame:方法来创建对象的)。这时,我们只要重写父类的initWithFrame:方法,并在initWithFrame:方法中调用子类的初始化器方法就可以达到无论通过哪种方式实例化的CustomView对象都会具有一个圆角
 //自定义 CustomView.h
 #import <UIKit/UIKit.h>
 
 @interface CustomView : UIView

- (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius NS_DESIGNATED_INITIALIZER;
@end


//自定义CustomView.m

#import "CustomView.h"

@implementation CustomView

- (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius {
    
    self = [super initWithFrame:frame];
    
    if (self) {
    	
    	self.layer.cornerRadius = cornerRadius;
    }
    
    return self;
}

- (instacetype)initWithFrame:(CGRect)frame {
	
	return [self initWithFrame:frame andCornerRadius:1];//这里的1代表默认值,你可以根据情况自己定义 相当于如果没有指定圆角大小,那么圆角大小就是1
}

  • 听上去可能有点绕,下面我来解释下:
    CustomView父类的指定初始化器是- (instacetype)initWithFrame:(CGRect)frame
    也就是无论你用UIView的哪个初始化方法来创建CustomView(CustomView继承于UIView,所以CustomView也继承了UIView的所有初始化方法),最终都会调用- (instacetype)initWithFrame:(CGRect)frame方法,你又在CustomView中重写了父类的- (instacetype)initWithFrame:(CGRect)frame方法,所以无论如何都会走
- (instacetype)initWithFrame:(CGRect)frame {
   
   return [self initWithFrame:frame andCornerRadius:1];//这里的1代表默认值,你可以根据情况自己定义 相当于如果没有指定圆角大小,那么圆角大小就是1
}

也就是无论如何都会走

- (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius {
    
    self = [super initWithFrame:frame];
    
    if (self) {
    	
    	self.layer.cornerRadius = cornerRadius;
    }
    
    return self;
}

所以,这样创建出来的CustomView对象一定会有一个圆角。

4. 如果调用顶级父类的init方法会怎样?

CustomView *cV = [[CustomView alloc] init];

  • 这里就用到OC的消息传递相关的知识。
  1. 查找当前当前对象(CustomView)的方法列表,发现没有init方法。
  2. 查找父类(UIView)的方法列表,发现init方法。 为什么会在UIView中发现init方法呢?不是应该在UIResponder的方法列表中吗?前面提到过,子类会重写父类的指定初始化器(在这里UIResponder的指定初始化器就是 init。ps:本例中对于initWithCoder:不做讨论,可以自行查阅相关资料)。
  3. 调用UIView的init方法,并在init方法中调用UIView的指定初始化器initWithFrame:,这时的初始化就变成了CustomView *cV = [[CustomView alloc] initWithFrame:];,而CustomView由于重写了父类的initWithFrame:方法,并且在initWithFrame:方法中调用了CustomView的指定初始化器— - (instancetype)initWithFrame:(CGRect)frame andCornerRadius:(CGFloat)cornerRadius;所以,这时会创建一个圆角为1的CustomView。

总结:

  • 1.当前指定初始化器的对象要重写父类的指定初始化器,在重写的父类初始化器中调用当前的初始化器。
  • 2.指定初始化器中要调用父类的指定初始化器
指定初始化器Designated Initializers)是 C99 标准引入的一种更灵活、直观的初始化语法,允许在初始化结构体或数组时显式指定某个成员或元素的名称和初始值,而不是按顺序提供所有值,对于维护和阅读代码特别有帮助,尤其是在处理复杂的结构体时 [^1][^2]。 ### 初始化结构体 可以使用指定初始化器来初始化结构体的特定成员,而不必按顺序传递所有字段的值。示例如下: ```c #include <stdio.h> // 定义一个结构体 Point struct Point { int x; int y; }; int main() { // 使用指定初始化器初始化结构体 struct Point p1 = { .x = 1, .y = 2 }; printf("p1.x: %d, p1.y: %d\n", p1.x, p1.y); return 0; } ``` 在上述代码中,定义了一个结构体`Point`,包含两个成员`x`和`y`。在`main`函数中,使用指定初始化器`{ .x = 1, .y = 2 }`对结构体变量`p1`进行初始化,分别为`x`和`y`指定了初始值 [^1]。 ### 跳过某些成员 通过指定初始化器,可以跳过某些不需要初始化的成员,并且可以以任意顺序对特定成员进行初始化。示例如下: ```c #include <stdio.h> typedef unsigned int uint; // 定义一个结构体 student struct student { uint id; char *sex; char *name; }; void displayStudent(struct student *s) { printf("学号:%-5d 性别:%-10s 姓名:%-10s \n", s->id, s->sex, s->name); } int main() { // 结构体的指定初始化,跳过某些成员 struct student stu2 = { .id = 11, .sex = "g" }; struct student stu3 = { .name = "stu3", .sex = "g", .id = 22 }; displayStudent(&stu2); displayStudent(&stu3); return 0; } ``` 在上述代码中,定义了一个结构体`student`,包含`id`、`sex`和`name`三个成员。在`main`函数中,使用指定初始化器对`stu2`和`stu3`进行初始化。`stu2`跳过了`name`成员的初始化,`stu3`以任意顺序对成员进行了初始化 [^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值