Dynamic Inline
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
dynamic_inline |
是 |
否 |
lint |
否 |
3.0.0 |
dynamic和@inline(__alway)不要同时使用
示例
非触发
class C {dynamic func f() {}}class C {@inline(__always) func f() {}}class C {@inline(never) dynamic func f() {}}
触发
class C {@inline(__always) dynamic ↓func f() {}}class C {@inline(__always) public dynamic ↓func f() {}}class C {@inline(__always) dynamic internal ↓func f() {}}class C {@inline(__always)dynamic ↓func f() {}}class C {@inline(__always)dynamic↓func f() {}}
补充
深入理解Swift派发机制
Dynamic-Swift
@inline
内联函数
Empty Count
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_count |
否 |
否 |
performance |
否 |
3.0.0 |
判空首选isEmpty而不是count等于0
示例
非触发
var count = 0[Int]().isEmpty[Int]().count > 1[Int]().count == 1[Int]().count == 0xff[Int]().count == 0b01[Int]().count == 0o07discount == 0order.discount == 0
触发
[Int]().↓count == 0[Int]().↓count > 0[Int]().↓count != 0[Int]().↓count == 0x0[Int]().↓count == 0x00_00[Int]().↓count == 0b00[Int]().↓count == 0o00↓count == 0
Empty Enum Arguments
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_enum_arguments |
是 |
是 |
style |
否 |
3.0.0 |
当枚举和关联类型匹配并没被使用时,可以省略参数。
示例
非触发
switch foo { case .bar: break}switch foo { case .bar(let x): break}switch foo { case let .bar(x): break}switch (foo, bar) { case (_, _): break}switch foo { case "bar".uppercased(): break}switch (foo, bar) { case (_, _) where !something: break}switch foo { case (let f as () -> String)?: break}switch foo { default: break}
触发
switch foo { case .bar↓(_): break}switch foo { case .bar↓(): break}switch foo { case .bar↓(_), .bar2↓(_): break}switch foo { case .bar↓() where method() > 2: break}func example(foo: Foo) { switch foo { case case .bar↓(_): break }}
Empty Parameters
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_parameters |
是 |
是 |
style |
否 |
3.0.0 |
首选()->而不是Void->
示例
非触发
let abc: () -> Void = {}func foo(completion: () -> Void)func foo(completion: () thows -> Void)let foo: (ConfigurationTests) -> Void throws -> Void)let foo: (ConfigurationTests) -> Void throws -> Void)let foo: (ConfigurationTests) ->Void throws -> Void)
触发
let abc: ↓(Void) -> Void = {}func foo(completion: ↓(Void) -> Void)func foo(completion: ↓(Void) throws -> Void)let foo: ↓(Void) -> () throws -> Void)
Empty Parentheses with Trailing Closure
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_parentheses_with_trailing_closure |
是 |
是 |
style |
否 |
3.0.0 |
使用尾随闭包时,应当避免在方法调用后使用空括号
示例
非触发
[1, 2].map { $0 + 1 }[1, 2].map({ $0 + 1 })[1, 2].reduce(0) { $0 + $1 }[1, 2].map { number in number + 1 }let isEmpty = [1, 2].isEmpty()UIView.animateWithDuration(0.3, animations: { self.disableInteractionRightView.alpha = 0}, completion: { _ in ()})
触发
[1, 2].map↓() { $0 + 1 }[1, 2].map↓( ) { $0 + 1 }[1, 2].map↓() { number in number + 1 }[1, 2].map↓( ) { number in number + 1 }func foo() -> [Int] { return [1, 2].map↓() { $0 + 1 }}
补充
为什么说是方法调用之后呢?
因为尾随闭包其实是调用方法的一个参数,但是可以写在外边,所以看起来反例里边的括号倒在闭包“之前”而非“之后”了。这里的之后指的是执行顺序而非书写顺序。
Empty String
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_string |
否 |
否 |
performance |
否 |
3.0.0 |
(判空)首选使用isEmpty而不是比较string和空字符串
示例
非触发
myString.isEmpty!myString.isEmpy
触发
myString↓ == ""myString↓ != ""
Empty XCTest Method
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_xctest_method |
否 |
否 |
lint |
否 |
3.0.0 |
避免使用空的测试方法
示例
非触发
class TotoTests: XCTestCase { var foobar: Foobar? override func setUp() { super.setUp() foobar = Foobar() } override func tearDown() { foobar = nil super.tearDown() } func testFoo() { XCTAssertTrue(foobar?.foo) } func testBar() { // comment... XCTAssertFalse(foobar?.bar) // comment... }}class Foobar { func setUp() {} func tearDown() {} func testFoo() {}}class TotoTests: XCTestCase { func setUp(with object: Foobar) {} func tearDown(object: Foobar) {} func testFoo(_ foo: Foobar) {} func testBar(bar: (String) -> Int) {}}class TotoTests: XCTestCase { func testFoo() { XCTAssertTrue(foobar?.foo) } func testBar() { XCTAssertFalse(foobar?.bar) }}
触发
class TotoTests: XCTestCase { override ↓func setUp() { } override ↓func tearDown() { } ↓func testFoo() { } ↓func testBar() { } func helperFunction() { }}class TotoTests: XCTestCase { override ↓func setUp() {} override ↓func tearDown() {} ↓func testFoo() {} func helperFunction() {}}class TotoTests: XCTestCase { override ↓func setUp() { // comment... } override ↓func tearDown() { // comment... // comment... } ↓func testFoo() { // comment... // comment... // comment... } ↓func testBar() { /* * comment... * * comment... * * comment... */ } func helperFunction() { }}class FooTests: XCTestCase { override ↓func setUp() {}}class BarTests: XCTestCase { ↓func testFoo() {}}
Explicit ACL
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_acl |
否 |
否 |
idiomatic |
否 |
3.0.0 |
所有的声明都应该指定访问控制符。
示例
非触发
internal enum A {}public final class B {}private struct C {}internal enum A { internal enum B {}}internal final class Foo {}internalclass Foo { private let bar = 5 }internal func a() { let a = }private func a() { func innerFunction() { } }private enum Foo { enum Bar { } }private struct C { let d = 5 }internal protocol A { func b()}internal protocol A { var b: Int}internal class A { deinit {} }
触发
enum A {}final class B {}internal struct C { let d = 5 }public struct C { let d = 5 }func a() {}internal let a = 0func b() {}
Explicit Enum Raw Value
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_enum_raw_value |
否 |
否 |
idiomatic |
否 |
3.0.0 |
枚举类型最好指定明确的raw value
示例
非触发
enum Numbers { case int(Int) case short(Int16)}enum Numbers: Int { case one = 1 case two = 2}enum Numbers: Double { case one = 1.1 case two = 2.2}enum Numbers: String { case one = "one" case two = "two"}protocol Algebra {}enum Numbers: Algebra { case one}
触发
enum Numbers: Int { case one = 10, ↓two, three = 30}enum Numbers: NSInteger { case ↓one}enum Numbers: String { case ↓one case ↓two}enum Numbers: String { case ↓one, two = "two"}enum Numbers: Decimal { case ↓one, ↓two}
Explicit Init
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit init |
否 |
是 |
idiomatic |
否 |
3.0.0 |
应当避免直接使用.init()
示例
非触发
import Foundation; class C: NSObject { override init() { super.init() }}struct S { let n: Int }; extension S { init() { self.init(n: 1) } }[1].flatMap(String.init)[String.self].map { $0.init(1) }[String.self].map { type in type.init(1) }
触发
[1].flatMap{String↓.init($0)}[String.self].map { Type in Type↓.init(1) }func foo() -> [String] { return [1].flatMap { String↓.init($0) }}
Explicit Self
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit self |
是 |
style |
style |
是 |
3.0.0 |
应当使用self.调用自己的变量和方法
示例
非触发
struct A { func f1() {} func f2() { self.f1() }}struct A { let p1: Int func f1() { _ = self.p1 }}
触发
struct A { func f1() {} func f2() { ↓f1() }}struct A { let p1: Int func f1() { _ = ↓p1 }}
补充
持保留意见、我们总是喜欢短小,表达力强的代码。满屏的self并不是我喜欢的。
Explicit Top Level ACL
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_top_level_acl |
否 |
否 |
idiomatic |
否 |
3.0.0 |
顶级声明必须明确指定访问权限控制符
示例
非触发
internal enum A {}public final class B {}private struct C {}internal enum A { enum B {}}internal final class Foo {}internalclass Foo {}internal func a() {}
触发
enum A {}final class B {}struct C {}func a() {}internal let a = 0func b() {}
Explict Type Interface
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explict type interface |
否 |
否 |
idiomatic |
否 |
3.0.0 |
属性最好声明类型。
示例
非触发
class Foo { var myVar: Int? = 0}class Foo { let myVar: Int? = 0}class Foo { static var myVar: Int? = 0}class Foo { class var myVar: Int? = 0}
触发
class Foo { ↓var myVar = 0}class Foo { ↓let mylet = 0}class Foo { ↓static var myStaticVar = 0}class Foo { ↓class var myClassVar = 0}class Foo { ↓let myVar = Int(0)}class Foo { ↓let myVar = Set<Int>(0)}
Extension Access Modifier
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
extension_access_mofifier |
否 |
否 |
idiomatic |
否 |
3.0.0 |
推荐使用扩展访问修饰符。
示例
非触发
extension Foo: SomeProtocol { public var bar: Int { return 1 }}extension Foo { private var bar: Int { return 1 } public var baz: Int { return 1 }}extension Foo { private var bar: Int { return 1 } public func baz() {}}extension Foo { var bar: Int { return 1 } var baz: Int { return 1 }}public extension Foo { var bar: Int { return 1 } var baz: Int { return 1 }}extension Foo { private bar: Int { return 1 } private baz: Int { return 1 }}extension Foo { open bar: Int { return 1 } open baz: Int { return 1 }}
触发
↓extension Foo { public var bar: Int { return 1 } public var baz: Int { return 1 }}↓extension Foo { public var bar: Int { return 1 } public func baz() {}}public extension Foo { public ↓func bar() {} public ↓func baz() {}}
补充
正例和反例有相同的地方、没能get到点子上。
Fallthrough
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
fallthrough |
否 |
否 |
idomatic |
否 |
3.0.0 |
避免使用fallthough
示例
非触发
switch foo {case .bar, .bar2, .bar3: something()}
触发
switch foo {case .bar: ↓fallthroughcase .bar2: something()}
Fatal Error Message
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
fatal_error_message |
否 |
否 |
idiomatic |
否 |
3.0.0 |
fatalError方法最好提供message
示例
非触发
func foo() { fatalError("Foo")}func foo() { fatalError(x)}
触发
func foo() { ↓fatalError("")}func foo() { ↓fatalError()}
补充
fatalError
File Header
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_header |
否 |
否 |
style |
否 |
3.0.0 |
头部描述应当和项目模式一致。SWIFTLINT_CURRENT_FILENAME占位符可以配置必须和禁止选项,这将被真实的文件名替换。
示例
非触发
let foo = "Copyright"let foo = 2 // Copyrightlet foo = 2 // Copyright
触发
// ↓Copyright//// ↓Copyright//// FileHeaderRule.swift// SwiftLint//// Created by Marcelo Fabri on 27/11/16.// ↓Copyright © 2016 Realm. All rights reserved.//
补充
我能想到的适用场景就是在文件重命名之后,相应的文件顶部的注释也要一并改掉。
File Line Length
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_length |
是 |
否 |
metrics |
否 |
3.0.0 |
文件不要超过太多行
示例
非触发
print("swiftlint")*400
触发
print("swiftlint")*401
补充
查看源码可知建议不超过400行,个人感觉不好控制,业务复杂可以多一些,业务简单少一些。关键是模块划分合理,层次结构分明。不必过分追求代码行数。仁者见仁,智者见智。
File Name
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_name |
否 |
否 |
idiomatic |
否 |
3.0.0 |
文件名应当和文件中声明的类型或扩展一致(若存在)
First Where
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
first_where |
否 |
否 |
performance |
否 |
3.0.0 |
在集合对象中优先使用.first(where:)而不是.filter{}.first
示例
非触发
kinds.filter(excludingKinds.contains).isEmpty && kinds.first == .identifiermyList.first(where: { $0 % 2 == 0 })match(pattern: pattern).filter { $0.first == .identifier }(myList.filter { $0 == 1 }.suffix(2)).first
触发
↓myList.filter { $0 % 2 == 0 }.first↓myList.filter({ $0 % 2 == 0 }).first↓myList.map { $0 + 1 }.filter({ $0 % 2 == 0 }).first↓myList.map { $0 + 1 }.filter({ $0 % 2 == 0 }).first?.something()↓myList.filter(someFunction).first↓myList.filter({ $0 % 2 == 0 }).first(↓myList.filter { $0 == 1 }).first
For Where
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
for_where |
是 |
否 |
idiomatic |
否 |
3.0.0 |
where修饰符优于在for中使用if
示例
非触发
for user in users where user.id == 1 { }for user in users { if let id = user.id { }}for user in users { if var id = user.id { }}for user in users { if user.id == 1 { } else { }}for user in users { if user.id == 1 {} else if user.id == 2 { }}for user in users { if user.id == 1 { } print(user)}for user in users { let id = user.id if id == 1 { }}for user in users { if user.id == 1 { } return true}for user in users { if user.id == 1 && user.age > 18 { }}for (index, value) in array.enumerated() { if case .valueB(_) = value { return index }}
触发
for user in users { ↓if user.id == 1 { return true }}
Force Cast
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_cast |
是 |
否 |
idiomatic |
否 |
3.0.0 |
应该避免强制转换
示例
非触发
NSNumber() as? Int
触发
NSNumber() ↓as! Int
Force Try
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_try |
是 |
否 |
idiomatic |
否 |
3.0.0 |
应该避免强制try
示例
非触发
func a() throws {}; do { try a() } catch {}
触发
func a() throws {}; ↓try! a()
Force Unwrapping
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_unwrapping |
否 |
否 |
idiomatic |
否 |
3.0.0 |
避免强制解包
示例
非触发
if let url = NSURL(string: query)navigationController?.pushViewController(viewController, animated: true)let s as! Testtry! canThrowErrors()let object: Any!@IBOutlet var constraints: [NSLayoutConstraint]!setEditing(!editing, animated: true)navigationController.setNavigationBarHidden(!navigationController.navigationBarHidden, animated: true)if addedToPlaylist && (!self.selectedFilters.isEmpty || self.searchBar?.text?.isEmpty == false) {}print("\(xVar)!")var test = (!bar)var a: [Int]!private var myProperty: (Void -> Void)!func foo(_ options: [AnyHashable: Any]!) {func foo() -> [Int]!func foo() -> [AnyHashable: Any]!func foo() -> [Int]! { return [] }
触发
let url = NSURL(string: query)↓!navigationController↓!.pushViewController(viewController, animated: true)let unwrapped = optional↓!return cell↓!let url = NSURL(string: "http://www.google.com")↓!let dict = ["Boooo": "👻"]func bla() -> String { return dict["Boooo"]↓! }let dict = ["Boooo": "👻"]func bla() -> String { return dict["Boooo"]↓!.contains("B") }let a = dict["abc"]↓!.contains("B")dict["abc"]↓!.bar("B")if dict["a"]↓!!!! {var foo: [Bool]! = dict["abc"]↓!context("abc") { var foo: [Bool]! = dict["abc"]↓!}open var computed: String { return foo.bar↓! }
Function Body Length
| 标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
function_body_length |
是 |
否 |
metrics |
否 |
3.0.0 |
函数主体不要过长