移动开发作家群(719776724)分享专题iOS开发iOS开发攻城狮的集散地

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

2018-08-04  本文已影响521人  CoderHG

只会介绍与 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)

五、控制器中的代码布局

这里说的控制器代码布局,仅仅是一个例子,只要是 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 中使用。

6.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 中的。

6.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)
上一篇 下一篇

猜你喜欢

热点阅读