前言

书接上篇《Swift5之我对@propertyWrapper的考虑一》尾部所抛出的一个问题:怎么处理@propertyWrapper带来的负向影响?

我现在的一个答案是:运用其他计划代替@propertyWrapper完结代码复用以及和其类似的语法糖效果。

那么该怎么做呢?我的探究答案是:自定义数据类型+字面量协议+“重载强制类型转化操作符”。

下面让我结合上篇中《@propertyWrapper计划示例》,对我的探究答案逐个展开阐述。

// @propertyWrapper计划示例
// ======= 完结的 propertyWrapper TwelveOrLess ========
@propertyWrapper
struct TwelveOrLess {
  var wrappedValue: Int {
    didSet {
      wrappedValue = min(wrappedValue, 12)
    }
  }
​
  init(wrappedValue: Int) {
    self.wrappedValue = min(wrappedValue, 12)
  }
}
​
// ======= 测试用例 ========
struct NormalRectangle {
  var height: Int
  var width: Int
}
​
struct ConstrainedRectangle {
  @TwelveOrLess var height: Int
  @TwelveOrLess var width: Int
}
​
func test_TwelveOrLess() {
  let normalRectangle = NormalRectangle(height: 24, width: 24)
  print("(type(of: normalRectangle.height))") // Prints "Int"let constrainedRectangle = ConstrainedRectangle(height: 24, width: 24)
  print("(type(of: constrainedRectangle.height))") // Prints "TwelveOrLess"
}

自定义数据类型

万变不离其宗,完结代码复用的一种方式,便是自定义数据类型,封装相同逻辑。而@propertyWrapper的做法也是让开发者依照约好定义一种数据类型,来完结代码复用。下面,就让我们对照例子,先完结一个自定义的数据类型TwelveOrLess和对应的测试用例吧:

// ======= 完结的自定义数据类型 TwelveOrLess ========
struct TwelveOrLess {
  var wrappedValue: Int {
    didSet {
      wrappedValue = min(wrappedValue, 12)
    }
  }
​
  init(wrappedValue: Int) {
    self.wrappedValue = min(wrappedValue, 12)
  }
}
​
// ======= 测试用例 ========
struct ConstrainedRectangle {
  var height: TwelveOrLess
  var width: TwelveOrLess
}
​
func test_TwelveOrLess() {
  // 报错:Cannot convert value of type 'Int' to expected argument type 'TwelveOrLess'
  let constrainedRectangle = ConstrainedRectangle(height: 24, width: 24)
  print("(type(of: constrainedRectangle.height))") // Prints "TwelveOrLess"
}

与上篇《@propertyWrapper计划示例》比照,ConstrainedRectangle中的特点heightwidth在编译时和运行时都是清晰的数据类型:TwelveOrLess——这种显性的、前后共同的表达和体现,能有用协助让开发者特别是初级开发者防止遇到上篇示例展现的那样疑惑:特点heightwidth在运行时类型为何产生了突变。

不过,这时候编译时,我们会遇到报错:Cannot convert value of type 'Int' to expected argument type 'TwelveOrLess'

因为在编译时,特点heightwidthTwelveOrLess类型,因而无法承受Int类型的字面量值进行实例初始化。而在上篇《@propertyWrapper计划示例》中,能够运用Int类型的字面量值进行实例初始化,为了对齐《@propertyWrapper计划》的开发体会,我们应该怎么做呢?那便是运用字面量协议。

字面量协议

字面量协议是Swift 提供给自定义数据类型完结和根底数据类型相同的经过字面量进行实例初始化的能力。此处不作过多展开,有爱好的童鞋可看我的这篇介绍文章:《Swift的字面量类型(Literal Type)和字面量协议(Literal Protocol)》

为了处理上述报错以及对齐《@propertyWrapper计划》的开发体会,只需要让TwelveOrLess完结ExpressibleByIntegerLiteral字面量协议即可:

// ======= 完结的自定义数据类型 TwelveOrLess ========
struct TwelveOrLess {
  var wrappedValue: Int {
    didSet {
      wrappedValue = min(wrappedValue, 12)
    }
  }
​
  init(wrappedValue: Int) {
    self.wrappedValue = min(wrappedValue, 12)
  }
}
​
// 完结ExpressibleByIntegerLiteral字面量协议
extension TwelveOrLess: ExpressibleByIntegerLiteral {
  typealias IntegerLiteralType = Intpublic init(integerLiteral value: IntegerLiteralType) {
    self.init(wrappedValue: value)
  }
}
​
// ======= 测试用例 ========
struct ConstrainedRectangle {
  var height: TwelveOrLess
  var width: TwelveOrLess
}
​
func test_TwelveOrLess() {
  let constrainedRectangle = ConstrainedRectangle(height: 24, width: 24)
  print("(type(of: constrainedRectangle.height))") // Prints "TwelveOrLess"
}

现在看起来开发体会和《@propertyWrapper计划》差不多了,可是还是有短板,那便是赋值操作时,会报错:Cannot convert value of type 'TwelveOrLess' to specified type 'Int'

func test_TwelveOrLess() {
  // 报错:Cannot convert value of type 'TwelveOrLess' to specified type 'Int'
  var temp_h: Int = constrainedRectangle.height
  print("temp_h: (temp_h)")
}

这时候该怎么处理呢?那便是“重载强制类型转化操作符”,在其赋值给其他类型时可进行强制转化。

“重载强制类型转化操作符”

可是,很不幸地,现在Swift不支撑重载强制类型转化操作符!!!

对此,这个探究计划卡在此处。可是,这儿将会经过C++展现这个探究计划,协助读者一窥全貌:

#include <iostream>class TwelveOrLess { 
public:
 int wrappedValue;
 TwelveOrLess() {
  }
 
 // 重载默认赋值运算符 =(相当于Swift的完结“字面量协议”)
 TwelveOrLess& operator = (const int& right) {
  wrappedValue = std::min(12,right);
  return *this;
  }
 
 // 重载强制类型转化操作符(type conversion operator)
 operator int() {
  return wrappedValue;
  }
};
​
int main() {
 // 测试字面量初始化实例
 TwelveOrLess t;
 t = 24;
 std::cout << "t: " << t.wrappedValue << std::endl; // Prints "12"
 // 测试转化为根底类型:重载强制类型转化操作符后,赋值给根底类型时,会进行自动强转。
 int i = 24;
 i = t; //等价于 i = t. operator int()
 std::cout << "i: " << i << std::endl; // Prints "12"
}

参照C++的做法,假若Swift支撑“重载强制类型转化操作符”,那么,就能够完结TwelveOrLess赋值给Int类型变量的开发体会。可是现在,Swift却不支撑。那还有没其他处理计划呢?一种处理计划便是完结一个和赋值运算符(=)类似自定义操作符。

完结自定义操作符

Swift现在是支撑开发者完结自定义操作符,因而接着上述思路,我们可给TwelveOrLess完结一个和赋值运算符(=)类似自定义操作符<<,然后经过这个操作符完结赋值操作:

// ======= 完结的自定义数据类型 TwelveOrLess ========
struct TwelveOrLess {
  var wrappedValue: Int {
    didSet {
      wrappedValue = min(wrappedValue, 12)
    }
  }
​
  init(wrappedValue: Int) {
    self.wrappedValue = min(wrappedValue, 12)
  }
}
​
// 完结ExpressibleByIntegerLiteral字面量协议
extension TwelveOrLess: ExpressibleByIntegerLiteral {
  typealias IntegerLiteralType = Intpublic init(integerLiteral value: IntegerLiteralType) {
    self.init(wrappedValue: value)
  }
}
​
// 完结自定义操作符 <<
infix operator <<
extension TwelveOrLess {
  static func <<(left: inout Int, right: TwelveOrLess) {
    left = right.wrappedValue
  }
}
​
// ======= 测试用例 ========
struct ConstrainedRectangle {
  var height: TwelveOrLess
  var width: TwelveOrLess
}
​
func test_TwelveOrLess() {
  let constrainedRectangle = ConstrainedRectangle(height: 24, width: 24)
  print("(type(of: constrainedRectangle.height))") // Prints "TwelveOrLess"
 
  var temp_h: Int = 21
  temp_h << constrainedRectangle.height
  print("temp_h: (temp_h)") // Prints "12"
}

至此,在赋值操作方面算是接近了《@propertyWrapper计划》的开发体会。

总结

现在探究出来的计划在代码复用、字面量初始化方面都是对齐《@propertyWrapper计划》的开发体会,并且保证了开发者的代码感知在编译时和运行时是共同的。可是,由于Swift暂不支撑重载强制类型转化操作符这点,导致在赋值操作方面稍差人意。若读者有其他好的想法,欢迎评论交流。