今天看啥  ›  专栏  ›  一颗生菜

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

一颗生菜  · 掘金  · 运营  · 2018-08-10 08:17

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

一、数据

Swift 是类型安全的语言:

  1. Swift 必须明确数据类型

  2. 如果取值错误会直接报错

  3. Swift 会进行溢出检查 (OC 也会检查)

  4. Swift 没有隐式类型转换, 不允许不同类型的数据类型进行运算

1.1 简单使用

Swift 很接近脚本语言,尤其是在数据类型方面。定义数据类型只允许使用 let 与 var,let 标识的是常量, 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 肯定是不行的。

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

看到这里,是否会不由自主的想到 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 也有 OC 中 id 的影子,所以在 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 中最为常见的是 结构体(基本数据类型),比如 String 与 Int8:


                                                

                                                    
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: 待处理

                                
}</uitouch *>

            

            

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

  1. 函数中可以定义函数,这个功能在 OC 也有类似的,就是方法中定义 Block。

  2. 函数重载,这个在 OC 中是实现不了的。

三、类

在 OC 中有三种 Class:Block,NSProxy 与 NSObject。据我现在所知,在 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 方法

这里的 setter 与 getter 方法,和 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?)

                                
}

        

        

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

  1. 没有继承,这种情况只能使用在没有继承 NSObject 的 Class 中,不能使用 weak 修饰,毕竟 weak 只能修饰 Class。

  2. 继承于 class,这种情况可用于所有的 Class。

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

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

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 中使用,optional 在 OC 中是可选的协议方法。

现在看在 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)

    

    

作者:CoderHG

链接:https://www.jianshu.com/p/dcf208268caf




原文地址:访问原文地址
快照地址: 访问文章快照