七个Swift中的陷阱以及避免方法
文章转载自简书作者bestswifter的文章,原文链接点击这里或者查看英文原文 1.协议扩展:强大但是需要谨慎使用一个Swift类可以去继承另一个类,这种能力是强大的。继承讲使类之间的特定关系更加清晰,并且支持细粒度代码共享。但是,Swift中如果不是引用类型的话(如:结构体、枚举),就不能具有继承关系。然而,一个值类型可以继承协议,同时协议可以继承另一个协议。虽然协议除了类型信息外不能包含其他代码,但是协议扩展( ? 但是Swift协议扩展的实现依然是一片新的未开发的领域,尚存在一些问题。代码并不总是按照我们期望的那样执行。因为这些问题出现在值类型(结构体与枚举)与协议组合使用的场景下,我们将使用类与协议组合的例子去说明这种场景下不存在陷阱。当我们重新改为使用值类型和协议的时候将会发生令人惊奇的事。 开始介绍我们的例子: 假设这里有使用两种不同谷物制作的三种 我们可以通过 因为大多数 Corn } class Pizza {
var crustGrain : Grain { return .Wheat }
//ohter common pizza behavior
}
class NewYorkPizza : Pizza {}
class ChicagoPizza : Pizza {}
这些默认的代码可以被重载去处理其它的情况(用玉米制作) class CornmealPizza : Pizza { override crustGain : Grain { return .Corn }
}
哎呀!这代码是错的,并且很幸运的是编译器发现了这些错误。你能发现这个错误么?我们在第二个crustGain中少写了 return .Corn } }
现在它通过编译并成功运行: // returns Wheat CornmealPizza().crustGrain // returns Corn
同时 pie:Pizza
但是通用类型 Swift的引用类型在这个Demo中工作的很好。但是如果这个程序涉及到并发性、竞争条件,我们可以使用值类型来避免这些。让我们来试一下值类型的Pizza吧! 这里和上面一样简单,只需把class修改为struct即可: struct NewYorkPizza { struct ChicagoPizza { struct CornmealPizza { crustGrain : Grain = .Corn } 执行 // returns Corn 当我们使用引用类型的时候,我们通过一个超类Pizza来达到目的。但是对于值类型将要求一个协议和一个协议扩展来合作完成。 protocol Pizza {} ?
extension Pizza { crustGrain : Grain { return .Wheat } }
?
struct NewYorkPizza : Pizza { }
struct ChicagoPizza : Pizza { }
struct CornmealPizza : Pizza { crustGain : Grain = .Corn }
这段代码可以通过编译,我们来测试一下: // returns Wheat What?! 对于执行结果,我们想说 ??在协议扩展中重写协议中的属性时要仔细核对 OK,我们把这个拼写错误改正过来: crustGrain : Grain = .Corn } 重新执行 // returns Corn Hooray! 为了在讨论 pie : Pizza
这个变量能够在不同种类的Pizza中去使用 为什么这个程序显示 在这种情况下,Swift提供一种解决方案,除了在协议扩展中( protocol Pizza { get } } return .Wheat } }
在协议内声明变量并在协议扩展中定义,这样会告诉编译器关注变量pie运行时的值。 在协议中一个属性的声明有两种不同的含义,静态还是动态调度,取决于这个属性是否在协议扩展中定义。 补充了协议中变量的声明后,代码可以正常运行了: ??在协议扩展中定义的每一个属性,需要在协议中进行声 然而这个设法避免陷阱的方式并不总是有效的。 导入的协议不能够完全扩展 框架(库)可以使一个程序导入接口去使用,而不必包含相关实现。例如苹果给我们提供了需要的框架,实现了用户体验、系统设施和其他功能。Swift的扩展允许程序向导入的类、结构体、枚举和协议中添加自己的属性(这里的属性并不是存储属性)。通过协议扩展添加的属性,就好像它原来就在协议中一样。但实际上定义在协议扩展中的属性并非一等公民,因为通过协议扩展无法添加属性的声明。 我们首先来实现一个框架,这个框架定义了 导入框架并扩展 import PizzaFramework
?
public Corn }
?
return .Wheat } }
extension CornmealPizza { return .Corn } }
和以前一样,静态调度产生一个错误的答案 pie : Pizza = CornmealPizza() pie.crustGrain // returns Wheat Wrong!
这个是因为(与刚才的解释一样)这个 ??不要对导入的协议进行扩展,新增可能需要动态调度的属性 正像刚才描述的那样,框架与协议扩展之间的交互,限制了协议扩展的效用,但是框架并不是唯一的限制因素,同样,类型约束也不利于协议扩展 Attributes in restricted protocol extensions:declaration is no longer enough 回顾一下此前的Pizza的例子: crustGrain : Grain = .Corn } 让我们用 struct Meal : MealProtocol { mainDish : MainDishOfMeal
}
结构体 protocol MealProtocol {
typealias MainDish_OfMealProtocol
mainDish : MainDish_OfMealProtocol { get }
isGlutenFree : Bool { get }
}
为了避免中毒,代码中使用了默认值(不含有谷蛋白) extension MealProtocol {
return false }
}
Swift中的 extension MealProtocol where MainDish_OfMealProtocol : Pizza { return mainDish.crustGrain == .Corn }
}
一个带有 让我们做一份美味的 meal : Meal = Meal(mainDish:CornmealPizza()) 结果: 正像我们在前面小节演示的那样,当发生动态调度的时候,我们应该在协议中声明,并且在协议扩展中进行定义。但是约束性扩展的定义总是静态调度的。为了防止由于意外的静态调度而引起bug: ??如果一个新的属性需要动态调度,避免使用约束性协议扩展 使用可选链赋值和副作用Swift可以通过静态地检查变量是否为 不幸的是,如果可选链中被赋值的引用可能为空,就可能导致错误,考虑下面这段代码, class Holder {
x = 0
}
?
n = 1
h:Holder? = nil
h?.x = n++
在这段代码的最后一行中,我们把 变量 ??避免把一个有副作用的表达式的结果通过可选链赋值给等号左边的变量 函数编程陷阱由于Swift的支持,函数式编程的优点得以被带入苹果的生态圈中。Swift中的函数和闭包都是一等公民,不仅方便易用而且功能强大。不幸的是,其中也有一些我们需要小心避免的陷阱。 比如 Swift的 我们重写 enum Grain {
Corn
}
struct CornmealPizza {
func setCrustGrain(inout grain:Grain) {
grain = .Corn
}
}
为了测试这个函数,我们给它传一个变量作为参数。函数返回后,这个变量的值应该从 pizza = CornmealPizza() grain:Grain = .Wheat
pizza.setCrustGrain(&grain)
grain // returns Corn
现在我们尝试在函数中返回闭包,然后在闭包中设置参数的值: func getCrustGrainSetter() -> (inout grain:Grain) -> Void { return { (inout grain:Grain) in
grain = .Corn
}
}
}
使用这个闭包只需要多一次调用: grain:Grain = .Wheat aClosure = pizza.getCrustGrainSetter()
grain // returns Wheat (We have not run the closure yet)
aClosure(grain:&grain)
grain // returns Corn
到目前为止一切正常,但如果我们直接把参数传进 func getCrustGrainSetter(inout grain:Grain) -> () -> Void { return { grain = .Corn }
}
}
然后再试一次: aClosure = pizza.getCrustGrainSetter(&grain) print(grain) // returns Wheat (We have not run the closure yet)
aClosure()
print(grain) // returns Wheat What?!?
??避免在闭包中使用 这个问题在Swift文档中提到过,但还有一个与之关联的问题值得注意,这与创建的闭包的等价方法:柯里化有关。 在使用柯里化技术时, 在一个创建并返回闭包的函数中,Swift为函数的类型和主体提供了一种简洁的语法。尽管这种柯里化看上去仅是一种缩略表达式,但它与 func getCrustGrainSetterWithCurry(inout grain:Grain)() -> Void { grain = .Corn
}
}
和显式创建闭包时一样,我们调用这个函数然后返回一个闭包: aClosure = pizza.getCrustGrainSetterWithCurry(&grain) 在上面的例子中,闭包被显式的创建但没能成功为 这说明在柯里化函数中, ??避免在柯里化函数中使用 总结:七个避免
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ruby:别名为bundled_ruby
- React Native通信机制详解
- Unity3D 利用 ScriptableObject 把 Xml 打包成 a
- oracle jdbc DECIMAL and NUMERIC
- Postgresql plpgsql / sql是否支持where子句中的
- ruby-on-rails – bcrypt错误:Devies ruby?? 2.
- 详解C语言的exp()函数和ldexp()函数以及frexp()函
- visual-c – WinRT应用程序在发布版本中崩溃,但没
- VB中判断.NET3.5是否安装
- sqlite命令行出现not found||sqlite进入真机权限