swift 4.0> 进阶知识点全面梳理(五)

1,Swift 的访问控制模型基于模块和源文件的概念;

2,访问级别:

Swift 为代码的实体提供个【五个】不同的访问级别。

Open 访问 和 public 访问 允许实体被定义模块中的任意源文件访问,同样可以被另一模块的源文件通过导入该定义模块来访问。在指定框架的公共接口时,通常使用 open 或 public 访问。 open 和 public 访问 之间的区别将在之后给出;

Internal 访问 允许实体被定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问。通常在定义应用程序或是框架的内部结构时使用。

File-private 访问 将实体的使用限制于当前定义源文件中。当一些细节在整个文件中使用时,使用 file-private 访问隐藏特定功能的实现细节。

private 访问 将实体的使用限制于封闭声明中。当一些细节仅在单独的声明中使用时,使用 private 访问隐藏特定功能的实现细节。

open 访问是最高的(限制最少)访问级别,private 是最低的(限制最多)访问级别。

open 访问仅适用于类和类成员,它与 public 访问区别如下:

public 访问,或任何更严格的访问级别的类,只能在其定义模块中被继承。

public 访问,或任何更严格访问级别的类成员,只能被其定义模块的子类重写。

open 类可以在其定义模块中被继承,也可在任何导入定义模块的其他模块中被继承。

open 类成员可以被其定义模块的子类重写,也可以被导入其定义模块的任何模块重写。

显式地标记类为 open 意味着你考虑过其他模块使用该类作为父类对代码的影响,并且相应地设计了类的代码。

3,单目标应用的访问级别:

你可能会将代码的一些部分标注为 file private 或private 以对模块中的其他代码隐藏它们的实现细节。

框架的访问级别:

当你开发一个框架时,将该框架的面向公众的接口标注为 open 或 public,这样它就能被其他的模块看到或访问,比如导入该框架的应用。这个面相公众的接口就是该框架的应用编程接口(API)。

4,你框架的任何内部实现细节仍可以使用 internal 默认访问级别,如果你想从框架的其他部分隐藏细节也可以将它们标注为 private 或 file private 。仅当你想将它设为框架的API时你才能将实体标注为 open 或 public 。

5,单元测试目标的访问级别

当你在写一个有单元测试目标的应用时,你的代码应该能被模块访问到以进行测试。默认情况下只有标注为 open 或 public 的才可以被其他模块访问。但是,如果你使用 @testable  属性标注了导入的生产模块并且用使能测试的方式编译了这个模块,单元测试目标就能访问任何 internal 的实体。

public class SomePublicClass {}

internal class SomeInternalClass {}

fileprivate class SomeFilePrivateClass {}

private class SomePrivateClass {}

public var somePublicVariable = 0

internal let someInternalConstant = 0

fileprivate func someFilePrivateFunction() {}

private func somePrivateFunction() {}

6,元组类型的访问级别是所有类型里最严格的。例如,如果你将两个不同类型的元素组成一个元组,一个元素的访问级别是 internal,另一个是 private,那么这个元组类型是 private 级别的。元组类型不像类、结构体、枚举和函数那样有一个单独的定义。元组类型的访问级别会在使用的时候被自动推断出来,不需要显式指明。

7,函数类型:

函数类型的访问级别由【函数成员类型和返回类型】中的最严格访问级别决定。如果函数的计算访问级别与上下文环境默认级别不匹配,你必须在函数定义时显式指出。

❌func someFunction() -> (SomeInternalClass, SomePrivateClass) {} 是无法通过编译的;

这个函数的返回类型是一个由两个在自定义类型里定义的类组成的元组。其中一个类是 “internal” 级别的,另一个是 “private”。因此,这个元组的访问级别是“private”(元组成员的最严级别)。

由于返回类型是 private 级别的,你必须使用 private  修饰符使其合法:

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {}

8,枚举类型:

枚举中的独立成员自动使用该枚举类型的访问级别。你不能给独立的成员指明一个不同的访问级别。

原始值和关联值:

枚举定义中的原始值和关联值使用的类型必须有一个不低于枚举的访问级别。例如,你不能使用一个 private 类型作为一个 internal 级别的枚举类型中的原始值类型。

9,嵌套类型:

private 级别的类型中定义的嵌套类型自动为 private 级别。fileprivate 级别的类型中定义的嵌套类型自动为 fileprivate 级别。public 或 internal 级别的类型中定义的嵌套类型自动为 internal 级别。如果你想让嵌套类型是 public 级别的,你必须将其显式指明为 public。

10,子类:

你可以继承任何类只要是在当前可以访问的上下文环境中。但子类不能高于父类的访问级别,例如,你不能写一个 internal 父类的 public 子类。

Getters 和 Setters:常量、变量、属性不能拥有比它们类型更高的访问级别。

你可以给 setter 函数一个比相对应 getter 函数更低的访问级别以限制变量、属性、下标的读写权限。你可以通过在 var 和 subscript 的置入器之前书写 fileprivate(set) , private(set) , 或 internal(set) 来声明更低的访问级别。 即使你没有给一个存储属性书写一个明确的 getter 和 setter,Swift 会为你合成一个 getter 和 setter 以访问到存储属性的隐式存储。使用 fileprivate(set) , private(set) 和 internal(set) 可以改变这个合成的 setter 的访问级别,同样也可以改变计算属性的访问级别。

11,struct TrackedString {

        private(set) var numberOfEdits = 0

        var value: String = "" {

        didSet {

              numberOfEdits += 1

        }     }     }

TrackedString 结构体定义了一个可存储字符串的属性 value ,它又一个初始值 "" (空字符串)。这个结构图同样定义了一个可存储整数的属性 numberOfEdits ,它被用于记录 value 的修改次数。这个记录由 value 属性中的didset属性实现,它会增加 numberOfEdits 的值一旦 value 被设为一个新值。

TrackedString 结构体和 value 属性都没有显式指出访问级别修饰符,因此它们都遵循默认的 internal 级别。 numberOfEdits 属性的访问级别已经标注为 private(set) 以说明这个属性的 getter 是默认的 internal 级别,但是这个属性只能被 TrackedString 内的代码设置。这允许 TrackedString 在内部修改 numberOfEdits 属性,而且可以展示这个属性作为一个只读属性当在结构体定义之外使用时——包括 TrackedString 的扩展。

12,你若有必要也可以显式指明 getter 和 setter方法。下面的例子提供了一个定义为 public 级别的 TrackedString 结构体。结构体成员(包括 numberOfEdits 属性)因此有一个默认的 internal 级别。你可以设置 numberOfEdits 属性的getter方法为 public,setter 方法为 private 级别,通过结合 public 和 private(set) 访问级别修饰符:

public struct TrackedString {

    public private(set) var numberOfEdits = 0

      public var value: String = "" {

            didSet {

                  numberOfEdits += 1

            }

      }

      public init() {}

}

13,结构体的默认成员初始化器:

如果结构体的存储属性时 private 的,那么它的默认成员初始化方法就是 private 级别。如果结构体的存储属性是 file private 的,那么它的默认成员初始化方法就是 file private 级别。否则就是默认的 internal 级别。正如以上默认初始化的描述,如果你想在另一个模块中使用结构体的成员初始化方法,你必须提供在定义中提供一个 public 的成员初始化方法

14,协议继承:

如果你定义了一个继承已有协议的协议,这个新协议最高与它继承的协议访问级别一致。例如你不能写一个 public 的协议继承一个 internal 的协议。

协议遵循:

类型可以遵循更低访问级别的协议。例如,你可以定义一个可在其他模块使用的 public 类型,但它就只能在定义模块中使用如果遵循一个 internal 的协议。

遵循了协议的类的访问级别取这个协议和该类的访问级别的最小者。如果这个类型是 public 级别的,它所遵循的协议是 internal 级别,这个类型就是 internal 级别的。

15,高级运算符:

与 C 的算术运算符不同,Swift 中算术运算符默认不会溢出。

溢出行为都会作为错误被捕获。

要允许溢出行为,可以使用 Swift 中另一套默认支持的溢出运算符,比如溢出加法运算符( &+ )。

所有这些溢出运算符都是以( & )符号开始的。

Swift 支持 C 里面所有的位运算符;

16,位取反运算符( ~ )是对所有位的数字进行取反操作; 是一个前缀运算符,需要直接放在运算符的前面,并且不能有空格:

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits  // equals 11110000

17,UInt8 类型的整数有八位,可以存储 0 到 255 之间的任意值。

18,位与运算符( & )可以对两个数的比特位进行合并。它会返回一个新的数,只有当这两个数都是 1 的时候才能返回 1 。

位或运算符( | )可以对两个比特位进行比较,然后返回一个新的数,只要两个操作位任意一个为 1 时,那么对应的位数就为 1。

位异或运算符,或者说“互斥或”( ^ )可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为 1。

19,位左移和右移运算符:

位左移运算符( << )和位右移运算符( >> )可以把所有位数的数字向左或向右移动一个确定的位数;

位左和右移具有给整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻倍,将一个数右移一位相当于把这个数减半。

20,无符号整数的移位操作,

对无符号整数的移位规则如下:

已经存在的比特位按指定的位数进行左移和右移。

任何移动超出整型存储边界的位都会被丢弃。

用 0 来填充向左或向右移动后产生的空白位。

这种方法称就是所谓的逻辑移位。

21,溢出运算符:在默认情况下,当向一个整数赋超过它容量的值时,Swift 会报错而不是生成一个无效的数。

Swift 提供三个算数溢出运算符来让系统支持整数溢出运算。这些运算符都是以 & 开头的:

溢出加法 ( &+ )

溢出减法 ( &- )

溢出乘法 ( &* )

22,unsignedOverflow 初始化为 UInt8 所能容纳的最大整数( 255 ,二进制为 11111111 )。溢出加法运算符( &+ )对其进行加 1 操作。这使得它的二进制表示正好超出 UInt8 所能容纳的位数,也就导致它溢出了边界,如下图所示。溢出后,留在 UInt8 边界内的值是 00000000 ,也就是十进制数值的 0 。

23,当我们对一个无符号整数使用溢出减法( &- )进行下溢运算时也会产生类似的现象:

var unsignedOverflow = UInt8.min

unsignedOverflow = unsignedOverflow &- 1

// unsignedOverflow is now equal to 255

var signedOverflow = Int8.min

signedOverflow = signedOverflow &- 1

// signedOverflow is now equal to 127

Int8 整数能容纳的最小值是 -128 ,以二进制表示即 10000000 。

对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。

24,struct Vector2D {

    var x = 0.0, y = 0.0

}

extension Vector2D {

    static func + (left: Vector2D, right: Vector2D) -> Vector2D {

        return Vector2D(x: left.x + right.x, y: left.y + right.y)

    }

}

25,要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字[表情]之前指定 prefix 或者 postfix 限定符:

extension Vector2D {

    static prefix func - (vector: Vector2D) -> Vector2D {

        return Vector2D(x: -vector.x, y: -vector.y)

    }

}

这段代码为 Vector2D  类型实现了单目减运算符( -a )。由于单目减运算符是前缀运算符,所以这个函数需要加上 prefix  限定符;

26,组合赋值运算符:

组合赋值运算符将赋值运算符( = )与其它运算符进行结合。比如,将加法与赋值结合成加法赋值运算符( += )。在实现的时候,需要把运算符的[表情]左参数设置成 inout  类型,因为这个参数的值会在运算符函数内直接被修改。

extension Vector2D {

    static func [表情]+= (left: inout Vector2D, right: Vector2D) {

        left = left + right

    }

}

27,不能对默认的赋值运算符( = )进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三元条件运算符 a ? b : c 进行重载。

28,要使用等价运算符来检查你自己类型的相等:

extension Vector2D {

    static func == (left: Vector2D, right: Vector2D) -> Bool {

        return (left.x == right.x) && (left.y == right.y)

    }

    static func != (left: Vector2D, right: Vector2D) -> Bool {

        return !(left == right)

    }

}

29,新的运算符要在全局作用域内,使用 operator 关键字进行声明,同时还要指定 prefix 、 infix 或者 postfix 限定符:

prefix operator +++ {}

实例:

extension Vector2D {

    static prefix func +++ (vector: inout Vector2D) -> Vector2D {

        vector += vector

        return vector

    }

}

30,自定义中缀运算符的优先级和结合性:

⭕结合性( associativity )可取的值有 left , right  和 none 。⭕

associativity 的默认值是 none , precedence 默认为 100 。下面例子定义了一个新的自定义中缀运算符 +- ,此运算符是 left 结合的,优先级为 140 :

infix operator +- { associativity left precedence 140 }

extension Vector2D {

    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {

        return Vector2D(x: left.x + right.x, y: left.y - right.y)

    }

}

let firstVector = Vector2D(x: 1.0, y: 2.0)

let secondVector = Vector2D(x: 3.0, y: 4.0)

let plusMinusVector = firstVector +- secondVector

// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

30,协议组合类型:

协议组合类型允许你指定一个值,该值的类型遵循多个协议的要求而不必显式定义一个新的命名型的继承自每个你想要该类型遵循的协议的协议。比如,指定一个协议组合类型 Protocol A & Protocol B & Protocol C  实际上是和定义一个新的继承自 Protocol A , Protocol B  , Protocol C 的协议 Protocol D 是完全一样的,但不需要引入一个新名字。同理,标明一个协议组合类型 SuperClass & ProtocolA 与声明一个新类型 SubClass 继承自 SuperClass 并遵循 ProtocolA 是一样的,但不需要引入新名字。

31,你可以使用后缀 self 表达式来获取类型作为一个值。比如说, SomeClass.self 返回 SomeClass 本身,而不是 SomeClas 的一个实例。并且 SomeProtocol.self 返回 SomeProtocol 本身,而不是运行时遵循 SomeProtocol 的某个类型的实例。

你可以对类型的实例使用dynamicType 表达式来获取该实例的动态运行时的类型,如下例所示:

let someInstance: SomeBaseClass = SomeSubClass()

someInstance.dynamicType.printClassName()

// Prints "SomeSubClass"

let metatype: SomeBaseClass.Type = SomeBaseClass.self

let anotherInstance = metatype.init(string: "some string")

32,可以使用特征运算符( === 和 !== )来测试一个实例的运行时类型和它的编译时类型是否一致。

if someInstance.dynamicType === someInstance.self { }

33,Swift 中的类型推断作用于单独的表达式或语句的级别。这意味所有用于推断省略的类型或表达式中的类型所必需的信息必须可以从表达式或其子表达式中的某个表达式的类型检查中获取。

34,在函数中, #function 会的值就是那个函数的名字,在方法里就是那个方法的名字,在属性设置器和读取器中则是属性的名字,在特殊的成员例如 init 或 subscript 就是关键字的名字,在最顶层文件,就就是当前模块的名字。

父类表达式使类互动于它的父类。它有以下形式:

super. member name

super[ subscript index ]

super.init( initializer arguments )

第一种形式用来访问父类中的成员。第二种形式用来访问父类的下标。第三种形式用来访问父类的初始化器。

35,通配符表达式:

(x, _) = (10, 20)

// x is 10, and 20 is ignored

36,path 可以包含使用方括号来包含下标,只要下标的形式参数遵循 Hashable 协议。这个例子在 key path 中使用下标来访问数组中的第二个元素:

let greetings = ["hello", "hola", "bonjour", "안녕"]

let myGreeting = greetings[keyPath: \[String].[1]]

// myGreeting is 'hola'

37,用 objc 特性标记的类必须继承自一个 Objective-C 中定义的类。如果你把 objc 用到类或协议中,它会隐式地应用于该类或协议中 Objective-C 兼容的成员上。如果一个类继承自另一个带 objc 特性标记或 Objective-C 中定义的类,编译器也会隐式地给这个类添加 objc 特性。标记为 objc 特性的协议不能继承自非 objc 特性的协议。

objc 特性同样会在下面的情况中隐式地添加:

声明是子类的重写,并且父类的声明有 objc 特性;

声明满足的需求来自一个拥有 objc 特性的协议;

声明有 IBAction , IBOutlet , IBDesignable , IBInspectable , NSManaged , 或者 GKInspectable 特性。

38,objc 特性可以接受一个特性实参,由一个标识符组成。当你想在 Objective-C 中为 objc 特性标记的实体暴露一个不同的名字时,用这个特性。你可以把这个实参用在命名类,枚举,枚举成员,协议,方法,getter,setter,初始化器。下面的例子把 ExampleClass 中 enabled 属性的getter作为 isEnabled 暴露给 Objective-C 代码,而不仅仅是属性本身的名字。

@objc

class ExampleClass: NSObject {

    var enabled: Bool {

        @objc(isEnabled) get {

            // Return the appropriate value

        }

    }

}

39,Swift 提供了下列 Interface Builde r特性: IBAction , IBOutlet , IBDesignable ,和 IBInspectable 。

40,IBAction 和 IBOutlet 特性都隐含 objc 特性。

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

推荐阅读更多精彩内容