SwiftTips之Language&API

Posted DCSnail-蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SwiftTips之Language&API相关的知识,希望对你有一定的参考价值。

令你极度舒适的Swift集合类高阶函数之后,把很久之前Swift知识进行了梳理并总结成文。这些Swift知识点大多是一些细节,容易忽略但使用效果又极佳,其中包括语言基础、内存、指针、OC差异、优雅奇点、开发环境等方面。其中包含一部分总结了喵神书中的点,特此注明。

if case let

首先,一个用到数据绑定的switch语法是这样的。

let someTuple = (66, 99)
switch someTuple 
case (100, _):
    print("左侧100, 不在乎右侧")
case (66, let right):
    print("左侧66, 右侧\\(right)")
case let (left, 88):
    print("左侧\\(left), 右侧为88")
case let (left, right):
    print("其它: \\(left) + \\(right)")

if case let其实和switch是有关系的,正如下面代码中的,两种表达式的效果是相同的。所以if case let其实就相当于switch case let的逻辑分支。

switch someTuple 
case let (left, 99):
    print("左侧\\(left), 右侧99")
default:
    print("右侧非99")


// 与上面的代码效果一致
if case let (left, 99) = someTuple 
    print("左侧\\(left), 右侧99")

除了if case let,类似的表达方式还有guard case let

guard case let (left, 99) = someTuple  else 
    fatalError("右侧非99")

以及for case let

let tupleArray = [(11, 22), (33, 55), (77, 77), (88, 88)]

// 而下面两种表达方式的效果是相同的
for (left, right) in tupleArray 
    print("\\(left) + \\(right)")

for case let (left, right) in tupleArray 
    print("\\(left) + \\(right)")


// 而且还可以配合 where使用
for case let (left, _) in tupleArray where left < 50 
    print("左侧\\(left)小于50")

当然你可以说这种写法很鸡肋,因为没有必要多敲两个代码。但它的真正价值在于你使用有关联值的枚举时,这并不是元组可以代替的。

enum BirthDay 
    case time(year: Int, month: Int, day: Int)


let birth1 = BirthDay.time(year: 2000, month: 1, day: 2)
let birth2 = BirthDay.time(year: 2010, month: 5, day: 20)
let birth3 = BirthDay.time(year: 2020, month: 10, day: 30)
let birthArr = [birth1, birth2, birth3]

for case let BirthDay.time(_, month, day) in birthArr where month <= 6 
    print("他是前半年的生日\\(month)\\(day)日")

== 和 ===

==表示值相同。值类型和引用类型都可以比较。

let string1 = "string"
let string2 = "string"

string1 == string2			// true

===表示两个引用类型引用自相同的实例,即引用了同一块内存区域。只能比较于引用类型。

let aView = UIView()
let bView = aView

aView == bView		// true
aView === bView		// true

多重可选类型

var optString: String? = "abc"
var aOptString: String?? = optString
var literalOptString: String?? = "abc"

print(aOptString)           // Optional(Optional("string"))
print(literalOptString)     // Optional(Optional("string"))

有值的多重可选类型aOptStringliteralOptString是等效的。

var optNil: String? = nil
var aOptNil: String?? = optNil
var literalOptNil: String?? = nil

print(aOptNil)          // Optional(nil)
print(literalOptNil)    // nil

nil的多重可选类型aOptNilliteralOptNil是不一样的类型。这说明,多重可选类型的分层逻辑还是很严谨的,它能明确定位nil究竟在哪一层。

class和 static的区别

有一个A类,还有一个B类继承于A类。下面代码中介绍了,关于父类和子类中使用staticclass关键字的场景。

class A 
    // class修饰
    class func aClassMethod() 

    class var aClassProperty: String 
        return "a"
    

    //    class var aClassSaveProperty = ""
    // Error: Class stored properties not supported in classes; did you mean 'static'?


    // static修饰
    static func aStaticMethod() 

    static var aStaticProperty: String 
        return "ap"
    


class B: A 
    // class修饰
    override class func aClassMethod() 

    override class var aClassProperty: String 
        return "b"
    

    // static修饰
    // Error: Cannot override static method
    //    static func aStaticMethod() 

    // Error: Cannot override static property
    //    override static var aStaticProperty: String 
    //        return "bp"
    //    

在类中staticclass关键字都可以修饰方法属性, 但有一些本质的不同:

  • 1.应用类型
    static修饰,表示静态方法或静态属性,可以用于所有类型 class,struct,enum。
    class修饰,表示类方法或类属性,只可以用于 class中。
  • 2.属性类型
    static修饰的属性可以是计算属性也可以是存储属性。
    class修饰的属性只能是计算属性。
  • 3.继承重写
    static修饰的类方法和属性是可以继承重写的。
    class修饰的类方法和类属性无法在子类中重写,相当于final class。

.Type、.self、Self

.Type: 当前类的元类型(Meta)。
.self: 静态获取当前类型或者实例的本身(包括classstructenumprotocol)。
Self: 不是一个特定的类型,遵循当前协议的类型或者当前类及其子类。

struct S 
    static var classProperty = ""
    var instanceProperty = ""

protocol P 

.Type.self

我认为下面的例子将.Type.self的区别表述的非常清楚了。

type(of: S())         // S

// S()实例, 其类型是 S
type(of: S().self)    // S

// S类型的本身, 其类型是 S.Type
type(of: S.self)      // S.Type

// S类型的元类, 其类型是 S.Type.Type
type(of: S.Type.self) // S.Type.Type

// P协议的本身, 其类型是 P.Protocol
type(of: P.self)      // P.Protocol

// P协议的元类, 其类型是 P.Type.Protocol
type(of: P.Type.self) // P.Type.Protocol

// intType的值是 Int, 类型是 Int.Type
let intType: Int.Type = Int.self
intType         // Int

在效果上,.self在类型后相当于取得类型本身,在实例后相当于取得这个实例本身。

S.classProperty
S().instanceProperty

在语法上,.self是可以省略不写的,所以下面的代码与上面的效果一致。

S.self.classProperty
S().self.instanceProperty

classForCoder也是一个获取类型的方法,但最好不要使用。它是Foundation框架下的NSObject属性,并不是swift的属性。在Swift开发中,尽可能保持Swift化

UIImageView.classForCoder()			// 不推荐
UIImageView.self					// 推荐

Self

Swift不能在协议中定义泛型进行限制,所以在声明或者实现协议时,Self就可以来代指实现这个协议本身的类型。

protocol SomeProcotol 
    func someFunc() -> Self

另外,用在类中,只可以用作方法的返回值(其他位置不可以使用)。
Self表示当前类及其子类,这里仅限于 class。

class SS 
    func some() -> Self  return self 

SS().some()

swift中动态和静态地获取类型

type(of: someInstance): 动态获取当前实例的类型.
.dynamicType: Deprecated, instead of type(of:)
someInstance.self: 静态获取类型
is: 静态获取类型

class BaseClass 
    class func printClassName() 
        print("BaseClass")
    

class SubClass: BaseClass 
    override class func printClassName() 
        print("SubClass")
    

let someInstance: BaseClass = SubClass()

someInstance是一个指定为BaseClassSubClass实例对象,这在OC中称为多态。

Swift默认情况下是不采用动态派发,而是静态的,所以函数的调用是在编译时期决定。比如is条件语句、.self,都是静态获取类型的。

someInstance is SubClass     		// True
someInstance is BaseClass    		// True
BaseClass.self is BaseClass.Type	// True

获取一个对象的动态类型,可以通过 type(of:)

type(of: someInstance) == SubClass.self      // True
type(of: someInstance) == BaseClass.self     // False

函数嵌套

这里所说的函数嵌套与柯里化中所提到的函数分层调用不是一个概念,而是在函数中继续定义函数。
来看下面一个函数:

func generateObjec(type: Int) -> String 
    if 0 == type 
        return zeroType()
     else if 1 == type 
        return oneType()
     else 
        return defaultType()
    


func zeroType() -> String 
    return "Zero"


func oneType() -> String 
    return "One"


func defaultType() -> String 
    return "Two"

如果使用函数嵌套将会如下效果:

func generateObjec(type: Int) -> String 
    func zeroType() -> String 
        return "Zero"
    
    func oneType() -> String 
        return "One"
    
    func defaultType() -> String 
        return "Two"
    
    
    if 0 == type 
        return zeroType()
     else if 1 == type 
        return oneType()
     else 
        return defaultType()
    

函数嵌套在你函数主体内容过长,且本模块的功能与外部逻辑没有任何关系时,能发挥非常大的作用。它会使你的单个函数不在冗长,且将它们分成几个小型的模块,且定义在主函数之内,并不影响外部的关系。

所以这样的访问权限和这样的模块化会提高代码可读性和维护性。这个Tip在之前文章Swift基础知识碎片中也曾聊过。

观察属性

在类ObserverAObserverB中,观察属性的使用逻辑和重写逻辑如下:

class ObserverA 
    var number :Int 
        get 
            print("get")
            return 1
        
        set 
            print("set")
        
    

class ObserverB: ObserverA 
    override var number: Int 
        willSet 
            print("willSet")
        
        didSet 
            print("didSet")
        
    


let obseverB = ObserverB()
obseverB.number = 0
// 打印顺序:
// get
// willSet
// set
// didSet

总结如下:

  • 1.初始化方法对属性的设定,以及在 willSet和 didSet中对属性的再次设定都不会再次触发属性观察。
  • 2.在 swift中的计算属性只是提供 set和 get两种方法,当你添加 willSet及 didSet方法时会报错。所以在同一个类型中,属性观察和计算属性是不能同时共存的。但我们可以通过继承重写计算属性来实现属性观察的目的。
  • 3.当触发 didSet的时候,会自动触发一次 get,这是因为 didSet中会用到 oldValue,而这个值需要在整个 set动作之前进行获取并存储待用,否则将无法确保正确性。如果我们不实现 didSet的话,那次 get也不会触发。

Protocol的调用逻辑

protocol AProtocol 
    func method1() -> String


extension AProtocol 
    func method1() -> String 
        return "在Protocol中的实现"
    

    func method2() -> String 
        return "在Protocol中的实现"
    


struct AStruct: AProtocol 

struct BStruct: AProtocol 
    func method1() -> String 
        return "在实际类中的实现"
    

    func method2() -> String 
        return "在实际类中的实现"
    

协议方法调用者为实际类型的情况

如果实际类型实现了协议,那么实际类型中协议的实现将被调用。
如果实际类型中没有实现协议,那么协议扩展中的默认实现将被调用。

AStruct().method1()     // 在Protocol中的实现
BStruct().method1()     // 在实际类中的实现

协议方法调用者为被推断为协议类型的情况:

如果方法在协议中进行了声明,且类型中实现了协议,那么类型中的实现将被调用。
如果方法没有在协议中声明,或者在类型中没有实现,那么协议扩展中的默认实现被调用。

BStruct().method1()     // 在实际类中的实现
BStruct().method2()     // 在实际类中的实现

let aProtocol = BStruct() as AProtocol
aProtocol.method1()     // 在实际类中的实现
aProtocol.method2()     // 在Protocol中的实现

Objective-C协议的默认实现

Swift可以在扩展中实现协议,从而进行默认调用,而OC中并没有这种方法。所以,孙源曾经封装了一个库来实现类似功能:ProtocolKit

下标语法

struct House 
    var peoples: [String]

    subscript(index: Int) -> String 
        set 
            peoples[index] = newValue
        
        get 
            return peoples[index]
        
    

    subscript(people: String) -> Int? 
        return peoples.firstIndex(of: people)
    


let peoples = ["Jordan", "Duncan", "YaoMing", "James", "Wade"]
var nbaHouse = House(peoples: peoples)
nbaHouse[1]			// "Duncan"
nbaHouse["James"]	// 3

nbaHouse[2]				// "YaoMing"
nbaHouse[2] = "Iverson"	// "Iverson"
nbaHouse.peoples		// ["Jordan", "Duncan", "Iverson", "James", "Wade"]

减少容器类的类型损失

我们想要不同类型的元素放入一个容器中,比如定义容器中元素类型 Any或者 Anyobject,但这样的转换会造成部分信息的损失。

let mixed: [Any] = [1, "two", true]
let any = mixed[0]

我们想要放入一个容器中的元素或多或少会有某些共同点,这就使得用协议来规定。这种方法虽然也损失了一部分类型信息,但是相对于Any或者Anyobject还是改善很多。

let mixed2: [CustomStringConvertible] = [1, "two", true]
for obj in mixed2 
    print(obj.description)

另一种做法是使用enum可以嵌套值的特点,将相关信息封装进enum中。这个方法绝对无懈可击。

enum MixedWrap 
    case IntValue(Int)
    case StringValue(String)
    case BoolValue(Bool)


let mixed3 = [MixedWrap.IntValue(1),
              MixedWrap.StringValue("two"),
              MixedWrap.BoolValue(true)]

for value in mixed3 
    switch value 
    case let .IntValue(i):
        print(i)
    case let .StringValue(s):
        print(s)
    case let .BoolValue(b):
        print(b)
    

模式匹配

在 Swift 中,使用 ~= 来表示模式匹配的运算符。~=操作符有下面三种API:

  • 1.判等类型是否相同
    func ~=(a: T, b: T) -> Bool
  • 2.判等与 nil比较的类型
    func ~=(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
  • 3.判等一个范围输入和某个特定值
    func ~=(pattern: I, value: I.Bound) -> Bool

Swift的switch就是使用了~=运算符进行模式匹配,case指定的模式作为左参数输入,而等待匹配的被switch的元素作为运算符的右侧参数。只不过这个调用是由 Swift隐式完成的。

switch "snail" 
case "snail":
    print("相等")
default:
    break


let num: Int? = nil
switch num 
case 以上是关于SwiftTips之Language&API的主要内容,如果未能解决你的问题,请参考以下文章

C Language 图 - 有向图 && 无向图 术语(十七)

C Language 串 - BF算法&&KMP算法

Oracle之备份还原

SQL Fundamentals || DCL(Data Control Language) || 系统权限&对象权限管理(GRANT&REVOKE)

C Language Study - gets , getchar &amp; scanf

如何在 plotly & R Language 中关闭特定的图例类型?