Swift基础语法

简介

  • 特点
    (1)优于OC,快速,安全
    (2)取消了预编译指令包括宏定义(OC用的太多了)
    (3)取消了OC指针和不安全访问的使用(看不到星星了)
    (4)舍弃 Objective-C 早期应用 Smalltalk 的语法,全面改为点语法
    (5)3.0中对Foundation框架做了很多改变,去除了NS,将绝大部分class转换成struct结构体(为了考虑性能和安全性,绝大部分使用结构体来代替以前的类,但是在实际使用感觉不到)
    (6)可以使用现有的 Cocoa 和 Cocoa Touch 框架
    , 以前是OC调UIKit,现在就是Swift调UIKit,这点事没问题的
    (7)Swift因为语法的严谨性可以让很多错误提前遇到,这样很少出现bug让程序停在main导致无法找到
    (8)@UIApplicationMain是程序的入口
    (9)只有.h没有.m
    (10)所有的代码都包括在{}里,默认方法func都有缩进!
    (11)语法的allocinit替换成()

  • PlayGround
    (1)可以看苹果官方自带的tips和100个tips,都在Placground来使用

  • 基础点
    (1)不适用self. 在闭包或者编译器提示的时候再使用
    (2)分号是用来分割语句的,如果一行洗很多,就可以加分号,一般时候可以不加
    (3)#function打印执行的函数
    (4)添加标记用到// MARK: - 选择,如果是接下来要做的可以用// TODO:和// FIXME:这些非常有用

一、常量和变量

定义

  • let 定义常量,一经赋值不允许再修改

  • var 定义变量,赋值之后仍然可以修改
    常量和变量的细节

    • 使用 : 类型,仅仅只定义类型
    • 常量有一次设置的机会
    • 应该尽量先选择常量,只有在必须修改时,才需要修改为 var
// 定义常量并且直接设置数值
let x: Int = 10
// 常量数值一经设置,不能修改,以下代码会报错
// x = 30

let y: Int
// 常量有一次设置的机会,以下代码没有问题,因为 `y` 还没有被设置数值
y = 10

// 一旦设置之后,则不能再次修改,以下代码会报错,因为 `y` 已经被设置了数值
// y = 50

print(x + y)

// 变量设置数值之后,可以继续修改数值
var z: Int
z = 100
z = 200

print(x + y + z)

自动推导

Swift 能够根据右边的代码,推导出变量的准确类型
只有相同类型的值才能够进行运算

// 整数默认的类型是 Int
let intValue = 200
// 小数的默认类型是 Double
let doubleValue = 10.5

// 如果要对不同类型的数据进行计算,必须要显式的转换
print(intValue + Int(doubleValue))
print(Double(intValue) + doubleValue)

注意:Swift对类型要求异常严格,任何不同类型的数据不能直接运算(哪怕是Int和Double),不会做一些自动的转换来转换成Double。Swift不存在基本数据类型,Int和Double都是结构体其实,强转用Double(x)完成,或者在定义的时候直接指定变量的类型let x : Double = 10;(很少使用)

二、控制流

if

  • Swift 中没有 C 语言中的非零即真概念
  • 在逻辑判断时必须显示地指明具体的判断条件 true / false
  • if 语句条件的 () 可以省略
  • 但是 {} 不能省略
let num = 100

if num > 10 {
    print("大 \(num)")
} else {
    print("小 \(num)")
}

三目

  • Swift 中的 三目 运算保持了和 OC 一致的风格
  • 提示:以下代码,可以按 cmd + shift + y,打开控制台,查看输出结果
num > 10 ? print("大 \(num)") : print("小 \(num)")

或者

num > 10 ? print("大 \(num)")  : () 
这样就对后面的不作处理。
() 表示空执行。

switch

  • switch 不再局限于整数
  • switch 可以针对任意数据类型进行判断
  • 每一个 case 后面必须有可以执行的语句
  • 不再强制需要 break
  • 如果没有任何需要执行的语句,可以使用 break
    要保证处理所有可能的情况,不然编译器直接报错,不处理的条件可以放在 default 分支中
  • 每一个 case 中定义的变量仅在当前 case 中有效,而 OC 中需要使用 {}
  • 多值 case 可以使用 , 分隔
let scoreString = "优"

switch scoreString {
case "优":
    let name = "学生"
    print(name + "80~100分")
case "良", "中": print("60~80分")
case "差": print("不及格")
default: break
}

switch 的条件判断

  • switch 中可以使用 where 子句判断条件
  • 如果只做条件判断,case 部分可以使用 _ 省略
  • 提示:Swift 中可以使用 _ 忽略任何不关心的内容
let score = 90
switch score {
case _ where score >= 80: print("优")
case _ where score >= 70: print("良")
case _ where score >= 60: print("中")
default: print("差")
}

for

  • swift取消了i++和++i和传统的for循环
for i in 0...5 {

}

for i in 0..<5 {

}
  • 反序遍历
 for i in (0..<10).reversed() {
        
}

三、可选项(Optional)

简要

(1)定义变量时,如果是可选的,表示可以有值,也可以是nil,用“?”
(2)强行解包 “!”,程序员来注意!,并且要少用,可能会崩
(3)最常见的错误:解包的时候发现nil。fatal error: unexpectedly found nil while unwrapping an Optional value
(4)let可选的话,没有默认值,需要赋值。var可选的话,默认值为nil
(5)可选项在参与计算时候必须解包

概念

  • Optional 是 Swift 的一大特色,也是 Swift 初学者最容易困惑的问题
  • 定义变量时,在类型后面添加一个 ?,表示该变量是可选的
  • 定义变量时,如果指定是可选的,表示该变量:
    • 可以有一个指定类型的值
    • 也可以是 nil

定义

  • 格式1(自动推导):var 变量名: Optional = 值
  • 格式2(泛型):var 变量名:Optional<类型> = 值
  • 格式3(简化格式):var 变量名: 类型? = 值
// 格式1
let x: Optional = 20
// 格式2
let y: Optional<Int> = 30
// 格式3
let z: Int? = 10

print(x)
print(y)
print(z)

默认值

  • 变量可选项的默认值是 nil
  • 常量可选项没有默认值,需要在定义时,或者构造函数中赋值
var x1: Int?
print(x1)

let x2: Int?
// 常量可选项没有默认值,在赋值之前不能使用
// print(x2)
x2 = 100
print(x2)

计算和强行解包

  • 可选值在参与计算前,必须解包 unwarping
  • 只有解包(unwrap)后才能参与计算
  • 在可选项后添加一个 !,表示强行解包
    • 如果有值,会取值,然后参与后续计算
    • 如果为 nil,强行解包会导致崩溃
print(x! + y! + z!)

程序员要对每一个 ! 负责

可选解包

  • 如果只是调用可选项的函数,而不需要参与计算,可以使用可选解包
  • 在可选项后,使用 ? 然后再调用函数
  • 使用可选解包可以:
    • 如果有值,会取值,然后执行后续函数
    • 如果为 nil,不会执行任何函数
var optionValue: Int?
print(optionValue?.description)
// 输出 nil

optionValue = 10
print(optionValue?.description)
// 输出 Optional("10")

与强行解包对比,可选解包更安全,但是只能用于函数调用,而不能用于计算

Paste_Image.png

可选项判断

不强行解包的方法

由于可选项的值可能为 nil,不允许直接参与运算,因此在实际开发中,经常需要判断可选项是否有值。

如果单纯使用 if,会让代码嵌套层次很深,不宜阅读和维护,为了解决这一问题,苹果提供了以下三种方式:

  • ??
  • if let / var
  • guard let / var

??

  • ?? 运算符可以用于判断可选项是否为 nil,如果是则使用后面的值替代

注意:?? 的优先级低,在使用时,应该注意使用 ()

let x: Int? = 10
let y: Int? = 100

print((x ?? 0) + (y ?? 0))

if let / var

  • 使用 if let,一旦进入 if 分支,表示可选项一定有值
  • 常量/变量的作用域仅在 {} 内部
  • 使用 , 可以判断多个可选项是否有值
  • 使用同名常量/变量,避免再次起名的烦恼
  • 如果要在分支逻辑中修改值,可以使用 var,通常使用 let
let name: String? = "Mike"
let age: Int? = 18

if let name = name,
    let age = age {
    print("\(name) 今年 \(age) 岁")
} else {
    print("姓名或者年龄为 nil")
}

guard let / var

  • guard 是与 if let 相反的语法,Swift 2.0 推出
  • guard 同样可以判断可选项是否有值
  • 多值判断使用 , 分隔
  • 如果发现 nil,在 else 分支返回
  • 分支结束后,所有的 常量/变量 都有值
  • 相比较 if let,guard 语法的分支结构能节省一层
  • 如果要在后续逻辑中修改值,可以使用 var,通常使用 let
func demo(name: String?, age: Int?) {

    guard let name = name,
        let age = age else {
            print("姓名或者年龄为 nil")

            return
    }

    print("\(name) 今年 \(age) 岁")
}

demo(name: name, age: age)

guard let和if let可以用同名变量接收。

因为总会取名字,if let name = name这样就可以,注意后面使用的时候用非空的那个!并且iflet和guardlet可以依次判断,先判断是一个字典,再拿字典的数组,在判断数组的值,可以一条线判断出来。

四、字符串

用String,是一个结构体,具有绝大多数NSString功能,支持直接遍历

(1)遍历:

func demo3() {
    
    // 字符串遍历(NSString不支持这么遍历)
    let str = "wowosnshi是"
    
    for s in str.characters {
        print(s)
    }
}

(2)长度:

// 返回指定编码对应的字节数,每个汉字三个字节
print(str.lengthOfBytes(using: .utf8))

// 返回真正字符串长度
print(str.characters.count)

(3)拼接:要注意可选项拼接不解决会带上Optional,剩下的都可以拼接,再也不用看StringWithFormat了

let name = "AA"

let age = 19

let title : String? = "sss"

print("\(name)\(age)\(title ?? "")")

(4)格式化:

  • 格式化成日期
let h = 8 , m = 10, s = 44

// OC中用stringWithFormat格式化日期,Swift中可以

let strDate = String(format: "%02d-%02d-%02d", h,m,s)

print(strDate)

(5)截取字符串:建议用NSStrin作中转,因为swift取串方法一直在改变

  • NSString方法
let str = "红红火火恍恍惚惚"

let strOC = str as NSString

strOC .substring(to: 1)

strOC.substring(with: NSMakeRange(0, 2))

五、数组

(1)就是中括号,注意数组的类型,并且基本数据类型不需要包装,可以直接方数组里,如果类型不一样(混合数组,但是基本不用),自动推导[NSObject]。在Swift中还有一个[AnyObject类型],标示任意对象,因为在Swift中一个类可以没有任何父类。

(2)遍历:

    // 遍历1(按照下标遍历)
    for i in 0..<array.count {
        
        
    }
    
    // 遍历2(遍历元素)
    for s in array {
        
             
    }
    
    // 遍历3(同时遍历下标和元素)
    for e in array.enumerated() {
        
        // let e: (offset: Int, element: String) e是一个元组        
        print("\(e.offset), \(e.element)")
        
    }
    
    // 遍历4(同时遍历下标和元素)
    for (n,s) in array.enumerated() {
        
        print("\(n),\(s)")
        
    }
    
    // 反序遍历
    for s in array.reversed() {
        
                
    }
        
    // 反序索引下标(这样写才对,先枚举再反序)
    for (n,s) in array.enumerated().reversed() {
        
        
    }

(3)增删改:

    array.append("AA")
    
    array[1] = "BBB"
    
    array.remove(at: 2)

(4)合并:用“+”号。但是要合并的数组的两个类型必须一致。

六、字典

一般是[String:NSObject],对应键值对.由于3.0后大部分都是结构体了,AnyObject不好用了,Any范围更大
(1)字典数组:
(2)增删改:和数组都类似,就是两个字典合并不像数组直接相加,而是需要遍历

七、函数

(1)外部参数,当外部参数用_替代的时候,会在外部调用的时候忽略形参名

(2)函数的默认值(OC不具备),这个使Swift比OC灵活很多很多,一个方法可以做很多事,因为OC会有各种参数和组合,Swift只需写一个最多的参数,然后不需要的设定默认值就是了

(3)无返回值 :直接省略 () Void都可以

(4)闭包:类似Block,比Block还广泛。OC中Block是匿名函数,Swift中函数是特殊的闭包。闭包在整个开发中和Block的应用场景一样。用于控制器/自定义视图/异步执行完成的回调。这些回调的特点就是都是以参数回调处理结果,返回值为Void。

  • 定义:
   let biBao = { (x: Int) -> Int in
            
       return x + 100
   }
        
   print(biBao(10))
  • GCD:将任务添加到队列,指定执行任务的函数。任务就是Block/闭包,队列以同步/异步的方式执行。

```      
    func loadData(compeletion:@escaping ( _ result: [String])->()) -> Void {
        
        DispatchQueue.global().async {
            
            print("耗时操作会获得一些结果 (Thread.current)")
            
            Thread.sleep(forTimeInterval: 1.0)
            
            let json = ["天气","不错","刮大风"]
            
            // 主线程回调
            DispatchQueue.main.async(execute: {
                
                print("主线程更新UI (Thread.current)")
                
                // 回调 -> 通过参数传递 执行闭包
                compeletion(json)
            })
        }
   
    }


调用:

// 执行的适合我就拿到了值
        loadData { (result) in
            
            print("获取的新闻数据 (result)")
        }



* 尾随闭包:如果函数的最后一个参数是闭包,那么参数就省略了,最后一个参数直接{}大括号包装

* 闭包的循环引用:

![方法1](//upload-images.jianshu.io/upload_images/1868951-ba4dbd3e7430bd1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        

![方法2](//upload-images.jianshu.io/upload_images/1868951-37bff51c1a762d9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(5)面向对象(各种构造函数):()就是allocInit,在Swift中对应init()。在swift中一个项目所有类都是共享的,可以直接访问,每一个类都默认有一个命名空间。A.name B.name God.name Dog.name。同一个类可以从属于不同的命名空间(假如有一个框架有Person类,做用户,还有一个框架做后台,也用Person。在OC中就只能靠前缀解决,HouTaiPerson,KuangJiaPerson。而Swift中的命名空间就是项目名。AAA项目有一个Person,那么AAA.Person就是AAA的Person类,此时再导入框架,那也是框架的.Person)
![](//upload-images.jianshu.io/upload_images/1868951-fe35f0c10b0f7e2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
* 在自定义的Nsobjiect类中,has no initalizers 标示没有初始化器,初始化器可以有多个,默认是init。当这个类有属性的时候,属性要分配内存空间,就是说要有初始值。那么其实就是先给自己的属性分配,然后给父初始。其实这么一看,Swift和OC是相反的!

![](//upload-images.jianshu.io/upload_images/1868951-440dc9822b651550.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

思路:OC是先调用爸爸。就是Person,Person会再调用NSObject,就是先跑上去什么都不管,先初始化了NSObject,然后才往下走挨个初始化。Swift是把自己完全初始化,再上去初始化爸爸,这么看比OC快了一圈,性能要好。

![](//upload-images.jianshu.io/upload_images/1868951-32291fce27beef6a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

* 重载构造函数:(重写是父类有这个方法,override。重载是函数名相同,参数和个数不同。init就重写,init+参数就重载。OC是没有重载的!都是initWithXXXXX)。重载其实是最基本的方式,OC没有其实很low,但是Swift有。

![](//upload-images.jianshu.io/upload_images/1868951-0beee0b1eba57574.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注意:如果重载了构造函数并且没有实现父类的init,那么系统不再提供init构造函数了(默认是有的),因为默认的构造函数不能给本类的属性分配空间(你不自己写name = ,系统就没办法分配)
 
* KVC构造函数:只需记住下面4点
![](//upload-images.jianshu.io/upload_images/1868951-104ce1b663f76f78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**所以一般在模型中加个? 然后用KVC实现(先调用init因为是运行时机制)**

* 模型中属性定义:基本数据类型 = 0,对象设置?
运行时中,基本类型设置?    属性设置私有都会让运行时拿不到,此时kvc就会出错。
![](//upload-images.jianshu.io/upload_images/1868951-0e8c14816279da28.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* 如果子类没有重写父类方法,调用的时候就会直接调用父类的方法。当继承一个类,就继承所有属性和方法,包括KVC。当PERSON写好了KVC后,
* 整体
![](//upload-images.jianshu.io/upload_images/1868951-722a51e523596920.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
(6)便利构造函数:关键字Convenience(开发用的很少,因为模型都是框架转,UI不需要便利)
*目的:条件判断,只有满足条件才实例化对象,防止不必要的内存开销,简化对象创建。本身是不负责属性的创建和初始化的。
 
![](//upload-images.jianshu.io/upload_images/1868951-f0dc301dfbbf6fb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 
  
(7)deinit:类似OC的Dealloc
![](//upload-images.jianshu.io/upload_images/1868951-562bd1efec2cfa04.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



### 八、分类:extension
便利构造函数 + 分类可以省略抽取很多代码。例如给UITextField/UIButton写分类,然后写便利构造函数,方便。

### 九、Swift的类,结构体,枚举三种都有构造函数,都可以有方法,就像OC的类



### 十、其它

####  懒加载:
在OC开发中,懒加载一般自定义控件。在Swift中,懒加载还是需要用的,可以保证控件延迟创建,还能避免处理控件解包。如果直接定义控件var label = UILabel,根据代码从上到下,会让控件在ViewDidLad之前就提前创建了。所以需要懒加载。OC中懒加载就是Get方法,Swift直接lazy var。当然也可以private lazy var来限定作用域。

(1)简单的懒加载:
![](//upload-images.jianshu.io/upload_images/1868951-018c8333a67c1f19.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(2)完整的懒加载:()就是函数执行,就是一个特殊的闭包,所以懒加载本质是一个闭包。一般不这么写。
![](//upload-images.jianshu.io/upload_images/1868951-203b83d0a055460e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  
(3)OC和Swift区别
 
* OC:


  • (UILabel *)label{
    //如果label等于nil就会创建!
    if (_label == nil) {
    _label = [[UILabel alloc]init];
    _label.text = @"loirou";
    [_label sizeToFit];
    _label.center = self.view.center;
    }
    return _label;
    }
OC是等于nil时候就懒加载

[self.view addSubview:self.label];
//释放label
_label = nil;

//会再次调用懒加载的代码
NSLog(@"%@",self.label);

当label设nil的时候就在此调用。在ios6中,didReceiveMemoryWarning是不清理视图的。
 
 
* Swift:

![](//upload-images.jianshu.io/upload_images/1868951-4754ad62e9252aff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
此时释放的时候就会报错。因为定义的时候没有?,就是一定有值得。
那么如果定义时候加? 一旦label = nil,也不会在执行懒加载了!因为懒加载根本没写如果是空就创建。
懒加载只会在第一次调用的时候执行闭包。Swift中一定注意不要主动清理视图或控件,因为懒加载不会创建了(例如内存警告别干掉控件,干掉了在也用不成了,因为懒加载就一次)


#### 计算型属性(只读):
(1)getter/setter(开发不用):
 
// 开发一般不用,还给搞一个_name。
// swift一般不会重写getter和setter
private var _name: String? // 假装的一个值
var name: String? { get{return _name}  set{_name = newValue}} // get返回成员变量 set记录成员变量

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

}


(2)计算型属性:readOnly只读:OC中重写get。Swift也是重写get,不管set就可以。
 

// 只读,此时set赋值时候就会报错
var name: String? { get{return "ABC"}}
// 还有一种简写:
var name: String? { return "ABC"}

看这类属性,是本身不保存内容的,都是通过计算获得结果。就可以当我就是个没有参数只有返回值的函数!!我每次return值给你我的任务就完成了。每次你调用我这个属性的时候,我都会进行一次计算!都会执行我的代码然后return给你。我自身不存储的。
 
(3)懒加载和计算型属性的区别:
![](//upload-images.jianshu.io/upload_images/1868951-3d568e0dedf0e0a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

(4)存储型属性:需要开辟空间,存储数据。一般的属性都是存储型属性(懒加载)

(5)存储和计算都可以?或者不加。看情况是不是必选

(四)Swift中设置模型数据:
Swift做好模型后。别的控件拿到模型后,由视图自己来显示。此时在didSet里写。就是替代OC的Setter方法。(OC的Setter要考虑_成员变量 = 值,而且如果是copy需要.copy,而Swift不需要考虑一切)


#### 命名空间:
* 在同一个空间(项目),全局共享。用第三方时,如果直接拖拽,那就从属于一个空间,很有可能冲突,所以用Cocopod
* 动态获得命名空间(从info.plist加载),命名空间和项目名称有关系。info的Bundle name其实就是命名空间(一般写的很奇怪 #(ProdectNmae))。
打印info
print(Bundle.main.infoDictionary)
 ![](//upload-images.jianshu.io/upload_images/1868951-f0870f71a52c609a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

赋值

// 获取命名空间的值,可选 let str =
Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""                 
let con = NSClassFromString(str + "." + "ViewController") as? UIViewController.Type

#### 反射机制:
对于任何类都可以知道类的所有属性和方法,对于任何对象都可以调用任何属性和方法,这种动态获取的信息和动态调用对象属性方法的功能成java的反射机制(Swift也有了)

(1)在OC中利用反射机制
  - 利用**NSClassFromString**方法来使用字符串获取类
  - 利用**isMemberOfClass**判断是否是某一个类
  - 利用**isKindOfClass**判断是否是某一个类的子类
  - 利用**conformsToProtocol**判断对象是否遵守某一个协议
  - 利用**respondsToSelector**判断是否实现了某一个方法
  - 利用**performSelector**或者**objc_msgSend**间接调用方法

(2)在Swift中利用反射机制类似。工作中用的很多很多。
场景:AppDelegate(OC中也用过,利用NSClassFromString获得类,然后设置根控制器。但是Swift中多了一个命名空间写法。)
 

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

window = UIWindow(frame: UIScreen.main.bounds)

// 依据String名字拿到控制器(添加项目名称,命名空间,不能有数字和特殊符号)
// 返回的是AnyClass? 需要as?强转
// 控制器添加Type类型
let rootControl = NSClassFromString("SwiftyDemo.ViewController") as? UIViewController.Type

let vc = rootControl?.init()

window?.rootViewController = vc

window?.makeKeyAndVisible()

return true

}

 
(3)第三方框架,用了很多反射机制和工厂方法,为了实现大量的解耦和封装,很麻烦。一个方法可能跳10个方法10个控制器才写了一个加法。但是如果涉及高级开发和封装,必须要经过这一步。
posted @ 2017-06-27 22:08  洛洛爱吃肉  阅读(1079)  评论(0编辑  收藏  举报