1.多用协议,少用继承
继承只能单继承,这无法解决实现多继承的功能,当我们已经继承了某个类,而又想继承其他类,这就很无奈,而类型可以遵循多个协议,swift推荐使用面向协议编程。
比如想写一个BaseViewController抽象出公共的UIViewController的功能,这时候可以用协议,因为协议可以有默认实现,我们可以在默认实现里实现公共代码,如果想访问UIViewController的属性,可以对协议进行限定:只允许UIViewController的子类可以遵循该协议,代码如下:
public protocol ModalPresentable where Self: UIViewController {
// 设置一个遮罩
var maskView: UIView { get }
}
private struct ModalPresentableKeys {
public static var maskViewKey = "MaskViewKey"
}
var maskView: UIView {
if let maskView = objc_getAssociatedObject(self, &ModalPresentableKeys.maskViewKey) as? UIView {
return maskView
}
let mask = UIView(frame: view.bounds)
mask.backgroundColor = UIColor(white: 0, alpha: 0.1)
mask.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// 可以访问self.view
self.view.addSubview(mask)
// 添加点击事件
mask.addTapGesture { [unowned self] _ in
self.view.endEditing(true)
self.dismiss(animated: true, completion: nil)
}
objc_setAssociatedObject(self, &ModalPresentableKeys.maskViewKey, mask, .OBJC_ASSOCIATION_RETAIN)
return mask
}
// where Self: UIViewController
限定只有UIViewController类型才能遵循此协议,这同时让我们获得了访问UIViewController里的属性的能力。
协议的作用不仅限与此,面向对象编程需要编程前考虑各个模型之间的关系,比如某个功能是应该放在父类还是应该放在父类的父类,面向协议编程让我们更容易构架我们的功能模块,这是一种扁平化的设计方式,我们需要某个功能就把他设计成协议。
2. 少用强制解包
通过!可以把Optional类型的对象强制解包,如果为nil,则会崩溃;可以通过下面几种方式解包:
var a: Any? = 1
if let a = a {
print(a)
}
if let a = a as? Int {
print(a)
}
switch a {
case let x as Int:
print(x)
default:
break
}
let b = a ?? 0
3. 条件保护使用guard,不用if
func test(_ num1: Int?, num2: Int?) {
// 推荐
guard let num1 = num1, let num2 = num2 else {
print("null")
return
}
print(num1, num2)
}
test(1, num2: 2)
test(nil, num2: 2)
4. 尽量使用函数式编程
比如想要实现一个功能,有一个学生对象数组,计算90分以上学生的分数总和:
struct Student {
var name: String
var uid: Int
var score: Double
}
let students = [
Student(name: "zhangsan", uid: 1, score: 80),
Student(name: "zhangsan", uid: 1, score: 90),
Student(name: "zhangsan", uid: 1, score: 94.5),
Student(name: "zhangsan", uid: 1, score: 70),
Student(name: "zhangsan", uid: 1, score: 100),
Student(name: "zhangsan", uid: 1, score: 89)]
let sum = students
.filter { $0.score >= 90 }
.map { $0.score }
.reduce(0, +)
print("90分以上学生分数总和:", sum)
使用filter过滤90分以上学生,map转换元素类型, reduce实现求和,代码简单方便。
5. 定义model时尽量用struct,少用class,除非需要继承。
就如上面的例子Student就是结构体,结构体是值类型更安全,class是引用类型。
6. 使用自定义初始化方法初始化类型
如果初始化类型时,同时需要向对象传递参数,则应该自定义初始化方法,传入参数。如下面代码初始化自定义ViewController
// 推荐方式
class RuleViewController: UIViewController {
// 这里属性应该声明为private
// 如果属性不需要修改,应该声明为let,
private let content: String
private let contentTitle: String
/// 自定义初始化
init(title: String, content: String) {
self.contentTitle = title
self.content = content
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// 尽量用let修饰
let viewController = RuleViewController(title: "title", content: "content")
PlaygroundPage.current.liveView = viewController
// 下面是不推荐的方式
class RuleViewController1: UIViewController {
private var content: String?
private var contentTitle: String?
}
let viewController2 = RuleViewController1()
PlaygroundPage.current.liveView = viewController2
下面的方式需要声明属性为可选类型,使用时需要解包,这样使用很不方便而且不安全。
7. 不需要改变的变量用let
如上个例子中的属性声明private let content: String
8. switch or if else
swift的 switch功能强大,可以绑定变量,在case中加where子句进行条件匹配,等等。
9. enum枚举从此站起来啦
enum 可以关联变量,可以在enum中定义方法,这让enum有更多使用场景;比如在接口请求时,接口类型和请求参数可以封装到enum中:
enum UserAPI {
case getUserInfo(uid: Int)
case login(userName: String, password: String)
/// 请求地址
var urlPath: String {
switch self {
case .getUserInfo:
return "/getuserInfo"
case .login:
return "/login"
default:
return ""
}
}
/// 请求参数
var paramter: [String: Any] {
switch self {
case .getUserInfo(uid: let uid):
return ["uid": uid]
case .login(userName: let un, password: let pwd):
return ["userName": un, "password": pwd]
default:
return [:]
}
}
}
10. 不要用固定类型,用范型
如果封装的功能适用于多个类型 ,可以用范型,或者用协议+范型,这两个结合能碰撞多火花,如下面的代码。
// 定义一个协议
protocol Named {
var name: String { get }
}
// Person 有名字
struct Person: Named {
var name: String
}
// 限定T必须遵循Named
struct PrintName<T: Named> {
let n: T
init(n: T) {
self.n = n
}
var printName: String {
return n.name
}
}
let s = Person(name: "zhangsan")
let p = PrintName(n: s)
print(p.printName)
11. 准确使用 open,public,internal,fileprivate,private
12. 有必要的时候可以将subscript 代替 func,使用起来真的很方便。
13. 便利初始化器
如果已有初始化器,又需要其他初始化方式,可以使用便利初始化器,便利初始化器不要最终调用唯一初始化器。
我们还以在extension中写一个便利初始化器,如下代码;定义了一个颜色便利初始化器,其中alpha有一个默认值1。
public extension UIColor {
convenience init(hex: UInt, alpha: CGFloat = 1) {
self.init(
red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
green: CGFloat((hex & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(hex & 0x0000FF) / 255.0,
alpha: alpha
)
}
}
print(UIColor(hex: 0x888888))
print(UIColor(hex: 0x878787, alpha: 0.3))
13. 合理给实参和行参命名,行参给调用者看,实参给定义者在方法内部使用。同时可以给参数默认值,避免定义多个重载方法。
let intArry = [1,3,4,5,2]
func getData(by index: Int, start startIndex: Int = 0)-> Int{
return intArry[index + startIndex]
}
print(getData(by: 1))
print(getData(by: 1, start: 1))
总结:切忌不要用OC的方式去写Swift,那样看起来是Swift实际上是OC,没有灵魂的Swift。
网友评论