[Swift] The Swift Programming Language - 初始化/ARC/可选链/协议/拓展/泛型/运算符

Initialization

swift的init和OC的不太一样,是不用return一个self的~ 它的职责只是确保这个类把所有该做的事情先做了再被使用~

存储属性应该在init里面赋值,或者开始声明的时候就给个default值,这些都是不会触发observer的~

If a property always takes the same initial value, provide a default value rather than setting a value within an initializer.

init是可以加参数的哦~

struct Color {
    var red = 0.0, green = 0.0, blue = 0.0
    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

如果所有属性声明的时候都有default值(除了optional的属性,optional的属性自动默认值是nil),并又没有父类,那么swift会自动生成init方法~

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

对于值类型会有的默认的init方法,但是如果你自己写了init就不会自动生成了哦~

struct SomeStructure {
    var storedTypeProperty : String
    
    init() {
        storedTypeProperty = "hh"
    }
}
// error
SomeStructure(storedTypeProperty: "ggg")

如果你想有自己的init又想要默认的,可以加extension~

struct SomeStructure {
    var storedTypeProperty : String
}

extension SomeStructure {
    init() {
        storedTypeProperty = "hh"
    }
}
SomeStructure(storedTypeProperty: "ggg")
  • 这个引发了我一个思考,如果子类继承父类并且覆写了init,也没初始化父类的参数会怎样呢?
class Counter {
    var count: Int
    
    init() {
        count = 10
    }
}

class Countera : Counter {
    var counta: Int
    override init() {
        counta = 11
    }
}

print("coutera: \(Countera().count)")
// 输出为coutera: 10

所以其实即使你没有调用super.init,好像是自动会在子类init结束后被调用的~ 关于父类子类的self问题还可以看看这个:https://segmentfault.com/q/1010000005807384

子类继承父类的时候,需要确保子类init也有初始化父类的属性,Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value. These are known as designated initializers and convenience initializers.

Designated initializers是类似全能初始化,会init所有属性,并且调用super.init(),Convenience initializers是可以少提供几个参数,调用全能初始化然后有些参数可以用默认的值。

  • Designated initializers must call a designated initializer from their immediate superclass.

  • Convenience initializers must call another initializer available in the same class.

  • Convenience initializers must ultimately end up calling a designated initializer.

初始化init的调用规则

注意调用顺序哦,防止被其他的初始化函数改了之前改过的属性。只有完全初始化完(所有属性有值)以后才可以调用方法之类的~

Unlike subclasses in Objective-C, Swift subclasses do not not inherit their superclass initializers by default.

Designated和Convenience的初始化函数是酱紫写哒:

// Designated
init( parameters ) {
    // statements
}

// Convenience
convenience init( parameters ) {
    // statements
}

如果子类和父类有一个同名的convenience init,并且提供了它比父类多的属性的赋值,那么也就是说父类的其他依赖于这个convenience init的convenience方法都可以做到初始化子类的所有属性,也就自然会被继承,所以可以用这些父类的convenience init来初始化子类,因为会优先调用子类自己实现的convenience初始化方法

当子类有新增的属性并声明时赋值了default值,并且没有自己的init方法,那么会继承父类所有的Designated以及Convenience的初始化方法

属性的default值还可以用closure来定义,初始化的时候会执行这个closure得到默认值,注意这个时候self还没有init完成,不能在closure里面用self的属性和方法

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure // someValue must be of the same type as SomeType return someValue
    }()
}

注意到closure赋值给属性作为default的时候,最后需要加(),因为如果你不加,其实就是定义了一个类型是closure的var,()的含义是调用也就是立即执行。所以这里作为default值需要执行得到真正的默认值。

举个例子:

struct Checkerboard {
    let boardColors: [Bool] = {
        var temporaryBoard = [Bool]()
        var isBlack = false
        for i in 1...10 {
            for j in 1...10 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBoard
    }()
}

Deinitialization

deinit在对象销毁之前调用,并且只有class有这个方法。

Superclass deinitializers are inherited by their subclasses, and the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation. Superclass deinitializers are always called, even if a subclass does not provide its own deinitializer. 父类的销毁函数会自动调用的~ 和dealloc类似~


Automatic Reference Counting

Reference counting only applies to instances of classes. Structures and enumerations are value types, not reference types, and are not stored and passed by reference. 值类型其实没必要引用计数的

swift提供两种解决循环依赖的方式:unowned references以及weak references,如果这个变量有可能是nil就用weak,如果永远不会为nil就用unowned references

weak必须是var不能是let哦,毕竟是会被设为nil的,并且必须是optional的。例如weak var tenant: Person?

unowned reference和weak的区别是,总是会有值的,所以是永远不optional的~ 所以如果用unowned修饰变量,arc是不能把它设为nil的,所以如果它指向的对象dealloc了这个地方和unsafe_retain一样不会置空。如果unowned的对象被释放,然后你再通过unowned指针访问它,就会出现crash

假设两个类分别是CreditCard以及customer,顾客不一定会有信用卡,但是每个信用卡一定会有顾客,所以这里可以用unowned修饰信用卡的主人,因为永远不会是nil,并且还保证了没有循环引用。例如unowned let customer: Customer

在这种时候,如果置空了customer,由于没有人strong持有customer,顾客就会被dealloc,然后也就没有人strong持有credit card了,于是两个就都被回收了~

let修饰的变量如果没default就要在init里面初始化哦,然后如果是下面这种其实是会报错的,因为init city的时候用到了没有初始化完全的city,但这种感觉就很矛盾不太好改的样子0.0 :

class Country {
    let name: String
    let capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    var name: String
    unowned let country: Country
    init (name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Implicitly Unwrapped Optionals:就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果该值是nil,也一样会报错crash掉。

如果属性不是在init而是在viewDidLoad这种里面赋值,但是又确定不会是nil,那么就可以用:(可参考:https://www.jianshu.com/p/d58558944e84

var myLabel: UILabel!  //!相当于下面这种写法的语法糖
var myLabel: ImplicitlyUnwrappedOptional<</span>UILabel>

closure类似block,也会和对象建立循环引用的,包括property的也会哦:

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
}
强引用环

The fact that asHTML is a lazy property means that you can refer to self within the default closure, because the lazy property will not be accessed until after initialization has been completed and self is known to exist.

而且在closure里面的时候,swift强制你使用self.来访问属性,这样也可以提醒自己其实捕获了self

Capture List

Capture List是用来解决closure循环引用的问题哒~ 只要在closure声明的前面增加一个[],然后包裹住捕获的对象即可:

lazy var someClosure1: (Int, String) -> String = {
    [unowned self] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
    return ""
}

===如果closure的type可推测===
lazy var someClosure2: () -> String = {
    [unowned self] in
    // closure body goes here
    return ""
}

unowned来修饰closure里面的self,代表着两者必须同时dealloc;如果对象会想dealloc,closure还持有着self那么就需要用weak啦,因为这个时候如果用unowned再访问已经被销毁的对象会crash


Optional Chaining

?来gracefully解包,而!是强制解包如果是nil会crash的。用optional的变量来做事情即使本来func的return type是non-optional的,最后的结果也是optional的,因为不确定是不是会被调到~ 但是注意不能用?来给optional的对象的属性赋值哦,赋值只能john.residence!.address = johnsAddress

对optional的对象进行方法调用也是类似的,即使方法没有返回值,其实也是返回Void的:

if john.residence?.printNumberOfRooms() {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")
}

john.residence?.printNumberOfRooms()的类型其实是Void?,所以如果调用成功其实会走if的逻辑。如果没有成功那么john.residence?.printNumberOfRooms()的值其实是nil。

访问optional变量的下标的时候也是需要在optional变量后面加问号的f let firstRoomName = john.residence?[0].name


Type Casting

is是用来对象判断是不是某个Class的,如果是就会return true,例如if item is Movie

as是用于向下转型的~ as?返回的是optional的,因为不一定可以成功转型,而as是强制向下转型,如果没有成功转回crash的。

AnyAnyObject可以用于指代任何类型。AnyObject 可以用于转OC里面的object类型,也对应swift里面的对象类型,Any是包含了基本数据类型

如果是AnyObject的数组可以直接整个数组向下转型的:for movie in someObjects as Movie[]

例如case let someInt as Int:,在case的时候比较特殊,强制as转型而非as?是没有问题的,as在这里是check and cast to a specific type.


Extensions

和OC里面的category很类似,区别是Swift的extension没有名字。类似的是,extension也是在define之前创建的对象也可以使用,和OC的category加载类似吧

功能:

  • Add computed properties and computed static properties
  • Define instance methods and type methods
  • Provide new initializers
  • Define subscripts
  • Define and use new nested types
  • Make an existing type conform to a protocol
extension SomeType {
  // new functionality to add to SomeType goes here
}

注意哦,extension可以加计算属性,但是不能加存储属性以及属性observer

extension可以增加convenience initializers,但是不能增加Designated initializersdeinit,这俩必须在原类里面写。


Protocols

protocol SomeProtocol {
  // protocol definition goes here
}

如果是遵循多个protocol,或者有父类都可以列在一起:class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol

在protocol里面对属性声明不会指明是存储属性还是计算属性,但是必须声明type以及setter和getter。

例如:

protocol SomeProtocol {
  var mustBeSettable: Int { get set }
  var doesNotNeedToBeSettable: Int { get }
}

如果是set和get都需要的属性,是不能用let的变量以及只有get的计算属性满足的哦。

在协议里面如果定义的是类的属性,而非对象的属性,不是用static而是用class哦。类方法也是类似的,应该是修饰class,而在实现的时候修饰static

protocol AnotherProtocol {
  class var someTypeProperty: Int { get set }
}

protocol SomeProtocol {
  class func someTypeMethod()
}

在protocol里面声明方法的时候,可以声明参数类型,但是不能声明默认值哦。在protocol里面如果用mutating标记了方法,那么在实现的时候可以不标记mutating~

声明对象遵循某个protocol的时候其实协议就被当做类型了,例如let generator: RandomNumberGenerator

类似category可以给现有的类加protocol,extension也可以拓展现有的类增加遵从的协议,尤其在你无法改这个类的source code的时候

protocol TextRepresentable {
    func asText() -> String
}
extension Dice: TextRepresentable {
    func asText() -> String {
        return "A \(sides)-sided dice"
    }
}

protocol的继承和class是一致的,:protocol InheritingProtocol: SomeProtocol, AnotherProtocol,如果参数是遵循某个协议可以这样来写celebrator: protocol<Named, Aged>

关于POP的一些:https://www.cnblogs.com/guohai-stronger/p/12359303.html

isas可以看对象是不是遵循了某个protocol的~ 但是如果想这么用,这个protocol必须标记为@objc注意哦,@objc是不能给枚举或者struct标记的哦~

如果方法是optional的需要这样标记:(@objc是因为optional的方法必须这样标记,所以protocol也需要标记@objc)

@objc protocol Card {
    @objc optional func refresh()
}

调用protocol的optional方法的时候,不像OC那种需要先看是不是responseTo,只要用someOptionalMethod?(someArgument)这种?调用即可,返回值就也是optional的,例如if let amount = dataSource?.incrementForCount?(count)


Generics

array和dictionary其实都是泛型,因为可以装int也能装string。

假设有这样一个函数来交换整数:

func swapTwoInts(inout a: Int, inout b: Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

那如果我们想交换浮点数呢?再写一个 swapTwoFloats ?但这里又不能写一个通用方法交换两个类型Any的对象,因为如果可以交换类型必须一致的。

可以借助泛型改造这样:

func swapTwoValues<T>( a: inout T, b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

<T>The brackets tell Swift that T is a placeholder type name within the swapTwoValues function definition. Because T is a placeholder, Swift does not look for an actual type called T。这样参数和return type就都可以用T了。

泛型的名字可以起的更清晰一点,例如对dictionary可以取名为KeyTypeValueType

对于class例如array要这么定义:

struct Stack<T> {
    var items = T[]()
    mutating func push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}

创建的时候酱紫:var stackOfStrings = Stack<String>()

如果你要给泛型加限制,比如dictionary的KeyType应该是Hashable的,就可以酱紫func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U),另外例如如果你想比较两个泛型对象,这俩对象的泛型就应该满足Equatable

如果在protocol里面用泛型是酱紫的:

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

使用这个protocol的时候再impl里面typealias ItemType = Int这样来标注ItemType是int类型的~ 如果你不写也可以,因为swift可以自己自动infer出来~

where也可以用于约束泛型例如酱紫:func allItemsMatch<C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>


Advanced Operators

swift里面的overflow默认是会运行时error的,如果你想兼容overflow那么需要用&+来替代+,所有overflow的操作符都以&开头。

例如:

var potentialOverflow = Int16.max
// potentialOverflow equals 32767, which is the largest value an Int16 can hold 
potentialOverflow += 1
// this causes an error

var willOverflow = UInt8.max
// willOverflow equals 255, which is the largest value a UInt8 can hold 
willOverflow = willOverflow &+ 1
// willOverflow is now equal to 0

并且&/&%return a value of zero if you divide by zero~

~取反:0b00001111 -> 11110000
&位与:0b11111100 & 0b00111111 -> 00111100
|位或:0b10110010 | 0b01011110 -> 11111110
^异或: 0b00010100 ^ 0b00000101 -> 00010001
<<左移,<<右移,如果是signed value会保持sign位正负号不变

运算符也是可以重载的~例如:(你还可以定义-或者++==!=神马的)

func + (left: Vector2D, right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335