[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fMxJKQu-rdh38KzMlXEUF0oOlcwS1YrYgyFvIR6JgbGs":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":28,"notice":28,"visitCount":29,"commentCount":30,"keywords":31},103,"2024-03-23T05:52:23.185Z","Swift知识一览（二）","## 一、枚举\n### 1.1 定义如下：\n```swift\nenum Season {\n    case spring\n    case summer\n    case autumn\n    case winter\n}\n或者写成\nenum Season {\n    case spring, summer, autumn, winter\n}\n\nvar current = Season.spring\ncurrent = .summer\nprint(current) // summer\n```\n\n\n### 1.2 枚举关联值(associate value)\n就是在枚举的声明中，关联某一个类型，配合switch使用枚举时，就可以从枚举中解析出关联值。\n```swift\nenum Direction {\n    case angle(Float)\n    case name(String)\n}\nvar dir = Direction.angle(45)\ndir = .name(\"东北方向\")\n\n// 从枚举中取关联值\nswitch dir {\n    case .angle(let pt):\n        print(\"当前方向角度：\\(pt)\")\n    case .name(let name):\n        print(\"当前方向是：\\(name)\")\n}\n```\n\n\n### 1.3 枚举的原始值(raw value)\n指的是在定义枚举的时候，可以给enum指定一个类型，然后可以给case中的枚举成员指定一个该类型的默认值，这个默认值就是枚举成员的关联值。\n```swift\nenum Direction : String { // 定义原始值的类型\n    case east = \"东\" // \"东\"就是原始值，依此类推\n    case south = \"南\"\n    case west = \"西\"\n    case north = \"北\"\n}\nvar dir = Direction.east\nprint(dir) // east\nprint(dir.rawValue) // 东\nprint(Direction.east.rawValue) // 东\n\n// 有一种直接创建枚举变量的方式\nlet dir = Direction(rawValue: \"南\")\n// 如果不包括在枚举成员的所有原始值中，那么此时的dir就是一个Direction?可选类型\n// 可能是nil，比如: Direction(rawValue: \"南北\")\n// 可选类型在后面会写到\n```\n\n还有隐式原始值的概念，如下：\n【如果枚举的原始值类型为Int、String，那么Swift会给枚举成员分配原始值。】\n```swift\nenum Direction : String {\n    case east, south, west, north\n}\n// 那么枚举成员的原始值就是对应的成员名称字符串\nprint(Direction.east) // east\nprint(Direction.north.rawValue) // east\n\nenum Direction: Int {\n    case east, south, west, north\n}\nprint(Direction.east.rawValue) // 0，后面成员的原始值依次加1\n\n// 如果给case其中的某几个case赋原始值，那么没赋原始值的枚举成员，\n// 它的原始值是紧靠它的前一个枚举成员原始值加1\n// 比如\nenum Direction: Int {\n    case east = 1, south, west = 8, winter\n}\nprint(Direction.east.rawValue) // 1\nprint(Direction.south.rawValue) // 2\nprint(Direction.west.rawValue) // 8\nprint(Direction.north.rawValue) // 9\n```\n\n### 1.4 枚举的递归(recursive enumeration)\n也就是指枚举的case枚举成员，如果有关联值，且其关联值类型就是当前枚举，这种情况就是递归枚举。\n递归枚举在定义时一定要在enum前加`indirect`修饰，或者在递归的枚举成员case前加`indirect`修饰。\n```swift\nindirect enum Direction {\n    case angle(Float)\n    case twoDirection(Direction, Direction)\n}\n\n// 或者\nenum Direction {\n    case angle(Float)\n    indirect case twoDirection(Direction, Direction)\n}\n```\n\n\n## 二、MemoryLayout获取数据类型占用的内存大小\n如果在Objective-C中，使用的是sizeof来获取类型占用的内存大小，Swift中使用的是MemoryLayout。\n### 2.1 具体使用\n```swift\nMemoryLayout\u003CInt>.size // 64位系统输出8，32位系统输出4\n\n// 针对变量获取内存大小\nlet num = 8;\nMemoryLayout.size(ofValue:num) // 8\n2.2 其他使用\n`MemoryLayout\u003CInt>.stride`和`MemoryLayout\u003CInt>.alignment`\n- stride 是指系统分配的内存大小\n- alignment是内存对齐的字节数\n- size是指在分配的内存中实际占用了多少字节\n// 和上面一样，都可以获取类型或者变量的类型的内存大小\nMemoryLayout\u003CInt>.stride\nMemoryLayout\u003CInt>.alignment\n\nlet num = 8;\nMemoryLayout.stride(ofValue:num)\nMemoryLayout.alignment(ofValue:num)\n```\n\n \n### 2.3 关联值和原始值在enum中的内存存储\n- 关联值存储在枚举变量类型中，所以需要能够存储所有大小的关联值的类型，也就是说，枚举类型的内存大小是所有关联值类型内存大小的和。\n举例如下：\n```swift\nenum Direction {\n    case ange(Int, Int)\n    case name // 这个地方如果没有指定关联类型，那么使用1个字节就可以存储\n}\n// 那么会是以下输出\nprint(MemoryLayout\u003CDirection>.alignment) // 8\nprint(MemoryLayout\u003CDirection>.size) // 17 解释：2 * 8 + 1 = 17\nprint(MemoryLayout\u003CDirection>.stride) // 24 解释：，内存对齐是8，所以取8的倍数24\n```\n\n\n- 原始值，比较特殊，不会占用到枚举类型的内存中去，具体的原始值存储在其他地方。\n```swift\nenum Direction : String {\n    case east, south, west, north\n}\n\nenum Direction: Int {\n    case east = 1, south, west, north\n}\n\n// 上面2种情况，枚举类型占用的内存都是1个字节。\n// 因为只用一个字节就可以表示出不同的枚举。\n// 可以想象使用以下方式，表示上面的默认1的原始值写法，直接返回某个具体的值就可以，没有必要存储原始值的类型。\nenum Direction: Int {\n    case east, south, west, north\n    \n    func rawValue() -> Int {\n        if self == 0 return 1\n        if self == 1 return 2\n        ...\n    }\n}\n```\n\n\n## 三、可选项（optional）\n也就是常说的可选类型，指一个变量的值可以是nil，或者是有确定的值。\n### 3.1 定义\n通过在类型名称后加一个？号，表示这个变量是可选类型\n```swift\nvar name: String? = nil // 等同于 var name: String?，因为可选类型未赋值时，默认就是nil\nname = \"小明\"\n\n// 输出的是Optional包裹的值\nprint(name) // Optional(\"小明\")\n```\n\n\n### 3.2 强制解包 & 可选项绑定\n切记，对值为nil的可选变量强制解包，会runtime error\n\n使用可选项时，需要判断是否为nil，或者复制给临时变量。这种方式叫可选项绑定。\n\n```swift\nvar name: String? = \"小明\"\nprint(name!) // \"小明\" 强制解包出来的就是直接值，不会有Optional包裹\n\nlet name1: String = name!\n\n// 使用可选项时，需要进行判断是否为nil，或者进行赋值\n// 这就叫做可选项绑定\nif name != nil {\n    // do something with name!\n}\n// 或者\nif let sureName = name { // sureName的作用域仅在这个大括号内\n    // do something with sureName\n}\n```\n\n\n这里还有一个概念，叫隐式解包，也就是直接使用感叹号!，例子如下：\n```swift\nvar a: Int! = 1 // 这个叫隐式解包的可选项\n// 这种方式可以给a置为nil，当然在使用之前必须赋值非nil，否则解包crash\na = nil\n\n// 这种有时候用在跟assert的情况\n```\n\n### 3.3 空合运算符??(nil-coalescing operator)\n指的是，用在可选类型变量后面，加一个??，紧跟一个同类型的变量，表示如果??前的可选项的值为nil，那么使用??后面的变量，否则使用??之前的变量。\n```swift\nlet a: String?\nlet b = a ?? \"未知名字\" // 因为b的值为\"未知名字\"，所以b的类型不是可选项类型\nprint(\"\\(a)\") // nil\nprint(\"\\(b)\") // \"未知名字\"\n\nlet a: String?\nlet b: String? = \"1\"\nlet c = a ?? b // 此时的c是Optional(\"1\")\n\nlet num: Int? = 1\nlet num1: Int = 2\nlet result = num ?? num1 // 此时的result是Int，非可选项\n```\n\n小结一下：\n- ??前的变量必须是可选项；\n- 如果??前后的2个变量都是可选类型，不管这2个可选类型是否有值，那么被赋值变量也是可选项；\n- 如果??前的可选项有值，??后的变量为非可选项，那么返回左边变量的时候就会对左边的可选项进行强制解包；\n总之一句话，对于??这样的空合运算符，左侧的被赋值变量的类型，取决于??后面的变量类型。\n\n另外，空合运算符可以联合使用，如下：\n```swift\nlet a: Int? = 1\nlet b: Int? = 2\nlet c = a ?? b ?? 3 // 那么c是Int，非可选项\n// 这种连续使用，左侧的c取决于最右侧的最后那个变量或者值\n```\n\n**特别注意的地方**：\n- 如果从字典中通过key取值，被赋值的变量是可选项（如果没有对应的key，那么返回的是nil）\n- 如果从数组中通过index取值，被赋值的变量是非可选项（如果有越界，程序直接crash）\n```swift\nlet dic = [\"name\": \"小明\", \"age\": 1]\nlet name = dic[\"name\"] // Optional(\"小明\")\nlet gender = dic[\"gender\"] // nil\n\nlet arr = [1, 2, 3]\nlet a = arr[0] // 1\nlet b = arr[3] // crash\n```\n\n\n### 3.4 guard\n和if正好相反，if是true才去执行大括号内的语句，guard是false才去执行紧接着的大括号内语句。\n在guard的else语句中，一定要写退出当前作用域(return、throw error、break、continue等），否则会报错\n```swift\nif condition {\n    // 如果condition为true，进入这个大括号\n}\n\nguard condition else {\n    // 如果condition为false，进入这个大括号\n    return\n}\n// 如果condition为true，会执行下面的代码\n\n\nvar name: String?\n...\nguard let sureName = name else {\n    print(\"name 不存在\")\n    return\n}\nprint(\"name is: \\(name)\")\n```\n\n\n### 3.5 多重可选项\n指大的是使用2个??来声明可选项\n```swift\nlet a: Int? = 1\nlet b: Int?? = a\nlet c: Int?? = 1\n\n// 在这种情况下，a 和 c 是等价的\nprint(b == c) // true\n\nlet a: Int? = nil\nlet b: Int?? = a\nlet c: Int?? = nil // 表示c中存储的是nil，而不是Optional(nil)\n// 在这种情况下，a 和 c 是不等价的\nprint(b == 3) // false\n```\n\n千万留心下面的这种情况：\n```swift\nlet a: Int? = nil\nlet b: Int?? = a\nlet c: Int?? = nil\n\nprint((b ?? 1) ?? 2) // 2，解释：首先b中存储了a这样一个可选项，那么b第一次解包出来的是a，然后再解1次包是nil\nprint((c ?? 1) ?? 2) // 1，解释：首先c中存储了nil，那么第一次解包出来就是nil，所以将1返回。\n```\n\n\n### 3.6 对多重可选项，可以使用断点调试来探究其构成\n断点使用llvm调试命令：`frame variable -R xxx`，xxx是变量\n```xcode-llvm\n(lldb) frame variable -R xxx\n// 或者简写\n(lldb) fr v -R xxx\n```\n![llvm指令.jpg](https://image.xinwei.ltd/17111730313711711173051270.jpg)\n可以看到每一个可选变量，它们的stack frame结构。\n双重可选变量内部是包裹的一个可选变量。\n![llvm指令1.jpg](https://image.xinwei.ltd/17111730698921711173097908.jpg)\n\n\n这是《Swift知识一览》系列的第二篇文章，若感兴趣，请关注后续发表的文章。","这篇文章是《Swift知识一览》的第二篇文章。该篇文章主要介绍Swift的枚举（关联值、原始值、递归枚举）、MemoryLayout、可选项等内容。根据案例来分析枚举、可选项的用法还有注意点。","https://image.xinwei.ltd/17111730313711711173051270.jpg",499668042977349,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"13121171998","全栈老韩",1,"https://image.xinwei.ltd/images/IMG_5430.JPG","全栈工程师，擅长iOS App开发、前端（vue、react、nuxt、小程序&Taro）开发、Flutter、React Native、后端（midwayjs、golang、express、koa）开发、docker容器、seo优化等。","2024-01-01T16:14:30.305Z",2,{"id":21,"name":23},"IT技术",3,{"id":24,"name":26,"parentId":21},"iOS",[],"",1261,0,"swift,swift develop,swift knowledge,swift知识,swift开发"]