Swift语法手记

1,601 阅读5分钟

1、Swift 调 OC

在桥接文件 SwiftDemo-Bridging-Header.h 里导入,如:

#import <MJRefresh/MJRefresh.h>
#import "TestView.h"
#import "UIView+HUD.h"

但需要注意的是,OC 的方法经过系统自动转换为 Swift 代码后,方法名可能会发生变化,比如单例方法:

+ (instancetype)sharedModel;

在 Swift 里的调用为:

UserModel.shared()

2、OC 调 Swift

导入 项目名-Swift.h,这是一个隐式的文件,这里面有系统自动转换的 Swift 代码。

#import "SwiftDemo-Swift.h"

注意要标注了 @objc 的属性和方法,才会被系统自动转换成 OC 的代码。比如:

Swift 属性:

@objc var urlStr: String?

系统自动转换成的 OC 属性:

@property (nonatomic, copy) NSString * _Nullable urlStr;

Swift 方法:

@objc public func cancelAllRequest() {}

系统自动转换成的 OC 方法:

- (void)cancelAllRequest;

3、Swift中的宏

Swift 里没有宏的这个概念,可以使用全局常量、全局变量、全局方法来代替。比如:

OC 的宏

#define kAppWidth          UIScreen.mainScreen.bounds.size.width

Swift 的全局常量

let kAppWidth = UIScreen.main.bounds.size.width

OC 的宏

#define kImageNamed(NAME)  [UIImage imageNamed:NAME]

Swift 的全局方法

func kImageNamed(_ name: String) -> UIImage? {
    return UIImage.imageNamed(name)
}

4、分类的处理

Swift 的分类的应用比 OC 多,在一个 Swift 的类里,经常使用一个分类来实现某个功能模块,比如:

// MARK: - TableView
extension SwiftController: UITableViewDelegate, UITableViewDataSource {}

// MARK: - 点击事件
extension SwiftController {}

给系统类添加方法,比如:

extension Dictionary {
    // MARK: 字典转字符串
    func stringValue() -> String? {
        let data = try? JSONSerialization.data(withJSONObject: self, options: [])
        let str = String(data: data!, encoding: String.Encoding.utf8)
        return str
    }
}

OC 的分类,在桥接文件 SwiftDemo-Bridging-Header.h 里导入后,可以直接调用,比如:

导入头文件

#import "UIImage+Extention.h"


OC 分类的方法声明

@interface UIImage (Extention)

/// 水印图片
- (UIImage *)waterImage;

@end


Swift 调用方法

let waterImg: UIImage = image!.water()

5、一些需要特别注意的语法

语法文档:swiftgg.gitbook.io/swift

5.1、类型转换

Swift 中,值永远不会被隐式转换为其他类型,只能显式转换,比如:

let a = 10;
let b = 1.0;
let c = a + Int(b);
let d = Double(a) + b;
let f = String(a)
let g = "\(a)"

5.2、数组字典初始化

let arr0: [Int] = []
let arr1: [Int] = [Int]()
let arr2: [Int] = [Int].init()
let arr3: [Int] = [Int].init(repeating: 0, count: 5)

let dict0: [String: Int] = [:]
let dict1: [String: Int] = [String: Int]()
let dict2: [String: Int] = [String: Int].init()

// 闭包式初始化, 只会执行一次
let arr4: [Int] = { () -> [Int] in
    return [1, 2]
}()

// 闭包式初始化, 可省略 () -> [Int] in
let arr5: [Int] = {
    return [3, 4]
}()

5.3、循环/遍历

forin 循环:

let interestingNumbers = [    "Prime": [2, 3, 5, 7, 11, 13]
]
for (key, numbers) in interestingNumbers {
    for number in numbers {
    }
    
    for (index, value) in numbers.enumerated() {
	}
}

while 循环:

var n = 2
while n < 100 {
    n *= 2
}

var m = 2
repeat {
    m *= 2
} while m < 100

区间循环:..< 创建的范围不包含上界,如果想包含的话使用 ...

for i in 0..<4 {}

for i in 0...4 {}

let names: [String] = ["a", "b", "c", "d"]
for name in names[2...] {
    print(name)
}

for name in names[...2] {
    print(name)
}

5.4、解包

if 加 感叹号(!)强制解包:

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}

if let 解包:

if let constantName = someOptional {
	// someOptional 有值
} else {
	// someOptional 为空
}

if let num = Int(optionalNum), let constantName = optionalName, num > 10  {
	// optionalNum 有值,optionalName 也有值,且 num 大于10
}

guard let 解包:

guard let name = person["name"] else {
	 // person["name"] 为空会走到这里
    return
}
// person["name"] 有值会继续往下执行

5.5、字符串

String 与 NSString 的无缝桥接:

var str: String = "a"
let nsStr: NSString = str as NSString
str = nsStr as String

多行字符串 ("""):

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

遍历:

for character in "Dog!🐶" {
    print(character)
}
    
for character in [Character]("Dog!🐶") {
    print(character)
}
    
for character in Array("Dog!🐶") {
    print(character)
}

字符转字符串:

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// 打印输出:“Cat!🐱”

String 的获取索引、插入、删除等操作比较繁琐,常转为 NSString 然后去处理:

let str = "Guten Tag!"
str[str.startIndex] 	// G
str[str.index(before: str.endIndex)]		// !
str[str.index(after: str.startIndex)]	// u
let index = str.index(str.startIndex, offsetBy: 7)
str[index]		// a


var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)

let range = welcome.index(welcome.endIndex, offsetBy: -2)..<welcome.endIndex
welcome.removeSubrange(range)

5.6、值类型和引用类型

Swift 中结构体和枚举是值类型,类(class)是引用类型。

Swift 中所有的基本类型:整数(Int)、浮点数(Float/Double)、布尔值(Bool)、字符串(String)、数组(Array)和字典(Dictionary),都是值类型,其底层也是使用结构体实现的。

值类型在被赋值给一个变量常量或者被传递给一个函数的时候,传过去的是拷贝后的值

let str0: String = "a"
var str1 = str0
str1 += "b"
print("str0 = \(str0), str1 = \(str1)")
// str0 = a, str1 = ab
引用类型在被赋予到一个变量常量或者被传递到一个函数时,传过去的是内存地址

let nsStr0: NSMutableString = NSMutableString.init(string: "a")
let nsStr1 = nsStr0
nsStr1.append("b")
print("nsStr0 = \(nsStr0), nsStr1 = \(nsStr1)")
// nsStr0 = ab, nsStr1 = ab

5.7、set/get

重写 set/get:

var num: Int {
    get {
        return 0
    }
    set(newNum) {
        print("\(newNum)")
    }
}

简化 Setter 声明,计算属性的 setter 没有定义表示新值的参数名的时候,可以使用默认名称 newValue:

var num: Int {
    get {
        return 0
    }
    set {
        print("\(newValue)")
    }
}

简化 Getter 声明, 在 getter 中忽略 return:

var num: Int {
    get {
        0
    }
    set {
        print("\(newValue)")
    }
}

只读计算属性,只有 getter 没有 setter 的计算属性叫只读计算属性:

// 只读属性 get 的简略写法, 每次都会执行里面的代码
var kTopWindow: UIWindow {
    var window = UIApplication.shared.keyWindow!
    if #available(iOS 13, *) {
        for wScene in UIApplication.shared.connectedScenes where wScene.activationState != UIScene.ActivationState.unattached {
            if let windowScene = wScene as? UIWindowScene, windowScene.windows.count > 0 {
                window = windowScene.windows.last!
                break
            }
        }
    }
    return window
}

5.8、类型转换

检查类型, 用类型检查操作符(is)来检查一个实例是否属于特定子类型:

let num = 10
if num is Int {
    print(num)
}

类型转换操作符(as? 或 as!):

let num = 10
if let newNum = num as? Int {
    print(newNum)
} else {}
    
let newNum = num as! Int

5.9、弱引用(weak)

weak var weakSelf = self
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    guard let strongSelf = weakSelf else {
        return
    }
}

5.10、访问控制

openpublic 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体
通常情况下,可以使用 openpublic 级别来指定框架的外部接口
open 只能作用于类和类的成员,它和 public 的区别主要在于 open 限定的类和成员能够在模块外能被继承和重写

internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问
通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 internal 级别, 也是系统默认的访问级别

fileprivate 限制实体只能在其定义的文件内部访问
如果功能的部分实现细节只需要在文件内使用时,可以使用 fileprivate 来将其隐藏

private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。
如果功能的部分细节只需要在当前作用域内使用时,可以使用 private 来将其隐藏。

5.11、单例

static let sharedInstance: NetWorkSwift = NetWorkSwift()

5.12、GCD

DispatchQueue.main.async {}

DispatchQueue.global().async {}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {}

5.12、闭包

闭包表达式语法:

{ (parameters) -> return type in
    statements
}

普通闭包, 顺序执行,不能延时:

private func p_normal(finish: (_ num: Int) -> Void) {
    finish(10)
}

逃逸闭包, 可以延时:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。

private func p_escaping(finish: @escaping (_ num: Int) -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        finish(20)
    }
    print(30)
}

调用
p_escaping { num in
    print(num)
}
// 先打印 30,再打印 20

5.13、系统版本判断

if #available(iOS 13, *) {}

5.14、面向协议编程

普通协议:

//定义协议
protocol Shakeable {
    func shake()
}

//实现协议
class ShakeView: UIView, Shakeable {
    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.25
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = CGPoint.init(x: self.center.x - 4.0, y: self.center.y)
        animation.toValue = CGPoint.init(x: self.center.x + 4.0, y: self.center.y)
        self.layer.add(animation, forKey: "position")
    }
}

//实现协议
class AnotherShakeView: UIView, Shakeable {
    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = CGPoint.init(x: self.center.x - 4.0, y: self.center.y)
        animation.toValue = CGPoint.init(x: self.center.x + 4.0, y: self.center.y)
        self.layer.add(animation, forKey: "position")
    }
}

面向协议:

//定义协议
protocol Shakeable {}

//实现协议
extension Shakeable where Self : UIView {
    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.25
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = CGPoint.init(x: self.center.x - 4.0, y: self.center.y)
        animation.toValue = CGPoint.init(x: self.center.x + 4.0, y: self.center.y)
        layer.add(animation, forKey: "position")
    }
}

class ShakeView: UIView, Shakeable {}
class AnotherShakeView: UIView, Shakeable {}

6、代码规范检测工具 SwiftLint

github.com/realm/Swift…

www.bbsmax.com/A/xl56GAykd…

使用 cocoapods 引入:

pod 'SwiftLint'

Xcode 设置,在 Build Phases 中新增一个 Run Script:

"${PODS_ROOT}/SwiftLint/swiftlint"

配置自定义规则:

用命令行创建配置文件:
touch .swiftlint.yml

用命令行显示隐藏文件:
defaults write com.apple.finder AppleShowAllFiles -bool true

找到 .swiftlint.yml,开始设置规则:
规则参考:https://github.com/realm/SwiftLint

注意:使用的时候,将.swiftlint.yml 放在需要执行swiftlint工程的根目录中,整个工程会执行.swiftlint.yml的配置;
	 如果在二级目录同样配置了.swiftlint.yml文件,则会执行二级目录下的配置

7、常见第三方

网络类使用 Alamofire、moya

布局 snapkit

json处理引入 SwiftJson、HandyJSON

图片库 Kingfisher

响应式编程 RxSwift

加解密 CryptoSwift

数据库 SQLite.swift、WCDB.swift