iOS笔记

// binary 2进制
// octonary 8进制
// decimal  10进制
// hexadecimal 16进制

ios重要资源

iOS抢红包软件

多target

倒计时

- 注意 在工程文件夹如果是有两个相同类,没有导入也会报重复定义错误

static

一.修饰局部变量:
1)让局部变量只初始化一次;
2)局部变量在程序中只有一份内存;
3)并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)。
二.修饰全局变量:
全局变量的作用域仅限于当前文件。

http://www.cnblogs.com/allencelee/p/7169071.html

ios 博客

学英语

Xcode技巧

swift中文

OC

copy 与 mutableCopy

集合类对象是指NSArray、NSDictionary、NSSet ...之类的对象。

非集合类对象是指NSString、NSNumber ...之类的对象。

  • 非集合对象
    • 对于不可变对象copy,浅拷贝,mutableCopy深复制。
    • 对于可变对象mutableCopy,copy,深拷贝。
  • 集合对象
    • 对于不可变对象copy,浅拷贝,mutableCopy深拷贝
    • 对于可变集合对象,无论是mutableCopy或者Copy都是深拷贝

strong 和 Copy的区别。要把NSString类型字符串赋值时,两者没区别。NSMutableString,strong会跟着变化,Copy做了深拷贝不会变
strong与copy区别

weak 和 assign的区别

  • assign指针所指向的内存被释放掉(释放不等于抹除,只是引用计数为0),不会自动赋值nil,这样再引用self.assignPoint就会导致野指针操作,如果这个操作发生时内存还没有改变内容,依旧可以输出正确的结果,而如果发生时内存内容被改变了,就会crash。

分类

    1. 分类只能增加方法,不能增加成员变量(添加成员变量使用会崩溃)
  • 2.分类中写property只会生成方法声明

  • 3.分类可以访问原来类中的成员变量

  • 4.如果分类的方法与原有类的方法同名,主函数在执行的话会优先分类的方法,原来类中的方法会被忽略.主要看方法调用的优先级.一般式分类-原来类-父类.(可以自己调节)(一般在开发中我们不推荐)

总结:分类的属性只是set、get方法申明,并没有成员变量的赋值,而继承类的属性会自动有成员变量的赋值

property (属性)

用property声明的成员属性,相当于生成了setter getter方法,重写了set和get方法,与@property生命的成员属性就不是一个成员属性了,是另外一个实例变量,而这个实例变量需要手动声明。

是同时重写了set和get方法后就与@property声明的成员属性不是同一个属性了

类声明
@interface ViewController : UIViewController

@property (copy, nonatomic) NSString *last;

@end


类实现
@implementation ViewController
// 一定要合成,否则set get找不到成员变量
@synthesize last = _last;

- (NSString *)last {
    return _last;
}

- (void)setLast:(NSString *)last {
    _last = last;
}

多线程 GCD

1.任务

任务就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

  • 同步执行:只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步执行:可以在新的线程中执行任务,具备开启新线程的能力。

2.队列

这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并行队列。

  • 并行队列(concurrent dispatch queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)
    • 并行功能只有在异步(dispatch_async)函数下才有效
  • 串行队列:(serial dispatch queue)让任务一个接着一个执行(一个任务执行完毕后,再执行下一个任务)

copy 与 mutablCopy

原对象 拷贝方法 副本对象类型 是否产生新对象 拷贝类型
NSArray copy NSArray NO 浅拷贝
NSArray mutableCopy NSMutableArray YES 深拷贝
NSMutableArray copy NSArray YES 深拷贝
NSMutableArray mutableCopy NSMutableArray YES 深拷贝

swift

  • let 声明的不能有计算属性

  • 重写了set方法必须重写get方法

  • 重写了set、get方法不能有初始值

  • willSet、didSet不能和set、get同时使用

  • 重写属性观察器(willSet、didSet)可以有初始值,不写必须有init方法初始化

  • 函数类型格式必须 (参数类型) -> Void, 前面的参数一定要有括号包裹住

      protocol Aprotocol {
         mutating func abc()
      }
    
      class Pclass:Aprotocol {
          var a: Int = 0
          func abc() { // 加上mutating会报错
             a = 10
          }
       }
    
      struct Csturct:Aprotocol {
          var a: Int = 0
          mutating  func abc() {
              a = 10
         }  
      }
    
    

nil

  • 1.swift的nil和OC的nil并不一样。在OC中,nil是一个指向不存在对象的指针。
  • 2.在swift中,nil不是指针,它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被置为nil,不只是对象类型。
// Int型置为nil
var a: Int? = 10
a = nil

// Bool型置为nil
var b: Bool? = true
b = nil

类型别名

typealias  INT = Int  // 定义了Int的别名 INT
let a : INT = 4
// 等效于c语言
typedef char Char; // 定义了char的别名Char

字符串

 // 把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。
let a = 3
let b = 4
let str = "I have \(a) apples"
let str1 = "I have \(a + b) oranges"


var str = "hwww"
str = "abc"
print(str.count) // 字符串长度

let a = "abc"
let b = "ddd"

// 字符串拼接 \(变量名)
let c = "\(a)\(b)"
print(c)
let min = 3
let sec = 4

// 字符串拼接
let tStr = String(format: "%02d:%02d", min,sec)
print(tStr)


数组

// 方式1
//let array:Array  = ["abc"]
// 方式2
let array:[String]  = ["a","b"]
print(array)
// 方式3
var arr = Array  ()
// 任意类型
// var arr = [Any]()
arr.append("abc")
arr.append("abc")
print(arr)
arr.remove(at: 0)
print(arr)
// 如果arr为空数组,则只能在第0个位置插入数据,否则超出范围奔溃
arr.insert("hhh",at:0)
// 下标和元素
for (index,item) in arr.enumerated() {
    print(index,item)
}

let arr1 = ["abc","bbb"]
let arr2 = ["ds","dss"]
// 相同元素可以合并,不同元素不能合并
let arr3 = arr1 + arr2
print(arr3)

字典

// 字典使用

// 使用
let dict:[String:Any] = ["a":"b","age":14]

var dicM = [String:Any]()

print(dict,dicM)

// 增加元素
dicM["name"] = "abc"
dicM.updateValue(123, forKey: "name")
dicM.updateValue("abc", forKey: "ab")

print(dicM)
for value in dicM.values {
    print(value)
}
for (key,value) in dicM {
    print(key,value)
}

元组

// 元组
// 方式1
let tuple1 = ("abc",12)

// 方式2
let tuple2 = (name:"1",age:2,h:232)
print(tuple2.age,tuple2.name)

// 方式3
let (name,age,height) = ("abf",33,32)

print(name,age,height)

可选类型

// 方式1
var name: Optional  = nil
// 方式2
var name1:String? = nil // 简化方式1 等价的

// 可选赋值1
name = Optional("aaa")
// 可选赋值2
name1 = "a"
print(name,name1)

// name! 强制解包

可选绑定

var name: String?
// 可选绑定(固定格式)
if let name = name {
    print(name)
}
如果 name包含一个值,创建一个叫做 name 的新常量并将可选包含的值赋给它。
如果转换成功,name 常量可以在 if 语句中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。

as 用法

// 将一种类型转换成另一种类型 as后接类型
let str:String = "abc"
let nsStr = str as NSString

// as? 转成可选类型
// as! 转成具体类型

let dict:[String:Any] = ["age":12,"name":"xiao"]

let tempName = dict["name"]

let name = tempName as? String

if let name = name { // 可选绑定
    print(name)
}

// 可选绑定直接直接转
if let name = dict["name"] as? String {
    print(name)
}


for in 遍历

// 只遍历不需要第几次可以用'_' 过滤
for _ in 0...3 {
    // 打印四次,0和3之间不能使用空格,必须不间断
}

for i in 0..<3 {
    // 打印3次
}

函数


func test() -> Void {
    print("test")

}
// 等同于(没有返回值可以省略->Void)
func test1() {
  print("test")
}
注意
严格上来说,虽然没有返回值被定义,test1 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。

// 提供一个默认参数
func test2(_ a: Int = 10) {
  print(a)
}
test2() // 不传参
test2(2) // 传参

// 多参数
func sum(numbers: Int...) -> Int {
    var s = 0
    for num in numbers {
        s += num
    }
    return s
}
// 注意 一个函数最多只能拥有一个可变参数
sum(10,20,30)

// 修改外部变量 
// 函数参数默认是常量,inout不能修饰可变参数
// 输入输出  inout修饰  不能有默认值
func swap(a: inout Int, b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var aaa = 10
var bbb = 20
print(aaa,bbb)
swap(&aaa, &bbb)
print(aaa,bbb)


// 函数类型
func test3(_ a: Int, _ b: Int) -> Int {
    return a + b
}
// 类型为 (Int, Int) -> Int  解读为该函数类型为两个Int型的参数并返回一个Int型的值
func test4() {
}
// 类型为 () -> Void   解读为一个没有参数,也没用返回值的函数

使用函数类型 
let f: (Int, Int) -> Int = test3
f(10,20)


//  函数类型作为参数
func test5(_ myFunc:(Int, Int) -> Int, a: Int) -> Int {
    print(myFunc(10,20))
    return 1
}
print(test5(test3, a: 3))

// 函数类型作为返回类型

func step1(_ a: Int) -> Int {
    return 1
}
func step2(_ a: Int) -> Int {
    return 2
}
func chooseFunc(isTrue: Bool) -> (Int)->Int {
    return isTrue ? step1 : step2
}

let newF = chooseFunc(isTrue: true)
print("newFunc",newF(1))

// 嵌套函数
func multipleFunc(newF: Bool) -> (Int)->Int {
    func step1(a: Int) -> Int {
        return a + 10
    }
    func step2(a: Int) -> Int {
        return a + 20
    }
    return newF ? step1 : step2
}
let aaaF = multipleFunc(newF: false)
print(aaF(10))

泛型函数

泛型函数可以适用于任何类型

// func swap(_ a: inout A, _ b: inout A) -> Void {}
func swap(_ a: inout T, _ b: inout T) -> Void {
   let temp = a
    a = b
    b = temp
}

// 多个泛型
func someFunc(someT: T, someU: U) {}

所有的 Swift 标准类型自动支持 Equatable 协议。
func findIndex(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}


协议

协议总是用var关键字(不能用let)声明变量属性,在类型声明后加上{set get}来表示属性可读可写。
protocol  SomeProtocol {
    var abc: Int {set get}
}

* 实现协议中方法时,若是类类型,则不用写mutating关键字。对于结构体和枚举,则不需写mutating.

类类型专属协议
添加class关键字来限制协议只能被类类型遵循,而机构体或枚举不能遵循该协议。class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前。

protocol BaseProtocol {
  func have()
}

protocol SomeProtocol:class,BaseProtocol {
  func run()
 }

协议合成

有时需要遵循多个协议,可以采用 someProtocol & AnotherProtocol这样的格式进行组合,称为协议合成。可以任意多个你想要遵循的协议用符合(&)分隔。

protocol Name {
    var name: String {get}
}
protocol Age {
    var age: Int {get}
}

class Person: Name,Age {
    var name: String
    var age: Int
    init(_ name: String, _ age: Int) {
        self.name = name
        self.age = age
    }
}

func  info(info: Name & Age) {
    print (info.name,info.age)
}

let p = Person("xiao", 10)

info(info:p)


检查协议一致性
使用is 和as 操作符来检查协议一致性。
is 用来检查实例是否符合某个协议,若符合则返回 true,否则返回 false。
as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil。
as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。

protocol HasAra {
    var area: Double {get}
}

class Circle: HasAra {
    var area: Double
    var pi: Double = 0
    init(area: Double) {
        self.area = area
    }
}

class Country: HasAra {
    var area: Double
    var gews = 0
    init(area: Double) {
        self.area = area
    }
}

class Animal {
    var area: Double
    init(area: Double) {
        self.area = area
    }
}

let ani = Animal(area: 10)
let cir = Circle(area: 20)
let cou = Country(area: 30)

let objs:[AnyObject] = [ani,cir,cou]

for obj in objs {
    // 向下转换类型 返回的类型必定是HasAra类型
    if let a = obj as? HasAra  {
        print(a.area) // 只能是HasAra类型,只有一个属性
    }
    // 不能 let c = obj as! HasAra 会失败
     if obj is HasAra {
        print(1111111)
    }
}

可选协议要求
1 在协议中使用optional关键字为前缀来定义可选要求。
2 协议和可选条件都必须带上@objc属性。
3 协议只能被类继承,结构体和枚举都不能遵循这种协议

@objc protocol Aprotocol {
    @objc optional func run()
    @objc optional var name: String {get}
}

协议扩展
协议可以通过扩展来为遵循协议的类型提供属性、方法及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而不需要在每个遵循协议的类型中都重复同样的实现。

protocol Aprotocol {
      func run()
      var name: String {get}
}

extension Aprotocol {
    func run() {
        print("run")
    }
    var name: String  {
        return ""
    }
}

class Per:Aprotocol {
}

class Dog {
    var delegate: Aprotocol?
    func k_Pring(){
        delegate?.run()
        print(delegate?.name)
    }
}
let per = Per()
let dog = Dog()

dog.delegate = per
dog.k_Pring()

枚举

enum MType {
    case get
    case post
    case put
    case delete
}
// 创建枚举
let type:MType = MType.get

// 给枚举赋值
enum NumType : Int{
    case east = 1
    case west = 2
    case north = 3
    case south = 4
}
enum StrType : String {
    case A = "ab"
    case B = "bc"
    case C = "dd"
}

// 直接定义
enum TestType {
    case a,b,c,d,e
}

定义变量

格式

var 变量名: 类型 = 值

var a:Int = 12
var b:Bool = true

  • 类型转换
let a : Double = 20.5
let b : Int = 10
// 类型后接括号
let c = Double(b) + a

guard使用

// 使用方式
// 1 guard一般用在函数 
// 2 必须要有else语句

// 跟if 一样的判断
//    guard 条件判断 else {
//        return
//    }
let a = 20

func online() {

    guard a > 10 else {
        // 不满足条件 return
        return
    }
    // 满足条件 
}

switch

// case default 必须后面跟随一条语句,可以是一个打印或者接break
let a = 10
switch a {
case 10,12,14: // 同时匹配多个用逗号隔开
    print("a")
    fallthrough // 穿透到下一次
case 11:
    print(1)
    fallthrough // 继续穿透到下一次
default:
    print("a")
}

结构体

// 1 定义结构体
struct Location {
    // 成员属性
    var x : Double
    var y : Double
    var z : Double
    // 方法
    func test() {
        print("结构体方法")
    }
    // 改变成员属性,必须加上mutating(变化)
    // 如果不想在调用方法提示参数名可以在前面加上 _
    mutating func add(_ newValue:Double) {
        x = newValue
        print(x)
    }
    // 3 系统会为每一个结构体提供一个默认的构造函数,并且该构造函数要去给每一个成员属性进行赋值
    // 构造函数以 init开头,并且不需要返回值
    // 在构造函数结束时,必须保证所有的成员属性有被初始化
//    // 系统默认实现
//    init(x:Double,y:Double,z:Double) {
//        self.x = x
//        self.y = y
//        self.z = z
//    }
    // 扩充构造函数
    init(xyzStr:String) {
        let array = xyzStr.components(separatedBy: ",")
        let item1 = array[0]
        let item2 = array[1]
        let item3 = array[2]
        
        // 先判断前面可选类型是否有值解包 否则使用后面的值
        x = Double(item1) ?? 0
        y = Double(item2) ?? 0
        z = Double(item3) ?? 0
    }
    
}
// 2 创建结构体

// 2 创建结构体
// 注意 如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行。(这种行为是由于结构体属于值类型,当值类型的实例被声明为常量的时候,它的所有属性也就成了常量) 
// 补充:引用类型不一样,把一个引用类型的实例赋值给一个常量后,仍然可以修改实例的变量属性。

// 使用系统构造函数
//var center = Location(x: 20, y: 30, z: 40)
var center = Location.init(xyzStr: "1,2,3")
center.test()
center.add(10)
print(center.x)



struct Point {
    var x = 0, y = 0
}
struct Size {
    var w = 0, h = 0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
            get {
            let centx = origin.x + 20
            let centy = origin.y + 20
            return Point(x: centx, y: centy)
        }
        // 可以省略newValue,系统默认了一个newValue
        set(newValue) {
            origin.x = newValue.x - 10
            print(newValue.x)
            origin.y = newValue.y - 10
        }
    }
   var readOnlyPoint:Point {
        // 如果只有只读属性可以省略掉get和花括号 直接返回
        return Point(x:10,y:10)
    }
}
var square = Rect(origin: Point(x: 10, y: 10), size: Size(w: 10, h: 10))

square.center = Point(x: 20, y: 20)

print(square.center)

lazy (延迟存储属性)

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用lazy来标识一个延迟存储属性。
注意:
1、必须将延迟存储属性声明为变量(var),因为属性的初始值可能再实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

2、 如果一个被标记为lazy的属性在没有初始化就同时被多个线程访问,则无法保证该属性只会被初始化一次。

下标

下标可以定义在类、结构体和枚举中,是访问集合,列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个Array实例中的元素可以写作someArray[index],访问Dictionary实例中的元素可以写作someDictionary[key]。

一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。

下标语法
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用subscript关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:

subscript(index: Int) -> Int {
    get {
      // 返回一个适当的 Int 类型的值
    }
    set(newValue) {
      // 执行适当的赋值操作
    }
}

属性观察器

// 属性观察期监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察期,即使新值和当前值相同的时候也不例外。
// willSet 在新的值被设置之前调用
// didSet 在新的值被设置之后立即调用
// willSet 观察器会将新的属性值作为常量参数传入,在 willSet 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue 表示。
// 同样,didSet 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 oldValue。如果在  didSet 方法中再次对该属性赋值,那么新值会覆盖旧的值。

class Calculate {
    var totol:Int = 0 {
        willSet(newValue) {
            print(newValue)
        }
        didSet(oldValue) {
            if totol > oldValue {
                print(totol,oldValue)
                totol = 10
            }
        }
    }
}

let s = Calculate()
s.totol = 1
print(s.totol)

定义类



class Person : NSObject{
    var name: String = "" {
        willSet(nw) {
            print(name)
        }
        didSet {
            print(name)
        }
    }
    // 类属性
    static var Count : Double?
    // 自己定义构造函数防止覆盖系统的构造函数重写
    override init() {
        
    }
    init(dict:[NSString:Any]) {
//        if let name = dict["name"] as? String {
//            self.name = name
//        }
        super.init()
        setValuesForKeys(dict as [String : Any])
        
    }

}

格式
// 可以不继承
class 类名 : SuperClass {
     // 定义属性和方法
}


class Student : NSObject {
    var age : Int = 0;
    var name : String?
    var money : Double = 0.0    
}

// 使用类
let stu = Student() // 创建对象
stu.age = 10;
stu.name = "who"
stu.money = 9999.9

重写 (override)

class Animal {
// 只读属性
    var descri: String  {
        return ""
    }
    func makeNoise()  {
        print("父类方法")
    }
}

class Person: Animal {
    override func makeNoise() {
        print(123)
        super.makeNoise()
    }
//你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
// 父类属性为只读,重写可以可写可读
    override var descri: String {
        set {
            
        }
        get {
          return super.descri + "124"
        }
    }
}


// 注意
如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过super.someProperty来返回继承来的值,其中someProperty是你要重写的属性的名字。

注意
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供willSet或didSet实现是不恰当。
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。

有属性观察器就不能有set get方法 只能选其中一个

// 防止重写
你可以通过把方法,属性或下标标记为final来防止它们被重写,只需要在声明关键字前加上final修饰符即可(例如:final var,final func,final class func,以及final subscript)。
如果你重写了带有final标记的方法,属性或下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。
你可以通过在关键字class前添加final修饰符(final class)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。

类型检查(is)

用类型检查操作符(is)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回  true,否则返回 false。
//  变量名 is  类型
let a = 10
if a is Int {
 print(true)
} 

扩展 (extension)

语法:
extension 类型 {
  
}

注意:
1.扩展可以为一个类型添加新功能,但不能重写已有的功能。
2.如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
3. 扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。

? !的使用

声明变量时在类型后添加?或者!,就是告诉编译器这是一个Optional的变量,如果没有初始化,你就将其初始化位nil。

var a: String?  // a位nil
var b: String?  // b为nil

声明变量时用?只是单纯的告诉Swift这是Optional,如果没有初始化就默认为nil,而通过!,则之后对该变量操作的时候都会隐式的在操作前添加一个!。

  • 问号?
    • 声明时添加?,告诉编译器这个是Optional,如果声明时没有手动初始化,就自动初始化为nil。
    • 在对变量值操作前添加?,判断如果变量是nil,则不响应后面的方法。
var a: String? // 声明时加? 初始化为nil
var b: String = "aa"
a = b?
  • 叹号!
  • 声明时添加!,告诉编译器这个是Optional,并且之后对该变量操作的时候,都隐式的在操作前添加!。
  • 在对变量操作前添加!,表示默认为非nil,直接解包进行处理。

绘制画线教程

你可能感兴趣的:(iOS笔记)