从 OC 到 Swift 的快速入门与专业实践

96
CoderHG
2018.08.04 14:44* 字数 4351

只会介绍与 OC 有明显区别的地方,不会介绍 OC 中没有的,比如元组。当前总结也只是蜻蜓点水而已,但是有 OC 的基础,看这些已经足够。

一、数据

Swift 是类型安全的语言:

  1. Swift 必须明确数据类型
  2. 如果取值错误会直接报错
  3. Swift 会进行溢出检查 (OC 也会检查)
  4. Swift 没有隐式类型转换, 不允许不同类型的数据类型进行运算

1.1 简单使用

Swift 很接近脚本语言,尤其是在数据类型方面。定义数据类型只允许使用 letvarlet 标识的是常量, var 标识的是变量。那么问题来了:那应该如何使用?在 Swift 中有可变类型么?

let name = "CoderHG"
print(name)

上面简单的定义了一个 name,如果没有看到后面具体的值,根本就不知道 name 是一个字符串类型。但是打断点查看,name 就是一个 String 类型的数据。将上面的代码中的 let 换成 var,会发现在上面的使用上没有任何的区别, 能正常使用。

var name = "CoderHG"
print(name)

但是还是有区别的,上面已经介绍 let 标识的是常量, var 标识的是变量。尽然是变量,可否将一个数字类型的 2 赋值给 name 呢?答案肯定是不可以的。在 Swift 中的定义,必须在定义的那一刻就要决定其数据类型。所以下面的这种定义是错误的:

var name
print(name)

这样的话,Xcode 是会直接报错的,因为在定义的时候没有指明 name 是什么类型。那么问题又来了,如何定义一个字符串,而又不希望有初始值呢?

var name: String
name = "CoderHG"
print(name)

这样,name 就是一个字符串类型的了。那么又出先了一个新问题,我可否这样定义:

let name: String
name = "CoderHG"
print(name)

使用 OC 的套路来思考上面的代码,肯定是不行的,因为一个常量只可能在定义的那一刻赋值,以后都是能读取其值,即为 只读。但是在 Swift 中有点不一样,Swift 在意的是第一次赋值,而不是定义时。所以上面的代码是没有问题的,但是如果再次给 name 赋值,那么肯定就出错了。

接下来主要介绍一下:在 Swift 中的可变类型。,在 OC 中一般使用 NSMutable¥ 来表示一个可变类型,那么在 Swift 中如何表示呢?其实 var 不仅代表一个变量,也代表着 OC 中的可变性。比如,可以这么使用:

var name = "CoderHG"
name.append(", Very GOOD!")
print(name)

如果换成 let 肯定是不行的。

上面简单的介绍了一下 letvar 的简单用法与注意事项。

看到这里,是否会不由自主的想到 OC 中这样的代码:

id obj = [[NSObject alloc] init];
   obj = [[HGPerson alloc] init];

然后 Swift 中也来了这么一段:

var obj = NSObject()
// var obj:HGPerson = NSObject() as! HGPerson
print(obj)
obj = HGPerson()
print(obj)

以上两段代码说明在 Swift 中的 var 也有 OCid 的影子,所以在 Swift 中做类型检测也是很有必要的。所以在 Swift 中会经常看类似这样的代码:

var obj = NSObject()
print(obj)
obj = HGPerson()
print(obj)

let person = obj as! HGPerson
print(person)

在上面用到了一个类型转换的标识 as!,在 Swift 中的全部类型转换标识,如下:

  1. is : 用于判断一个实例是否是某一种类型
  2. as : 将实例转成某一种类型 (比如子类->父类转换)
  3. as?:将某个类型转成可选类型,通过判断可选类型是否有值,来决定是否转化成功
  4. as!: 将某个类型转成具体的类型,但是注意:如果不是该类型,那么程序会崩溃

1.2 数据类型

OC 中的数据类型主要分成两种:基本数据类型与对象类型,在 Swift 中也一样。但是在 Swift 中最为常见的是 结构体(基本数据类型),比如 StringInt8

public struct String
public struct Int8 : FixedWidthInteger, SignedInteger

OC 中字符串是对象类型,数字是基本数据类型(NSNumber 除外)。当然这些结构体类型的数据,都是可以无缝衔接到对象类型,比如 NSString,一般使用 String 就能满足很多的场景。

1.3 可选与非可选数据类型

Swift 中,一个变量没有 默认值 这种说法。一个变量要么是有值、要么没有值,这就叫做 可选类型Swift 中的可选类型,是一种单独的数据类型。有可选类型,那么就有非可选类型。
关于这部分,前不久在简书上简单的总结了一下,可以参考 对 Swift 中可选类型的理解

有值与没值、是两种状态,而不是两种具体的值。

1.4 结构体

先看一个简单的结构体:

// 定义一个结构体
struct HGStruct {
    var name:String?
    var des:String?
    
    func desFunc() -> Void {
        print(name! + "_" + des!)
    }
}

// 可以这样使用:
// 无参构造函数
var st = HGStruct()
// 逐一构造函数
st = HGStruct(name: "HG", des: "Good")
// 调用结构体函数
st.desFunc()

对于一个结构体来说,只要是有属性,系统默认有两个构造函数,一个是无参构造函数,一个是 逐一构造函数

逐一构造函数: 将所有的属性作为参数的构造函数。

构造函数:不用 func 作为修饰,函数名统一为 init

以上的两种构造函数是自动生成的,也可以自定义构造函数。比如:

// 自定义构造函数
init(name:String) {
    self.name = name
    des = "Good!"
}

自定义的构造函数有一个明显的特点,不需要加 func 关键字。还有一个特点是:一旦自定义了构造函数,那么自动生成的构造函数都将失效。
这里有一个方法可以做到构造函数的随意组合,就是重写 逐一构造函数,将所有的参数都弄一个默认值。如下:

// 重写 逐一构造函数
init(name:String = "", des:String = "") {
    self.name = name
    self.des = des
}

关于结构体,也是属于基本数据类型,是 类型,是不能直接在结构体内部直接修改其 属性 的值的。比如:

// 更新名字
func update(name:String) -> Void {
    self.name = name
}

这样是会直接报错的,必须在 func 的前面加一个关键字 mutating。如:

// 更新名字
mutating func update(name:String) -> Void {
    self.name = name
}

到这里,关于 Swift 中结构体的使用介绍,基本差不多了。在 Swift 的实际开发中,结构体的使用也是比较频繁的。由上面的介绍可以知道,功能也比 OC 中的多,主要的原因是有 函数。在上面的代码中也能看在,也有 self 关键字,使用方式与 Class 几乎一致。所以在一些轻量级的场合,可以直接选择使用结构体。

1.5 枚举

简单的定义:

// 枚举定义
enum HGEnum {
    case go
    case back
}

可以这样的使用:

{
    direction(d: HGEnum.go)
}

func direction(d:HGEnum) -> Void {
    switch d {
    case .go:
        print("go")
    case .back:
        print("back")
        
    }
}

从上面也能看出获取枚举的方式,HGEnum.go.go 是同一个,但是要保证只有这个枚举有这个 go,否则出错。

关于枚举的值

直接这样打印:

print(HGEnum.go)

发现打印结果是:go,枚举值仅仅是一个符号, 不代表任何类型。如果想要绑定原始值, 必须指明枚举的类型,比如:

// 枚举定义
enum HGEnum:String {
    case go   = "go_value"
    case back = "back_value"
}

一旦指明了枚举的类型,在使用上没有区别,可以使用 rawValue 获取具体的值:

print(HGEnum.go.rawValue)  // go_value
print(HGEnum.go)           // go

枚举有能定义函数

func enumFunc() {
    print(self.rawValue, "_哈哈哈哈哈")
}

代用方式:

HGEnum.go.enumFunc()
HGEnum.back.enumFunc()

二、方法到函数

Swift 中就没有方法这种叫法了,统一称函数。函数定义的模板如下:

func 函数名(参数列表) -> 返回值类型 { 
    代码块 
    return 返回值 
} 

对于函数这一块,没有什么特别的。这里有一个规律,就是 Swift 函数转成 OC 方法的时候,是这样的:

// Swift 函数
func hello(name:String, from:String) -> String {
    return "你好!我是 " + name + ", 来自于 " + from
}

// 转成 OC 是这样的 
// - (NSString*)helloWithName:(NSString*)name from:(NSString*)from;

上面代码的亮点是函数名与方法名,是有规律的。这也给我们一个在 OC 中方法命名规范的提醒:第一个参数以 With 做拼接,并首字母大写,其它参数前的方法名部分直接使用参数的名称。当然,规范仅仅是一个规范而已,苹果的 API 也并非全部按照这样的规范,比如:

// tap
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // TODO: 待处理
}

关于 函数 这一块,相对 OC 来说有以下两个明显的不同:

  1. 函数中可以定义函数,这个功能在 OC 也有类似的,就是方法中定义 Block。
  2. 函数重载,这个在 OC 中是实现不了的。

三、类

OC 中有三种 Class:BlockNSProxyNSObject。据我现在所知,在 Swift 中没有了 Block,但是有了一种闭包的东西。除此之外,在 Swift 中的 Class 可以不用继承任何的基类。
OC 中即使是一个简单的数据模型都需要继承于 NSObeject,显得有些重量级。但是在实际上还是有很多区别的。

3.1 简单的定义

有两个致命的规律:

  1. 定义的 Class 一定要有属性,否则直接报错
  2. 创建一个类的实例, 必须在创建之后, 里面所有的非可选属性必须有值,否则报错
class HGPerson {
    
}

没有任何属性,直接报错。

class HGPerson {
    var name:String
}

name 为非可选,创建实例之后 name 没有值,直接报错。

class HGPerson {
    var name:String
    // var name:String = ""
    // 构造函数
    init() {
        name = ""
    }
}

重写构造函数,非可选属性 name 默认有值。每个 Class 都会有一个默认的无参构造函数,一旦有重写,默认构造函数将失效。

在使用上,与 OC 中的几乎完全一样。

3.2 特殊方法

在一个 Class 中,我们往往比较在乎的是一个实例的生命周期。总之一句话:生于构造函数,毁于虚构函数
构造函数:一个特殊的函数,与结构体中的一样。不用 func 作为修饰,函数名统一为 init
虚构函数:实例销毁时系统调用的函数 deinit,功能与 OC 中的 dealloc 一样。

3.3 setter 与 getter 方法

这里的 settergetter 方法,和 OC 中的还有点不一样。比如:

var doSomething:String {
    set {
        // setter
        
    }
    
    get {
        // getter
        return ""
    }
}

这里需要注意一点,在 Swift 中的只读属性,将上面的 set 去掉,就是只读属性的。

3.4 属性监听

var sex:String = "" {
    willSet(newValue) {
        print("当前的值 = " + self.sex + ",新值 = " + newValue)
    }
    didSet(oldValue) {
        print("当前的值 = " + self.sex + ",之前的值 = " + oldValue)
    }
}

这里要注意一个问题:在构造函数中的 setter 方法是不会被监听到的。

3.3 注意事项

Swift 中的 Class 是可以没有基类的。

四、协议(代理)

4.1 简单使用

定义

/// MARK 定义一个代理
protocol DetailDelegate: NSObjectProtocol {
    // 从控制器返回 content 内容
    func detail(vc:DelegateDetailController?, content:String?)
}

关键字:protocolNSObjectProtocol。这里需要注意一点的是,在 Swift 中的协议也是可以没有基类的,在 OC 中也一样,但是一般都是继承于 NSObject 协议。在 Swift 中有以下三种情况:

  1. 没有继承,这种情况只能使用在没有继承 NSObjectClass 中,不能使用 weak 修饰,毕竟 weak 只能修饰 Class
  2. 继承于 class,这种情况可用于所有的 Class
  3. 继承于 NSObjectProtocol,这种情况可用于所有的 Class。与第2中的区别是,这个协议自带了很多的系统协议。所以继承于这种协议的不推荐使用在没有继承于 NSObjectClass 中,因为 在 Swift 中的所有协议函数都是强制必须要实现的

综上,继承于 class一般使用在没有继承于 NSObjectClass 中,而继承于 NSObjectProtocol一般使用于继承于 NSObjectClass 中。没有继承的使用在结构体与枚举中,这个就很厉害了,在上面的结构体与枚举中就知道,这两种数据结构也是可以定义函数的,所以有这样的的协议场景也是很相当合理的。

delegate变量

// 定义个代理变量
weak var delegate:DetailDelegate?

OC 一致,需要弱化。

执行

let cell = tableView.cellForRow(at: indexPath)
delegate?.detail(vc: self, content: cell?.textLabel?.text)

4.2 协议函数可选

上面已经提到 在 Swift 中的所有协议函数都是强制必须要实现的,熟知在 OC 中是可以实现可选,即 @optional@required 之分。那么问题来了:OC 与 Swift 是可以相互使用的,那么在 Swift 中如何给 OC 提供一个可选的函数呢?详情可以参考最后部分的第一小节。
最终会发现,提供给 OC 的可选函数,在 Swift 中也是可选的。

五、泛型

5.1 函数中使用泛型

// 函数中使用泛型
func num<T>(a:T) -> T {
    return a
}

// 使用
let var_int = num(a: 1)
let var_double = num(a:1.1)
// 打印
print(var_int)
print(var_double)

那么问题来了:模板与函数重载有何异同?这两个东西还是挺相似的。

5.2 协议中使用泛形型

// 协议中使用泛形型
protocol Animal {
    associatedtype T
    func run() -> T
    func music() -> T
}

class Person: Animal {
    
    func run() -> Person {
        print("Person - run")
        return self
    }
    
    func music() -> Person {
        print("Person - music")
        return self
    }
}

class Pig: Animal {
    
    func run() -> Pig {
        print("Pig - run")
        return self
    }
    
    func music() -> Pig {
        print("Pig - music")
        return self
    }
}

// 使用
let p = Person()
p.run().music()

let pig = Pig()
pig.run().music()

5.3 指定泛型的类型

// 指定泛型的类型
class BigPig: Pig {
}

func funDefine<T>(a:T) where T:Pig {
}

// 直接报错
//funDefine(a: Person())
// 正确
funDefine(a: BigPig())

六、闭包

6.1 闭包为何物

闭包就是一个函数,又称匿名函数。看到闭包,肯定会联想到 OC 中的 Block,OC 中的 Block 从本质上看依然是一个对象,核心的功能依旧是一个函数。
回归正题:闭包是一个函数,但是这个函数肯定不同于普通函数。

写到这里,很想多唠叨唠叨: 很多的时候,同一个东西于不同的叫法,必定有一定的道理。就如同 OC 中的方法,其本质也是函数,但是方法在使用上以及提现形式上还是有所不同的。有的时候,有的小伙伴就喜欢把 OC 中的方法叫函数,听起来极为别扭。就好比 他是一个动物他是一个人 的区别。

6.2 简单使用

6.2.1 简单使用(一)

// 闭包的简单使用
func funcBody(a:Int, b:Int, block:(Int, Int)->(Int)) -> Int {
    return block(a, b)
}

// 加法运算
func sum(a:Int, b:Int) -> Int {
    let c = a + b
    return c
}
// 减法运算
//func sub(a:Int, b:Int) -> Int {
//    let c = a - b
//    return c
//}
// 或者
let sub:(Int, Int) -> Int = {
    (a, b) -> Int in
    return a - b
}

// 加法
var value = funcBody(a: 5, b: 2, block: sum)
print(value)
// 结果为 7

// 减法
value = funcBody(a: 5, b: 2, block: sub)
print(value)
// 结果为 3

// 乘法
value = funcBody(a: 5, b: 2, block: { (aa, bb) -> (Int) in
    return aa * bb
})
print(value)
// 结果为 10

6.2.2 简单使用(二)

尾随闭包

// 除法
value = funcBody(a: 15, b: 3) { (aa, bb) -> (Int) in
    return aa/bb
}
print(value)
// 结果为 5

6.2.2 简单使用(三)

逃逸闭包

// 逃逸闭包
func taoyi(block:@escaping ()->Void) -> Void {
    // 这里与 OC 中的 Block 不相同
    // 在 OC 中, Block 中使用对象,会在 Block 对象中自动生成一个对应对应的引用(强引用/若引用)
    // 但是在 Swift 中却不能
    // 故一旦在 Swift 中,闭包中使用闭包的情况下, 想要延长其被引用的闭包, 那么需要将此闭包声明成逃逸闭包, 即添加关键词 @escaping
    let q = DispatchQueue(label: "label")
    let time = DispatchTime.now() + DispatchTimeInterval.seconds(2)
    q.asyncAfter(deadline: time) {
        block()
    }
    
    // 那么问题来了, 如何确定需要逃逸闭包:
    // 1. Xcode 会自动提示
    // 2. 像上面的例子, asyncAfter函数的中的闭包已经加有 @escaping 关键字
}
//调用闭包函数
taoyi {
    print("逃逸闭包的使用")
}

6.3 解决循环引用

// 定义一个 Class
class WeakPerson {
    var block:((Int)->())?
    func test() {
        // weak var weakSelf = self
        // unowned let weakSelf = self
        block = {
            /** [weak self] */[unowned self] (a) in
            print(self, a)
            // print(weakSelf ?? "")
        }
        block!(9)
    }
    
    deinit {
        print("释放了", self)
    }
}

var wp = WeakPerson()
wp.test()

wp = WeakPerson()

由以上代码可知,解决的方案有如下4种:

// weak var weakSelf = self
// unowned let weakSelf = self
// [weak self]
// [unowned self]

unowned 相当于 OC 中的 __unsafe_unretained

七、控制器中的代码布局

这里说的控制器代码布局,仅仅是一个例子,只要是 Class 都是同样的套路
OC 中,默认的代码结构是这样的:

#import "HomeController.h"

@interface HomeController ()

@end

@implementation HomeController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}


@end

核心的代码都是写在 @implementation@end 之间,如果要将其中的功能分开,只能是通过 分类 或者直接另建文件。在 Swift 中,默认的结构是这样的:

class HomeController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

全部的代码,都是写在第一个大括号中。但是可以借助 extension 来做分割:

/// 系统相关函数实现
class HomeController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

/// 登录 相关的函数实现
extension HomeController {
    
}

/// 叫车 相关的函数实现
extension HomeController {
    
}

/// UITableViewDelegate 的协议函数
extension HomeController: UITableViewDelegate {
    
}

/// UITableViewDataSource 的协议函数
extension HomeController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 45;
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "id")
        
        return cell!
    }
}

/// HGObjectDelegate 协议函数
extension HomeController: HGObjectDelegate {
    
}

以上是一个简单的分割,在实际的开发中,可能没有这么简单,毕竟实际的项目的代码更加复杂。
上面简单的代码中可以看到可以通过 extension 做针对性的分离。

八、@objc

@objc 这个关键组合,作用是在 Swift 中实现,在 OC 中使用。

8.1 协议中使用

在协议中,会看到这个关键词。在上面的介绍中,Swift 中的协议一旦被遵循,那所有的函数都必须是先实现的,没有可选函数这一说。换成 OC 的说法就是必选的。在 OC 中没有实现必选方法是报警告,在 Swift 中是直接报错。那么问题来了:在 OC 中是有可选协议方法的,如果这个协议是在 Swift 中实现,应该如何处理呢?

  1. OC 中如何使用 Swift 中的协议?
  2. 如何在 Swift 中给 OC 提供可选协议函数?

一个简单的例子如下:

@objc
protocol HGObjectDelegate {
    // 可选的协议方法
    @objc optional func optionalFunc()
}

@objc 代表可以在 OC 中使用,optionalOC 中是可选的协议方法。

现在看在 HGObjectDelegate 没有任何的集成,相当于在 OC 中没有继承 NSObject 一样。但是可以直接使用与 OC 中的所有 Class 中。在 Swift 中,这个协议是不能使用在没有继承的 Class 中的。

8.2 函数中使用

Swift 实现的函数,是可以很好的转换成 OC 方法的,一般不使用转换,其实在上面也已经有介绍。但是 Swift 中的函数与 OC 中的方法还是有所差异的,比如在 Swift 中有重载,然而。。。。这种情况就需要 @objc 做一下转换。比如以下的代码:

@objc(sumIntWithA:b:)
func sum(a:Int, b:Int) -> Int {
    print("Int")
    return a+b;
}

@objc(sumDoubleWithA:b:)
func sum(a:Double, b:Double) -> Double {
    print("Double")
    return a+b
}

一看就懂,无需介绍。
OC 中这么使用:

HGObject* obj = [HGObject new];
NSInteger int_Result = [obj sumIntWithA:9 b:4];
float doble_Result = [obj sumDoubleWithA:3.2 b:2.3];
NSLog(@"%zd %f", int_Result, doble_Result);

Swift 中这么使用:

let obj = HGObject();
let sum1 = obj.sum(a: 1, b: 2)
let sum2 = obj.sum(a: 2.3, b: 2.5)
print(sum1, sum2)

其它介绍

1、懒加载

在 OC 中的懒加载,就是 getter 方法的代名词,然而在 Swift 中有特定的关键字(lazy)来标识:

// 懒加载
lazy var name:String = {
    return "CoderHG"
}()

但是两者有一个极为不同的点是:Swift 中的懒加载只会调用一次,然而在 OC 中如果当前属性对应的成员变量的值为具体某个值(一般都是空值)时,将会重新赋值。

2、链式编程

链式编程,在 OC 中的代表作是 Masory。在 Swift 中的链式编程,其中的一种形式:

// 模仿 Masory
class CalculatorMaker {
    var result:Int = 0
    func sum(a:Int) -> CalculatorMaker {
        result += a;
        return self
    }
    func sub(a:Int) -> CalculatorMaker {
        result -= a;
        return self
    }
    
}
class Calculator {
    static func start(block:(CalculatorMaker) -> ()) -> Int {
        let cal = CalculatorMaker()
        block(cal)
        return cal.result
    }
}

let s = Calculator.start { (maker) in
    maker.sum(a: 2).sub(a: 3).sum(a: 5)
}
print(s)
// 结果: 4

其实在上面的 Pig 类中也用到了

let pig = Pig()
pig.run().music()
Swift之恋
Gupao