├── .gitignore ├── LearnSwift ├── Base │ ├── DataType.swift │ └── Tokenizer.swift ├── Helper │ └── DDYLog.swift ├── LearnSwift.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── mac.xcuserdatad │ │ │ ├── IDEFindNavigatorScopes.plist │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── mac.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── LearnSwift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── Network │ ├── DDYICMPPing.swift │ ├── DDYNetInfo.swift │ ├── DDYNetdiag.swift │ ├── DDYRealReachability.swift │ ├── DDYTCPPing.swift │ ├── DDYUDPTraceRoute.swift │ └── LPFDSet.swift ├── README.md └── Swift ├── AVFoundation.md ├── Blogs.md ├── LLDB.md ├── Note.md ├── Swift 派发机制.md ├── Swift001.md ├── Swift002.md ├── Swift003.md ├── Swift004.md ├── Swift005.md ├── Swift006.md ├── Swift007.md ├── Swift008.md ├── Swift009.md ├── Swift010.md ├── Swift011.md ├── Swift012.md ├── SwiftTips.md ├── UIImageView.md └── temp.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LearnSwift/Base/DataType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import os 3 | 4 | class DataType { 5 | public class func test() { 6 | self.testCreateDictSomeMethod() 7 | } 8 | 9 | // Swift 包含了 Objective-C 上所有基本数据类型,另外还增加了高阶数据类型,如元组(Tuple),还增加了可选类型(Optional) 10 | // MARK: - 常量&变量 11 | private func test1() { 12 | // 常量用let声明 13 | let dataTypeLet = "let" // 类型推断,根据所赋值得到类型String,等同于 let dataTypeLet: String = "let" 14 | // 变量用var声明 15 | var dataTypeVar = "var" 16 | dataTypeVar = "changed" 17 | print("\(dataTypeLet) \(dataTypeVar)") 18 | } 19 | 20 | // MARK: - 整型 21 | private func test2() { 22 | // MARK: 有符号整型: Int,Int8,Int16,Int32,Int64 23 | // MARK: 无符号整型: UInt,UInt8,UInt16,UInt32,UInt64 24 | // MARK: 兼容NS框架: NSInteger 25 | let age: UInt = 18 // 如果不显式声明数据类型为UInt则类型推导得到类型为Int 26 | var count: Int = 10086 // Int在64位平台上和Int64等同,在32位平台和Int32等同 27 | count = 1008611 28 | // MARK: arc4random() 这个全局顶级函数会生成10位数的随机整数(UInt32)。其生成的最大值是4294967295(2^32 - 1),最小值为0 29 | // 生成一个 1~100 的随机数(包括1和100) 30 | let random1 = Int(arc4random() % 100) + 1 31 | let random2 = Int(arc4random_uniform(100)) + 1 32 | print("\(age) \(count) \(random1) \(random2)") 33 | } 34 | 35 | // MARK: - 浮点型 36 | private func test3() { 37 | // MARK: Double/Float64: 64位浮点数。存储很大或精度很高的浮点数时使用 38 | // MARK: Float/Float32: 32位浮点数。精度要求不高时使用 39 | let score: Float = 99.5 40 | let velocity: Double = 10.33333 41 | // MARK: 为增强大数可读性,Swift增加了下划线来分隔数字(整数、浮点数) 42 | let num1 = 10_000_000_000 43 | let num2 = 1_000_000.000_000_1 44 | let num3: Int = 1_0_0_0_1 45 | print("\(score) \(velocity) \(num1) \(num2) \(num3)") 46 | } 47 | 48 | // MARK: - 布尔型 49 | private func test4() { 50 | // MARK: Bool: 用来表示逻辑上真(true)与假(false),但不能用0和非0表示 51 | var isSelected: Bool = false 52 | isSelected = true 53 | print(isSelected) 54 | } 55 | 56 | // MARK: - 字符型 57 | private func test5() { 58 | // MARK: Character: 一般指单个字符 59 | let firstChar: Character = "C" 60 | // MARK: 字符串 String: 是字符的序列集合 NSString: 兼容NS框架字符串 61 | var city = "BeiJing" 62 | // 用\(str) 方式包裹变量常量 63 | let message = "Welcome to \(city)" 64 | // 更改city为"ShangHai"但message中仍为"BeiJing" 65 | city = "ShangHai" 66 | // MARK: String和NSString比较 67 | // String 是一个结构体,性能更高 68 | // NSString 是一个OC类,性能略差 69 | // String 支持直接遍历 70 | print("\(firstChar) \(city) \(message)") 71 | // 大写 72 | let changeStr1 = city.uppercased() 73 | // 小写 74 | let changeStr2 = city.lowercased() 75 | // 首字母大写 76 | let changeStr3 = city.capitalized 77 | // 判断是否空字符串 78 | if message.isEmpty == false { 79 | // message.count得到字符数量 80 | print("\(message.count)") // 18 81 | print("\(city)") // ShangHai 82 | print(message) // Welcome to BeiJing 83 | print(changeStr1) // SHANGHAI 84 | print(changeStr2) // shanghai 85 | print(changeStr3) // Shanghai 86 | } 87 | 88 | // MARK: String截取字符串 89 | let originalStr = "Welcome to BeiJing" 90 | // 取前三个字符 91 | let prefixStr = originalStr.prefix(3) // Wel 92 | // 取后三个字符 93 | let suffixStr = originalStr.suffix(3) // ing 94 | // 取限定范围[3..<6]内字符 95 | let indexStart = originalStr.index(originalStr.startIndex, offsetBy: 3) 96 | let indexEnd = originalStr.index(originalStr.startIndex, offsetBy: 6) 97 | let midStr = originalStr[indexStart.. = [] 110 | let arrayM4: [String?] = [] 111 | print("\(arrayM1) \(arrayM2) \(arrayM3) \(arrayM4)") 112 | // 一个数组的完成类型为:Array。ElementType表示数组中元素的类型 113 | let array1 = Array() 114 | // 一种精简的表示法:Array[ElementType] 115 | let array2 = [Int]() 116 | // 声明一个Double类型常量数组,创建10个元素,每个元素都是2.0 117 | let array3 = [Double](repeating: 2.0, count: 10) 118 | // 字面量方式声明一个有4个元素的Int类型数组常量 119 | let array4 = [1, 2, 3, 4] 120 | // 声明一个有2个元素的 Any 类型数组常量 121 | let array5 = [1, "two", true, 1.1] as [Any] 122 | print("\(array1) \(array2) \(array3) \(array4) \(array5)") 123 | // MARK: Array和NSArray比较 124 | // Array是一个Swift结构体,性能较高 125 | // NSArray是一个OC类,性能略差 126 | // Array可以放普通类型 127 | } 128 | 129 | public class func testHandle() { 130 | // 声明一个空数组变量(let声明为常量, var声明变量,即可变数组) 131 | var testArray = [String]() 132 | // 追加元素 姿势1 133 | testArray.append("six") 134 | // 追加元素 姿势2 135 | testArray += ["seven"] 136 | // 指定位置添加元素 137 | testArray.insert("one", at:0) 138 | // 通过下标修改数组中的数据 139 | testArray[0] = "message" 140 | // 通过小标区间替换数据(前3个数据),没有则追加 141 | testArray[0...2] = ["message","Apple","com"] 142 | // 交换元素位置 143 | testArray.swapAt(1, 2) 144 | // 删除下标为2的数组 145 | testArray.remove(at: 2) 146 | // 删除最后一个元素 147 | testArray.removeLast() 148 | // 删除数组中所有元素 keepingCapacity:保持最大容量 149 | testArray.removeAll(keepingCapacity: true) 150 | // 数组组合 151 | let addStringArr = testArray + ["1", "2"] 152 | // 使用for in 实现数组遍历 153 | for value in addStringArr { 154 | print("\(value)"); 155 | } 156 | // 通过enumerate函数同时遍历数组的所有索引与数据 157 | for (index, value) in addStringArr.enumerated() { 158 | print("index:\(index) data:\(value)"); 159 | } 160 | // 过滤数组元素(元素长度小于6) 161 | let newTypes = addStringArr.filter { $0.count < 6 } 162 | // 创建包含100个元素的数组 ["条目0", "条目1" ... "条目5"] 163 | let intArray1 = Array(0..<6).map{ "条目\($0)"} 164 | // 创建1-10连续整数数组 姿势1 闭区间 165 | let intArray2 = [Int](1...10) 166 | // 创建1-10连续整数数组 姿势2 半闭半开区间 167 | let intArray3 = [Int](1..<11) 168 | // 获取数组元素个数 169 | let testArrayCount = testArray.count 170 | // 判断数组是否为空 171 | if testArray.isEmpty == false { 172 | print("\(testArray)") 173 | print("\(testArrayCount)") 174 | print("\(addStringArr)") 175 | print("\(newTypes)") 176 | print("\(intArray1)") 177 | print("\(intArray2)") 178 | print("\(intArray3)") 179 | } 180 | } 181 | 182 | // MARK: - 字典 183 | private func testDict() { 184 | // MARK: Dictionary: 是无序的键值对的集,分配常量得到不可变字典,分配变量得到可变字典 185 | // 字典是由键值 key:value 对组成的集合 186 | // 字典中的元素之间是无序的 187 | // 字典是由两部分集合构成的,一个是键集合,一个是值集合 188 | // 字典是通过访问键间接访问值的 189 | // 键集合是不能有重复元素的,而值集合是可以重复的 190 | // Swift中的字典类型是Dictionary,也是一个泛型集合 191 | // 使用let修饰的字典是不可变字典 192 | // 使用var修饰的字典是可变字典 193 | // 建立个空字典变量(let声明为常量, var声明变量,即可变字典) 194 | var fruitPriceDict = [String: Int]() // Dictionary() 195 | fruitPriceDict = ["apple":10, "pear":9, "banana":8, "peach":11, "strawberry":30, "lemon":1] 196 | print(fruitPriceDict) 197 | // 声明一个字典变量,其key为String类型 value为Any类型 198 | var personDict = ["name":"LiLei", "age":18, "nickName":"XiaoLi", "score":100] as [String : Any] 199 | // 修改key对应value值(不存在则添加)姿势1 200 | personDict.updateValue("city", forKey: "BeiJing China") 201 | // 修改key对应value值(不存在则添加)姿势2 202 | personDict["city"] = "BeiJing" 203 | // 删除key值及对应value值 姿势1 204 | personDict.removeValue(forKey: "score") 205 | // 删除key值及对应value值 姿势2 206 | personDict["nickName"] = nil 207 | // 访问字典的key集合 208 | let keysSet = personDict.keys 209 | // 访问字典的values数组 210 | let valueArray = personDict.values 211 | print("\(keysSet) \(valueArray)") 212 | } 213 | 214 | // MARK: - 遍历字典 215 | public class func testDictEnumerated() { 216 | let personDict = ["name":"LiLei", "age":18, "nickName":"XiaoLi", "score":100] as [String : Any] 217 | // 遍历字典 姿势1 218 | for (key, value) in personDict { 219 | print("\(key):\(value)"); 220 | } 221 | // 遍历字典 姿势2 222 | for keyAndValue in personDict { 223 | print("keyAndValue: \(keyAndValue)") 224 | } 225 | // 只遍历字典的键(key) 226 | for key in personDict.keys { 227 | print("\(key)"); 228 | } 229 | // 只遍历字典的值(value) 230 | for value in personDict.values { 231 | print("\(value)"); 232 | } 233 | } 234 | 235 | // MARK: - 字典过滤和合并 236 | public class func testDictFilterAndMerge() { 237 | // 建立个空字典变量(let声明为常量, var声明变量,即可变字典) 238 | let fruitPriceDict = ["apple":10, "pear":9, "banana":8, "peach":11, "strawberry":30, "lemon":1] 239 | // 过滤字典元素 240 | let fruitPriceDict2 = fruitPriceDict.filter { $0.value < 10 } 241 | print("\(fruitPriceDict2)") 242 | // 合并 姿势1 243 | var dict1 = ["name":"000","age":18,"title":"888"] as [String : Any] 244 | let dict2 = ["name":"da","hegiht":190] as [String : Any] 245 | 246 | for e in dict2 { 247 | dict1[e.key] = dict2[e.key] 248 | } 249 | // 如果key存在会修改,key不存在会新增 250 | print(dict1) 251 | var dic = ["one": 10, "two": 20] 252 | // merge方法合并 253 | let tuples = [("one", 5), ("three", 30)] 254 | dic.merge(tuples, uniquingKeysWith: min) 255 | print("dic:\(dic)") 256 | // merging方法合并 257 | let dic2 = ["one": 0, "four": 40] 258 | let dic3 = dic.merging(dic2, uniquingKeysWith: min) 259 | print("dic3:\(dic3)") 260 | // merge(_: uniquingKeysWith:):这种方法会修改原始Dictionary 261 | // merging(_: uniquingKeysWith:):这种方法会创建并返回一个全新的Dictionary 262 | } 263 | 264 | // MARK: - 字典另类创建方式 265 | public class func testCreateDictSomeMethod() { 266 | // 通过元组创建字典 267 | let tupleKeyValueArray = [("Monday", 30), ("Tuesday", 25), ("Wednesday", 27)] 268 | let dictFromTuple = Dictionary(uniqueKeysWithValues: tupleKeyValueArray) 269 | print(dictFromTuple) // ["Monday": 30, "Tuesday": 25, "Wednesday": 27] 270 | // 通过键值序列创建字典 271 | let keyArrayToDict = ["Apple", "Pear"] 272 | let valueArrayToDict = [7, 6] 273 | let keyValueArrayToDict = Dictionary(uniqueKeysWithValues: zip(keyArrayToDict, valueArrayToDict)) 274 | print(keyValueArrayToDict) 275 | // 用键序列/值序列创建字典 276 | let arrayKeyOrValue = ["Monday", "Tuesday", "Wednesday"] 277 | let indexKeyDict = Dictionary(uniqueKeysWithValues: zip(1..., arrayKeyOrValue)) 278 | let indexValueDict = Dictionary(uniqueKeysWithValues: zip(arrayKeyOrValue, 1...)) 279 | print("\(indexKeyDict) \(indexValueDict)") // [1: "Monday", 2: "Tuesday", 3: "Wednesday"] ["Wednesday": 3, "Tuesday": 2, "Monday": 1] 280 | // 数组分组成字典(比如下面生成一个以首字母分组的字典) 281 | let nameGroupArray = ["LiLei", "LiXiaolong", "LiuDehua", "HanMeimei", "HanLei", "SunWukong", "ErLangshen"] 282 | let dictFromNameGroup = Dictionary(grouping: nameGroupArray) { $0.first! } 283 | print(dictFromNameGroup) // ["S": ["SunWukong"], "L": ["LiLei", "LiXiaolong", "LiuDehua"], "E": ["ErLangshen"], "H": ["HanMeimei", "HanLei"]] 284 | 285 | let parsedType = OSLogType.debug 286 | let log = OSLog(subsystem: "my.subsystem.domain", category: "myCategory") 287 | os_log("%{private}@", log: log, type: parsedType, "os_log_test") 288 | } 289 | 290 | // MARK: - 获取对象占用内存 291 | // https://www.jianshu.com/p/36234c1ee24a 292 | private func getMemory() { 293 | let a1 = MemoryLayout.size 294 | let a2 = MemoryLayout.stride 295 | let a3 = MemoryLayout.alignment 296 | let a4 = class_getInstanceSize(DataType.self) 297 | print("\(a1) \(a2) \(a3) \(a4)") 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /LearnSwift/Base/Tokenizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tokenizer.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/13. 6 | // 7 | 8 | import Foundation 9 | import NaturalLanguage 10 | import CoreText 11 | 12 | // [iOS 分词处理](https://www.jianshu.com/p/6539b0aee5c2) 13 | // iOS 自带两种分词方式 NaturalLanguage 及 CFStringTokenizer 14 | extension String { 15 | @available(iOS 12.0, *) 16 | public func NLTokenizer() -> [String] { 17 | let tokenizer = NaturalLanguage.NLTokenizer(unit: .word) 18 | tokenizer.string = self 19 | var keyWords: [String] = [] 20 | 21 | tokenizer.enumerateTokens(in: startIndex.. [String] { 31 | let cString = self as CFString 32 | let nsString = self as NSString 33 | let cStringCount = nsString.length 34 | let ref = CFStringTokenizerCreate( 35 | nil, 36 | cString, 37 | CFRangeMake(0, cStringCount), 38 | kCFStringTokenizerUnitWord, 39 | CFLocaleCopyCurrent() 40 | ) 41 | CFStringTokenizerAdvanceToNextToken(ref) 42 | var range: CFRange = CFStringTokenizerGetCurrentTokenRange(ref) 43 | var keywords: [String] = [] 44 | var preTokenEndIndex: Int = -1 45 | while range.length > 0 { 46 | let defaultIndex = preTokenEndIndex + 1 47 | if defaultIndex < range.location { 48 | let ignoredRange = NSRange( 49 | location: defaultIndex, 50 | length: range.location - defaultIndex 51 | ) 52 | let ignoredString = nsString 53 | .substring(with: ignoredRange) 54 | keywords.append(ignoredString) 55 | } 56 | preTokenEndIndex = range.location + range.length - 1 57 | 58 | let keyWord = nsString 59 | .substring(with: NSRange(location: range.location, length: range.length)) 60 | keywords.append(keyWord) 61 | 62 | CFStringTokenizerAdvanceToNextToken(ref) 63 | range = CFStringTokenizerGetCurrentTokenRange(ref) 64 | } 65 | 66 | if preTokenEndIndex + 1 < count { 67 | let ignoredLocation = preTokenEndIndex + 1 68 | let ignoredRange = NSRange(location: ignoredLocation, length: count - ignoredLocation) 69 | let ignoredString = nsString 70 | .substring(with: ignoredRange) 71 | keywords.append(ignoredString) 72 | } 73 | 74 | return keywords 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /LearnSwift/Helper/DDYLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYLog.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/19. 6 | // 7 | 8 | import Foundation 9 | import os 10 | 11 | /// subsystem for current project. 12 | /// - setup it when start a new project. 13 | private let subsystem = "com.ddy.log" 14 | 15 | /// Custom log system. 16 | final public class DDYLog { 17 | 18 | public struct Output: OptionSet { 19 | public let rawValue: Int 20 | 21 | public init(rawValue: Int) { 22 | self.rawValue = rawValue 23 | } 24 | 25 | public static let info = Output(rawValue: 1 << 0) 26 | public static let debug = Output(rawValue: 1 << 1) 27 | public static let warning = Output(rawValue: 1 << 2) 28 | public static let error = Output(rawValue: 1 << 3) 29 | 30 | public static let all: Output = [.info, .debug, .warning, .error] 31 | } 32 | 33 | /// setup log level and when does the log work. 34 | public static var output: Output = [.debug, .warning, .error] 35 | 36 | @available(iOS 10.0, *) 37 | /// level info 38 | static let infoLog = OSLog(subsystem: subsystem, category: "INFO") 39 | /// info level 40 | /// 41 | /// - Parameters: 42 | /// - string: log info description 43 | /// - fileName: file name 44 | /// - methodName: method name 45 | /// - lineNumber: line number 46 | public static func info(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { 47 | #if DEBUG 48 | let log = "\(fileName):line \(lineNumber) method:\(methodName):\(string)" 49 | if output.contains(.info) { 50 | if #available(iOS 10.0, *) { 51 | os_log("%@", log: infoLog, type: .info, log) 52 | } else { 53 | print(": %@", log) 54 | } 55 | } 56 | #endif 57 | } 58 | 59 | @available(iOS 10.0, *) 60 | /// level debug 61 | static let debugLog = OSLog(subsystem: subsystem, category: "DEBUG") 62 | /// debug level 63 | /// 64 | /// - Parameters: 65 | /// - string: log info description 66 | /// - fileName: file name 67 | /// - methodName: method name 68 | /// - lineNumber: line number 69 | public static func debug(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { 70 | #if DEBUG 71 | let log = "\(fileName):line \(lineNumber) method:\(methodName):\(string)" 72 | if output.contains(.debug) { 73 | if #available(iOS 10.0, *) { 74 | os_log("%@", log: debugLog, type: .debug, log) 75 | } else { 76 | print(": %@", log) 77 | } 78 | } 79 | #endif 80 | } 81 | 82 | @available(iOS 10.0, *) 83 | /// level warning 84 | static let warningLog = OSLog(subsystem: subsystem, category: "WARNING") 85 | public static func warning(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { 86 | if output.contains(.warning) { 87 | let log = "\(fileName):line \(lineNumber) method:\(methodName):\(string)" 88 | if #available(iOS 10.0, *) { 89 | os_log("%@", log: warningLog, type: .fault, log) 90 | } else { 91 | print(": %@", string) 92 | } 93 | } 94 | } 95 | 96 | @available(iOS 10.0, *) 97 | /// level error 98 | static let errorLog = OSLog(subsystem: subsystem, category: "ERROR") 99 | public static func error(_ string: String, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line) { 100 | if output.contains(.error) { 101 | let log = "\(fileName):line \(lineNumber) method:\(methodName):\n\(string)" 102 | if #available(iOS 10.0, *) { 103 | os_log("%@", log: errorLog, type: .error, log) 104 | } else { 105 | print(": %@", string) 106 | } 107 | } 108 | } 109 | 110 | /// just output in console. 111 | /// 112 | /// - Parameters: 113 | /// - message: output message. 114 | /// - fileName: file name. 115 | /// - methodName: method name. 116 | /// - lineNumber: line number. 117 | static func out(message: N, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line){ 118 | #if DEBUG 119 | print("\(fileName):\(lineNumber) \(methodName):\(message)"); 120 | #endif 121 | } 122 | } 123 | // [swift 自定义Log,提升调试效率](https://www.jianshu.com/p/5946b4859e10) 124 | // [探索 Swift 中的日志系统](https://www.136.la/jingpin/show-13384.html) 125 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DDYSwift/LearnSwift/67836281a381c18e2b3f699b871e07e32a213853/LearnSwift/LearnSwift.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | LearnSwift.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | window = UIWindow.init(frame: UIScreen.main.bounds) 10 | window?.makeKeyAndVisible() 11 | window?.backgroundColor = UIColor.white 12 | window?.rootViewController = UINavigationController.init(rootViewController: ViewController()) 13 | return true 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "2x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "83.5x83.5" 82 | }, 83 | { 84 | "idiom" : "ios-marketing", 85 | "scale" : "1x", 86 | "size" : "1024x1024" 87 | } 88 | ], 89 | "info" : { 90 | "author" : "xcode", 91 | "version" : 1 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LearnSwift/LearnSwift/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ViewController: UIViewController { 4 | 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | DataType.testCreateDictSomeMethod() 8 | } 9 | 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /LearnSwift/Network/DDYNetInfo.swift: -------------------------------------------------------------------------------- 1 | import CoreTelephony 2 | 3 | public class DDYNetInfo: CustomStringConvertible { 4 | 5 | /// 网络连接状态改变通知 6 | public static let reachabilityChangedNotification = Notification.Name(rawValue: "com.ddy.netdiag.notification.reachabilityChanged") 7 | private(set) var reachability: DDYRealReachability? 8 | /// 获取运营商信息 9 | var cellulars: [CTCarrier]? { 10 | let info = CTTelephonyNetworkInfo() 11 | if #available(iOS 12.0, *) { 12 | return info.serviceSubscriberCellularProviders?.map({ $0.value }) 13 | } else if let carrier = info.subscriberCellularProvider { 14 | return [carrier] 15 | } 16 | return nil 17 | } 18 | 19 | public var description: String { 20 | var externalNetworkIP: String? 21 | let semaphore = DispatchSemaphore(value: 0) // 创建信号量 22 | fetchExternalNetworkIP { (ip) in 23 | externalNetworkIP = ip 24 | semaphore.signal() // 在网络请求结束后发送通知信号 25 | } 26 | _ = semaphore.wait(timeout: .distantFuture) // 发送等待信号 27 | 28 | return """ 29 | status:\(reachability?.status.description ?? "unknown") 30 | cellulars:\(cellulars?.description ?? "unknown") 31 | 外网IP:\(externalNetworkIP ?? "unknown") 32 | """ 33 | } 34 | } 35 | 36 | // MARK: - Network Reachability monitor 37 | extension DDYNetInfo { 38 | /// 开始监听网络连接 39 | public func startMonitoring(_ host: String?) { 40 | guard reachability == nil else { return assert(false, "reachability started Monitoring") } 41 | 42 | if let host = host, let reachability = DDYRealReachability(host: host) { 43 | return startMonitoring(reachability) 44 | } 45 | 46 | if let reachability = DDYRealReachability() { 47 | return startMonitoring(reachability) 48 | } 49 | return assert(false, "reachability start Monitoring fail") 50 | } 51 | 52 | private func startMonitoring(_ reachability: DDYRealReachability) { 53 | reachability.monitorCallback = { (state) in 54 | DDYNetInfo.externalNetworkIP = nil // 当网络状态发生改变后将此属性置为nil 55 | 56 | #if DEBUG 57 | print("⚠️⚠️⚠️ ----------------------------------------- ⚠️⚠️⚠️") 58 | print("net state:-> \(state.description)") 59 | print("⚠️⚠️⚠️ ----------------------------------------- ⚠️⚠️⚠️") 60 | #endif 61 | DispatchQueue.main.async { 62 | NotificationCenter.default.post(name: DDYNetInfo.reachabilityChangedNotification, object: nil) 63 | } 64 | } 65 | 66 | if !reachability.startMonitoring() { 67 | assert(false, "reachability start Monitoring fail") 68 | } 69 | 70 | self.reachability = reachability 71 | } 72 | 73 | /// 停止监控网络变化 74 | public func stopMonitoring() { 75 | reachability?.stopMonitoring() 76 | } 77 | } 78 | 79 | // MARK: - External Network IP 80 | extension DDYNetInfo { 81 | /// 外网IP 82 | public private(set) static var externalNetworkIP: String? 83 | /// 查询外网IP 84 | public func fetchExternalNetworkIP(_ completion: @escaping (String?) -> Void) { 85 | if let ip = DDYNetInfo.externalNetworkIP { 86 | return completion(ip) 87 | } 88 | 89 | httpReq("https://ifconfig.me/ip") { (data, error) in 90 | if let data = data, let ip = String(data: data, encoding: .utf8) { 91 | DDYNetInfo.externalNetworkIP = ip 92 | return completion(ip) 93 | } 94 | self.httpReq("https://httpbin.org/ip") { (data, error) in 95 | if let data = data { 96 | do { 97 | let obj = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) 98 | if let ip = (obj as? [String: Any])?["origin"] as? String { 99 | DDYNetInfo.externalNetworkIP = ip 100 | return completion(ip) 101 | } 102 | } catch { 103 | assert(false, "error: \(error), data: \(data)") 104 | } 105 | } 106 | completion(nil) 107 | } 108 | } 109 | } 110 | 111 | private func httpReq(_ urlString: String, completion: @escaping (Data?, Error?) -> Void) { 112 | guard let url = URL(string: urlString) else { return assert(false, "url invalid.") } 113 | let req = URLRequest(url: url, timeoutInterval: 10.0) 114 | let session = URLSession(configuration: URLSessionConfiguration.default) 115 | let task = session.dataTask(with: req) { (data, _, error) in 116 | completion(data, error) 117 | } 118 | task.resume() 119 | } 120 | } 121 | 122 | // MARK: - 域名解析 123 | extension DDYNetInfo { 124 | /// 通过域名获取服务器DNS地址(IPv4 and IPv6) 125 | public func dns(_ hostName: String) -> [String]? { 126 | var ipv4DNSs = ipv4DNS(hostName) 127 | if let ipv6DNSs = ipv6DNS(hostName), !ipv6DNSs.isEmpty { 128 | if ipv4DNSs == nil { 129 | return ipv6DNSs 130 | } else { 131 | ipv4DNSs?.append(contentsOf: ipv6DNSs) 132 | } 133 | } 134 | return ipv4DNSs 135 | } 136 | 137 | /// 通过域名获取服务器DNS地址(Only IPv4 or IPv6) 138 | public func dnsIPv4OrIPv6(_ hostName: String) -> [String]? { 139 | let ipv4DNSs = ipv4DNS(hostName) 140 | // 由于在IPV6环境下不能用IPV4的地址进行连接监测,所以只返回IPV6的服务器DNS地址 141 | if let ipv6DNSs = ipv6DNS(hostName), !ipv6DNSs.isEmpty { 142 | return ipv6DNSs 143 | } 144 | return ipv4DNSs 145 | } 146 | 147 | public func ipv4DNS(_ hostName: String) -> [String]? { 148 | let hostN = Array(hostName.utf8CString) 149 | guard let phot = gethostbyname(hostN), let addr_list = phot.pointee.h_addr_list else { return nil } 150 | 151 | var result: [String] = [] 152 | var j = 0 153 | while let addr = addr_list[j] { 154 | var ip_addr = in_addr() 155 | memcpy(&ip_addr, addr, MemoryLayout.size) 156 | var ip = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) 157 | inet_ntop(AF_INET, &ip_addr, &ip, socklen_t(INET_ADDRSTRLEN)) 158 | if let ipAddress = String(utf8String: ip) { 159 | result.append(ipAddress) 160 | } 161 | j += 1 162 | } 163 | return result 164 | } 165 | 166 | public func ipv6DNS(_ hostName: String) -> [String]? { 167 | let hostN = Array(hostName.utf8CString) 168 | // 只有在IPV6的网络下才会有返回值 169 | guard let phot = gethostbyname2(hostN, AF_INET6), let addr_list = phot.pointee.h_addr_list else { return nil } 170 | 171 | var result: [String] = [] 172 | var j = 0 173 | while let addr = addr_list[j] { 174 | var ip6_addr = in6_addr() 175 | memcpy(&ip6_addr, addr, MemoryLayout.size) 176 | var ip = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN)) 177 | inet_ntop(AF_INET6, &ip6_addr, &ip, socklen_t(INET6_ADDRSTRLEN)) 178 | if let ipAddress = String(utf8String: ip) { 179 | result.append(ipAddress) 180 | } 181 | j += 1 182 | } 183 | return result 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /LearnSwift/Network/DDYNetdiag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYNetdiag.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class DDYNetdiag { 11 | 12 | public static let shared = { return DDYNetdiag() }() 13 | 14 | public private(set) lazy var netInfo = DDYNetInfo() 15 | 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /LearnSwift/Network/DDYRealReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYRealReachability.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/19. 6 | // 7 | 8 | import CoreTelephony 9 | import SystemConfiguration 10 | 11 | public class DDYRealReachability { 12 | private let reachability: SCNetworkReachability 13 | private var queue: DispatchQueue = DispatchQueue(label: "com.ddy.netdiag.queue.reachability") 14 | 15 | public var monitorCallback: ((Status) -> Void)? 16 | 17 | public var isReachable: Bool { return isReachableOnCellular || isReachableOnWiFi } 18 | public var isReachableOnCellular: Bool { return status.isCellular } 19 | public var isReachableOnWiFi: Bool { return status == .reachable(.wifi) } 20 | public var status: Status { return flags.map(Status.init) ?? .unknown } 21 | 22 | private var previousStatus: Status? 23 | private var flags: SCNetworkReachabilityFlags? { 24 | var flags = SCNetworkReachabilityFlags() 25 | return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil 26 | } 27 | 28 | public init?() { 29 | var zero = sockaddr() 30 | zero.sa_len = UInt8(MemoryLayout.size) 31 | zero.sa_family = sa_family_t(AF_INET) 32 | guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil } 33 | self.reachability = reachability 34 | } 35 | 36 | public init?(host: String) { 37 | guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } 38 | self.reachability = reachability 39 | } 40 | 41 | deinit { 42 | stopMonitoring() 43 | } 44 | 45 | public func startMonitoring() -> Bool { 46 | var context = SCNetworkReachabilityContext(version: 0, info: Unmanaged.passRetained(self).toOpaque(), retain: nil, release: nil, copyDescription: nil) 47 | let callback: SCNetworkReachabilityCallBack = { (_, flags, info) in 48 | let observer = info.map { Unmanaged.fromOpaque($0).takeUnretainedValue() } 49 | observer?.reachabilityDidChange(with: flags) 50 | } 51 | 52 | let callbackEnabled = SCNetworkReachabilitySetCallback(reachability, callback, &context) 53 | let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, queue) 54 | 55 | // Manually call listener to give initial state, since the framework may not. 56 | if let currentFlags = flags { 57 | queue.async { 58 | self.reachabilityDidChange(with: currentFlags) 59 | } 60 | } 61 | return callbackEnabled && queueEnabled 62 | } 63 | 64 | public func stopMonitoring() { 65 | SCNetworkReachabilitySetCallback(reachability, nil, nil) 66 | SCNetworkReachabilitySetDispatchQueue(reachability, nil) 67 | previousStatus = nil 68 | } 69 | 70 | private func reachabilityDidChange(with flags: SCNetworkReachabilityFlags) { 71 | let newStatus = Status(flags) 72 | guard previousStatus != newStatus else { return } 73 | previousStatus = newStatus 74 | 75 | queue.async { self.monitorCallback?(newStatus) } 76 | } 77 | } 78 | 79 | extension DDYRealReachability { 80 | public enum Status: CustomStringConvertible { 81 | public enum ConnectionType { 82 | case wifi 83 | case cellular 84 | case cellular2G 85 | case cellular3G 86 | case cellular4G 87 | //case cellular5G 88 | } 89 | case unknown 90 | case notReachable 91 | case reachable(ConnectionType) 92 | 93 | public var isCellular: Bool { 94 | guard case .reachable(let type) = self else { return false } 95 | return type == .cellular || type == .cellular2G || type == .cellular3G || type == .cellular4G 96 | } 97 | 98 | init(_ flags: SCNetworkReachabilityFlags) { 99 | guard flags.isActuallyReachable else { self = .notReachable; return } 100 | var networkStatus: Status 101 | if flags.isCellular { 102 | let phonyNetwork = CTTelephonyNetworkInfo() 103 | if let radioAccessTechnology = phonyNetwork.currentRadioAccessTechnology { 104 | if radioAccessTechnology == CTRadioAccessTechnologyLTE { 105 | networkStatus = .reachable(.cellular4G) 106 | } else if radioAccessTechnology == CTRadioAccessTechnologyEdge || radioAccessTechnology == CTRadioAccessTechnologyGPRS { 107 | networkStatus = .reachable(.cellular2G) 108 | } else { 109 | networkStatus = .reachable(.cellular3G) 110 | } 111 | } else { 112 | if flags.isTransientConnection { 113 | if flags.isConnectionRequired { 114 | networkStatus = .reachable(.cellular2G) 115 | } else { 116 | networkStatus = .reachable(.cellular3G) 117 | } 118 | } else { 119 | assert(false, "Unknown cellular ???") // debug 120 | networkStatus = .reachable(.cellular) 121 | } 122 | } 123 | } else { 124 | networkStatus = .reachable(.wifi) 125 | } 126 | self = networkStatus 127 | } 128 | 129 | public var description: String { 130 | switch self { 131 | case .unknown: return "unknown" 132 | case .notReachable: return "notReachable" 133 | case .reachable(let type): 134 | switch type { 135 | case .wifi: return "wifi" 136 | case .cellular: return "cellular" // 未知蜂窝网络类型 137 | case .cellular2G: return "2G" 138 | case .cellular3G: return "3G" 139 | case .cellular4G: return "4G" 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | extension DDYRealReachability.Status: Equatable {} 147 | extension SCNetworkReachabilityFlags { 148 | var isReachable: Bool { return contains(.reachable) } 149 | var isTransientConnection: Bool { return contains(.transientConnection) } 150 | var isConnectionRequired: Bool { return contains(.connectionRequired) } 151 | var canConnectAutomatically: Bool { return contains(.connectionOnDemand) || contains(.connectionOnTraffic) } 152 | var canConnectWithoutUserInteraction: Bool { return canConnectAutomatically && !contains(.interventionRequired) } 153 | var isActuallyReachable: Bool { return isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) } 154 | var isCellular: Bool { return contains(.isWWAN) } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /LearnSwift/Network/DDYTCPPing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYTCPPing.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum DDYNetdiagError: Error { 11 | case accessingDNSFailed // Problem accessing the DNS 12 | case ipResolutionFailed 13 | } 14 | public protocol DDYTCPPingDelegate: AnyObject { 15 | func tcpPing(_ pinger: DDYTCPPing, output line: String) 16 | func tcpPing(_ pinger: DDYTCPPing, didFailWith error: Error) 17 | } 18 | 19 | public class DDYTCPPing { 20 | static private let kRequestStoped: Int32 = -2 // 中途取消的状态码 21 | 22 | public struct Result: CustomStringConvertible { 23 | public let code: Int32 24 | public let ip: String? 25 | public let maxTime: TimeInterval 26 | public let minTime: TimeInterval 27 | public let avgTime: TimeInterval 28 | public let loss: UInt32 29 | public let count: UInt32 30 | public let totalTime: TimeInterval 31 | public let stddev: TimeInterval 32 | 33 | public var description: String { 34 | if code == 0 || code == DDYTCPPing.kRequestStoped { 35 | return String(format: "tcp connect min/avg/max = %.3f/%.3f/%.3fms", minTime, avgTime, maxTime) 36 | } 37 | return "tcp connect failed \(code)" 38 | } 39 | } 40 | 41 | private let host: String 42 | private let port: UInt16 43 | private let count: UInt32 44 | private var stopped: Bool = false 45 | private weak var delegate: DDYTCPPingDelegate? 46 | private let completion: ((Result) -> Void)? 47 | 48 | deinit { 49 | #if DEBUG 50 | print("DDYTCPPing release memory.") 51 | #endif 52 | } 53 | 54 | public init(host: String, port: UInt16 = 80, count: UInt32, delegate: DDYTCPPingDelegate?, completion: ((Result) -> Void)?) { 55 | self.host = host 56 | self.port = port 57 | self.count = count 58 | self.delegate = delegate 59 | self.completion = completion 60 | } 61 | 62 | public func start() { 63 | DispatchQueue.global().async { self.run() } 64 | } 65 | 66 | public func stop() { 67 | stopped = true 68 | } 69 | 70 | private func run() { 71 | let begin = Date() 72 | delegate?.tcpPing(self, output: "connect to host \(host):\(port) ...\n") 73 | 74 | let hostaddr = Array(host.utf8CString) 75 | 76 | var addr = sockaddr_in() 77 | addr.sin_len = UInt8(MemoryLayout.size) 78 | addr.sin_family = sa_family_t(AF_INET) 79 | addr.sin_port = in_port_t(port).bigEndian 80 | addr.sin_addr.s_addr = inet_addr(hostaddr) 81 | 82 | if addr.sin_addr.s_addr == INADDR_NONE { 83 | guard let host = gethostbyname(hostaddr), let h_addr = host.pointee.h_addr_list?[0] else { 84 | delegate?.tcpPing(self, didFailWith: DDYNetdiagError.accessingDNSFailed) 85 | if let completion = completion { 86 | DispatchQueue.main.async { 87 | completion(self.buildResult(-1006, ip: nil, durations: nil, loss: 0, count: 0, total: 0)) 88 | } 89 | } 90 | return 91 | } 92 | addr.sin_addr = h_addr.withMemoryRebound(to: in_addr.self, capacity: 1) { $0.pointee } 93 | } 94 | 95 | guard let ipAddr = inet_ntoa(addr.sin_addr), let ip = String(utf8String: ipAddr) else { 96 | delegate?.tcpPing(self, didFailWith: DDYNetdiagError.ipResolutionFailed) 97 | return 98 | } 99 | delegate?.tcpPing(self, output: "connect to ip \(ip):\(port) ...\n") 100 | 101 | var intervals = [TimeInterval](repeating: 0, count: Int(count)) 102 | var r: Int32 = 0 103 | var loss: UInt32 = 0 104 | var connectCount: UInt32 = 0 105 | for index in 0.. Result { 133 | if code != 0 && code != DDYTCPPing.kRequestStoped { 134 | return Result(code: code, ip: ip, maxTime: 0, minTime: 0, avgTime: 0, loss: 1, count: 1, totalTime: time, stddev: 0) 135 | } 136 | var max: TimeInterval = 0 137 | var min: TimeInterval = 10000000 138 | var sum: TimeInterval = 0 139 | var sum2: TimeInterval = 0 140 | durations?.forEach({ 141 | if $0 > max { max = $0 } 142 | if $0 < min { min = $0 } 143 | sum += $0 144 | sum2 += $0 * $0 145 | }) 146 | 147 | let avg = sum / TimeInterval(count) 148 | let avg2 = sum2 / TimeInterval(count) 149 | let stddev = sqrt(avg2 - avg * avg) 150 | return Result(code: code, ip: ip, maxTime: max, minTime: min, avgTime: avg, loss: loss, count: count, totalTime: time, stddev: stddev) 151 | } 152 | 153 | private func conn(_ addr_in: UnsafePointer) -> Int32 { 154 | let sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 155 | if sock == -1 { 156 | return errno 157 | } 158 | 159 | var on: Int32 = 1 160 | assert(setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout.size(ofValue: on))) == 0) 161 | assert(setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, socklen_t(MemoryLayout.size(ofValue: on))) == 0) 162 | 163 | var timeout = timeval(tv_sec: 10, tv_usec: 0) 164 | assert(setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, socklen_t(MemoryLayout.size(ofValue: timeout))) == 0) 165 | assert(setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, socklen_t(MemoryLayout.size(ofValue: timeout))) == 0) 166 | 167 | let addr = addr_in.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size) { $0 } 168 | if connect(sock, addr, socklen_t(MemoryLayout.size)) < 0 { 169 | let err = errno 170 | close(sock) 171 | return err 172 | } 173 | close(sock) 174 | return 0 175 | } 176 | } 177 | 178 | -------------------------------------------------------------------------------- /LearnSwift/Network/DDYUDPTraceRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYUDPTraceRoute.swift 3 | // LearnSwift 4 | // 5 | // Created by ddy on 2022/9/19. 6 | // 7 | 8 | import Foundation 9 | import Darwin 10 | 11 | /// 监测TraceRoute命令的的输出到日志变量; 12 | public protocol DDYUDPTracerouteDelegae: AnyObject { 13 | func udpTraceroute(_ traceroute: DDYUDPTraceroute, output routelog: String) 14 | func udpTracerouteDidEnd(in traceroute: DDYUDPTraceroute) 15 | } 16 | /// TraceRoute网络监控 17 | /// 主要是通过模拟shell命令traceRoute的过程,监控网络站点间的跳转 18 | /// 默认执行20转,每转进行三次发送测速 19 | public class DDYUDPTraceroute { 20 | public private(set) var isRunning: Bool = false // 检测traceroute是否在运行 21 | private var udpPort: UInt16 // 执行端口 22 | private var maxTTL: UInt32 // 执行转数 23 | private var maxAttempts: UInt32 // 每转的发送次数 24 | private weak var delegate: DDYUDPTracerouteDelegae? 25 | 26 | /// 初始化 27 | /// - Parameters: 28 | /// - ttl: 执行转数(建议30) 29 | /// - attempts: 每转的发送次数(建议3) 30 | /// - port: 执行端口(默认 30001) 31 | public init(ttl: UInt32, attempts: UInt32, port: UInt16 = 30001, delegate: DDYUDPTracerouteDelegae?) { 32 | self.maxTTL = ttl 33 | self.udpPort = port 34 | self.maxAttempts = attempts 35 | self.delegate = delegate 36 | } 37 | 38 | /// 监控 tranceroute 路径 39 | public func startTrace(_ host: String) -> Bool { 40 | // 从name server获取server主机的地址 41 | guard let serverDNSs = DDYNetdiag.shared.netInfo.dns(host), !serverDNSs.isEmpty else { 42 | delegate?.udpTraceroute(self, output: "TraceRoute>>> Could not get host address") 43 | delegate?.udpTracerouteDidEnd(in: self) 44 | return false 45 | } 46 | 47 | let ipAddr0 = serverDNSs[0] 48 | // 设置server主机的套接口地址 49 | let addrData: Data 50 | let isIPV6: Bool 51 | if ipAddr0.range(of: ":") == nil { 52 | isIPV6 = false 53 | var nativeAddr4 = sockaddr_in() 54 | nativeAddr4.sin_len = UInt8(MemoryLayout.size(ofValue: nativeAddr4)) 55 | nativeAddr4.sin_family = sa_family_t(AF_INET) 56 | nativeAddr4.sin_port = in_port_t(udpPort).bigEndian 57 | inet_pton(AF_INET, Array(ipAddr0.utf8CString), &nativeAddr4.sin_addr.s_addr) 58 | 59 | addrData = Data(bytes: &nativeAddr4, count: MemoryLayout.size(ofValue: nativeAddr4)) 60 | } else { 61 | isIPV6 = true 62 | var nativeAddr6 = sockaddr_in6() 63 | nativeAddr6.sin6_len = UInt8(MemoryLayout.size(ofValue: nativeAddr6)) 64 | nativeAddr6.sin6_family = sa_family_t(AF_INET6) 65 | nativeAddr6.sin6_port = in_port_t(udpPort).bigEndian 66 | inet_pton(AF_INET6, Array(ipAddr0.utf8CString), &nativeAddr6.sin6_addr) 67 | 68 | addrData = Data(bytes: &nativeAddr6, count: MemoryLayout.size(ofValue: nativeAddr6)) 69 | } 70 | let destination = addrData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: sockaddr.self) } 71 | // 初始化套接口 72 | var fromAddr = sockaddr() 73 | var error: Bool = false 74 | 75 | isRunning = true 76 | 77 | // 创建一个支持ICMP协议的UDP网络套接口(用于接收) 78 | let recv_sock = socket(Int32(destination.pointee.sa_family), SOCK_DGRAM, isIPV6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP) 79 | if recv_sock < 0 { 80 | delegate?.udpTraceroute(self, output: "TraceRoute>>> Could not create recv socket") 81 | delegate?.udpTracerouteDidEnd(in: self) 82 | return false 83 | } 84 | 85 | // 创建一个UDP套接口(用于发送) 86 | let send_sock = socket(Int32(destination.pointee.sa_family), SOCK_DGRAM, 0) 87 | if send_sock < 0 { 88 | delegate?.udpTraceroute(self, output: "TraceRoute>>> Could not create xmit socket") 89 | delegate?.udpTracerouteDidEnd(in: self) 90 | return false 91 | } 92 | 93 | let cmsg = "GET / HTTP/1.1\r\n\r\n" 94 | var n = socklen_t(MemoryLayout.size(ofValue: fromAddr)) 95 | var buf = [CChar](repeating: 0, count: 100) 96 | //var readBuffer: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE) 97 | 98 | var ttl: UInt32 = 1 // index sur le TTL en cours de traitement. 99 | var timeoutTTL: Int32 = 0 100 | var icmp: Bool = false // Positionné à true lorsqu'on reçoit la trame ICMP en retour. 101 | var startTime: Date // Timestamp lors de l'émission du GET HTTP 102 | var delta: TimeInterval // Durée de l'aller-retour jusqu'au hop. 103 | 104 | // On progresse jusqu'à un nombre de TTLs max. 105 | while ttl <= maxTTL { 106 | memset(&fromAddr, 0, MemoryLayout.size(ofValue: fromAddr)) 107 | // 设置sender 套接字的ttl 108 | let setResult: Int32 109 | if isIPV6 { 110 | setResult = setsockopt(send_sock,IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, socklen_t(MemoryLayout.size(ofValue: ttl))) 111 | } else { 112 | setResult = setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, socklen_t(MemoryLayout.size(ofValue: ttl))) 113 | } 114 | if (setResult < 0) { 115 | error = true 116 | delegate?.udpTraceroute(self, output: "TraceRoute>>> setsockopt failled") 117 | } 118 | 119 | // 每一步连续发送maxAttenpts报文 120 | icmp = false 121 | var traceTTLLog = String(repeating: "", count: 20) 122 | traceTTLLog.append("\(ttl)\t") 123 | var hostAddress = "***" 124 | for `try` in 0...size : MemoryLayout.size)) 128 | if (sentLen != cmsg.count) { 129 | print("Error sending to server: \(errno) \(sentLen)") 130 | error = true 131 | traceTTLLog.append("*\t") 132 | } 133 | 134 | //从(已连接)套接口上接收数据,并捕获数据发送源的地址。 135 | if (-1 == fcntl(recv_sock, F_SETFL, O_NONBLOCK)) { 136 | print("fcntl socket error!\n") 137 | return false 138 | } 139 | 140 | /* set recvfrom from server timeout */ 141 | var tv = timeval() 142 | var readfds = fd_set() 143 | tv.tv_sec = 1 144 | tv.tv_usec = 0 //设置了1s的延迟 145 | readfds.ddy_zero() // FD_ZERO 146 | readfds.ddy_set(recv_sock) // FD_SET 147 | select(recv_sock + 1, &readfds, nil, nil, &tv) 148 | 149 | if readfds.ddy_isSet(recv_sock) { 150 | timeoutTTL = 0 151 | if recvfrom(recv_sock, &buf, 100, 0, &fromAddr, &n) < 0 { 152 | error = true 153 | traceTTLLog.append(String(format: "%s\t", strerror(errno))) 154 | } else { 155 | icmp = true 156 | delta = Date().timeIntervalSince(startTime) * 1000 157 | 158 | //将“二进制整数” -> “点分十进制,获取hostAddress和hostName 159 | if (fromAddr.sa_family == AF_INET) { 160 | var display = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) 161 | let fromAddr_in = withUnsafeBytes(of: &fromAddr) { $0.baseAddress!.assumingMemoryBound(to: sockaddr_in.self) } 162 | var s_fromAddr = fromAddr_in.pointee.sin_addr.s_addr 163 | inet_ntop(AF_INET, &s_fromAddr, &display, socklen_t(INET_ADDRSTRLEN)) 164 | hostAddress = String(utf8String: display) ?? String(format: "%s", display) 165 | } else if (fromAddr.sa_family == AF_INET6) { 166 | var ip = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN)) 167 | let fromAddr_in6 = withUnsafeBytes(of: &fromAddr) { $0.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self) } 168 | var sin6_fromAddr = fromAddr_in6.pointee.sin6_addr 169 | inet_ntop(AF_INET6, &sin6_fromAddr, &ip, socklen_t(INET6_ADDRSTRLEN)) 170 | hostAddress = String(utf8String: ip) ?? String(format: "%s", ip) 171 | } 172 | 173 | if (`try` == 0) { 174 | traceTTLLog.append("\(hostAddress)\t\t") 175 | } 176 | traceTTLLog.append(String(format: "%0.2fms\t", delta)) 177 | } 178 | } else { 179 | timeoutTTL += 1 180 | break 181 | } 182 | // On teste si l'utilisateur a demandé l'arrêt du traceroute 183 | if !isRunning { 184 | ttl = maxTTL 185 | icmp = true // On force le statut d'icmp pour ne pas générer un Hop en sortie de boucle 186 | break 187 | } 188 | } 189 | 190 | // 输出报文,如果三次都无法监控接收到报文,跳转结束 191 | if icmp { 192 | delegate?.udpTraceroute(self, output: traceTTLLog) 193 | } else { 194 | // 如果连续三次接收不到icmp回显报文 195 | if timeoutTTL >= 4 { 196 | break 197 | } else { 198 | delegate?.udpTraceroute(self, output: "\(ttl)\t********\t") 199 | } 200 | } 201 | 202 | if hostAddress == ipAddr0 { break } 203 | ttl += 1 204 | } 205 | 206 | isRunning = false 207 | delegate?.udpTracerouteDidEnd(in: self) // On averti le delegate que le traceroute est terminé. 208 | return !error 209 | } 210 | 211 | /// 停止traceroute 212 | public func stopTrace() { 213 | isRunning = false 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /LearnSwift/Network/LPFDSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DDYFDSet.swift 3 | // DDYNetdiagDemo 4 | // 5 | // Created by pengli on 2019/11/28. 6 | // Copyright © 2019 pengli. All rights reserved. 7 | // 8 | 9 | import Darwin 10 | 11 | private let ddy_fd_set_count = Int(__DARWIN_FD_SETSIZE) / 32 12 | extension fd_set { 13 | @inline(__always) 14 | mutating func ddy_withCArrayAccess(block: (UnsafeMutablePointer) throws -> T) rethrows -> T { 15 | return try withUnsafeMutablePointer(to: &fds_bits) { 16 | try block(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Int32.self)) 17 | } 18 | } 19 | 20 | @inline(__always) 21 | private static func ddy_address(for fd: Int32) -> (Int, Int32) { 22 | var intOffset = Int(fd) / ddy_fd_set_count 23 | #if _endian(big) 24 | if intOffset % 2 == 0 { 25 | intOffset += 1 26 | } else { 27 | intOffset -= 1 28 | } 29 | #endif 30 | let bitOffset = Int(fd) % ddy_fd_set_count 31 | let mask = Int32(bitPattern: UInt32(1 << bitOffset)) 32 | return (intOffset, mask) 33 | } 34 | 35 | /// 将fd_set归零(替换FD_ZERO宏) 36 | public mutating func ddy_zero() { 37 | ddy_withCArrayAccess { $0.initialize(repeating: 0, count: ddy_fd_set_count) } 38 | } 39 | 40 | /// 在fd_set中设置一个fd(替换FD_SET宏) 41 | /// - Parameter fd: 要添加到fd_set的fd 42 | public mutating func ddy_set(_ fd: Int32) { 43 | let (index, mask) = fd_set.ddy_address(for: fd) 44 | ddy_withCArrayAccess { $0[index] |= mask } 45 | } 46 | 47 | /// 从fd_set清除fd(替换FD_CLR宏) 48 | /// - Parameter fd: 从fd_set清除的fd 49 | public mutating func ddy_clear(_ fd: Int32) { 50 | let (index, mask) = fd_set.ddy_address(for: fd) 51 | ddy_withCArrayAccess { $0[index] &= ~mask } 52 | } 53 | 54 | /// 检查fd_set中是否存在fd(替换FD_ISSET宏) 55 | public mutating func ddy_isSet(_ fd: Int32) -> Bool { 56 | let (index, mask) = fd_set.ddy_address(for: fd) 57 | return ddy_withCArrayAccess { $0[index] & mask != 0 } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LearnSwift 2 | 3 | 4 | > #### [Swift1-数据类型 元组 枚举 结构体](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift001.md) 5 | > #### [Swift2-运算符和流程控制语句](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift002.md) 6 | > #### [Swift3-访问修饰词 函数 闭包](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift003.md) 7 | > #### [Swift4-类 属性 协议 范型 扩展](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift004.md) 8 | > #### [Swift5-转型 可选链 其他知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift005.md) 9 | > #### [Swift6-多线程](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift006.md) 10 | > #### [Swift7-高阶函数 map flatMap compactMap filter reduce](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift007.md) 11 | > #### [Swift8-琐碎知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift008.md) 12 | > #### [Swift9-加密](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift009.md) 13 | > #### [Swift10-UIview UILabel UIButton](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift010.md) 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Swift/AVFoundation.md: -------------------------------------------------------------------------------- 1 | * [问题解决:AVAudioSession的中断处理方法失效](https://blog.csdn.net/lixing333/article/details/50333553) 2 | * [kAudioSessionProperty_AudioCategory 的设置](www.bubuko.com/infodetail-1422227.html) 3 | * [iOS音频被QQ音乐 中断的处理](https://www.jianshu.com/p/e24e90a9c708) 4 | * [一篇对iOS音频比较完善的文章](https://blog.csdn.net/mandagod/article/details/80603376) 5 | * -------------------------------------------------------------------------------- /Swift/Blogs.md: -------------------------------------------------------------------------------- 1 | * [吐司提示](https://github.com/tattn/TTToast) 2 | * [神笔马良——基于 Metal 的涂鸦框架](https://www.jianshu.com/p/13849a90064a) 3 | * [MoreCodable](https://github.com/tattn/MoreCodable) 4 | * [ICMP SwiftPing](https://github.com/ankitthakur/SwiftPing) 5 | * [ICMP SwiftPing](https://github.com/samiyr/SwiftyPing) 6 | * [ConcurrentPinger](https://github.com/krispis1/ConcurrentPinger) 7 | * [ICMP SPLPing](https://github.com/OliverLetterer/SPLPing) 8 | * [GBPing](https://github.com/lmirosevic/GBPing) 9 | * [iOSPingTester](https://github.com/6ag/iOSPingTester) 10 | * [ping 网络诊断](https://github.com/mediaios/net-diagnosis) 11 | * [丢包率 测速](https://github.com/PowerCC/MobileSpeed) 12 | * [网络测速](https://github.com/rongwf/NetworkSpeed) 13 | * [启动时间分析](https://www.jianshu.com/p/98a9963288da) 14 | * [音视频基础](luohuagu.com/2021/10/11/audio-video-basic/) 15 | * [Audio Unit详解(一)实时变调音效处理](https://www.jianshu.com/p/0fe2142fb863) 16 | * [phone 播放amr](https://blog.csdn.net/chenyong05314/article/details/8893056) 17 | * [iOS RTMP 推流代码分析](luohuagu.com/2021/10/11/ios-rtmp-push/) 18 | * [对视频进行各种美化处理](https://github.com/xujingzhou/VideoBeautify) 19 | * [xujingzhou 视频处理相关](https://github.com/xujingzhou?tab=repositories) 20 | * [SSAudioQueueRecording](https://github.com/Michael-iOSer/SSAudioQueueRecording) 21 | * [SSAudioQueuePlayer](https://github.com/Michael-iOSer/SSAudioQueuePlayer) 22 | * [推流框架LFLiveKit](https://github.com/LaiFengiOS/LFLiveKit) 23 | * [直播总结](https://github.com/guoxiaopang/LiveExplanation) 24 | * [图片处理](https://github.com/yackle/CLImageEditor) 25 | * [特效相机](https://github.com/ZZZZou/AwemeLike) 26 | * [Label效果](https://github.com/kciter/GlitchLabel) 27 | * [WasmPatch](https://github.com/everettjf/WasmPatch) 28 | * [OCRunner](https://github.com/SilverFruity/OCRunner) 29 | * [热修复](https://www.sotvm.com/payment/pricing) 30 | * [Mac系统打开dmg出现 “资源忙” 解决方法](https://www.jianshu.com/p/397e762025d2) 31 | * [SwiftHook](https://github.com/623637646/SwiftHook) 32 | * [JSBox中幽灵触发器的实现原理探索](https://juejin.cn/post/6844903750700793863) 33 | * [老司机 iOS 周报 #51 | 2019-01-07](kmanong.top/kmn/qxw/form/article?id=2283&cate=49) 34 | * [iOS15 UITableView分组高度出现异常的问题](https://www.jianshu.com/p/9d4b791ca8c4) 35 | * [适配iOS15 & Xcode 13](https://www.jianshu.com/p/54b3a053c250) 36 | * [rxswift 运用](https://github.com/LeoMobileDeveloper/awesome-rxswift) 37 | * [RN](https://github.com/LeoMobileDeveloper/ReactNativeMaterials) 38 | * [QTEventBus](https://github.com/LeoMobileDeveloper/QTEventBus) 39 | * [UITableView 优化](https://github.com/LeoMobileDeveloper/Blogs/blob/master/Swift/Elegant%20TableView.md) 40 | * [张芳涛 Metal](https://github.com/zhangfangtaozft/Metal-Tutorial) 41 | * [落影](https://github.com/loyinglin) 42 | * [蒸米](https://github.com/zhengmin1989) 43 | * [Mask](https://github.com/hellochenms/WindowAndScenery) 44 | * [MetalFilters](https://github.com/alexiscn/MetalFilters) 45 | * [swift 微信](https://github.com/alexiscn/WeChatSwift) 46 | * [Metal 过渡效果](https://github.com/alexiscn/MTTransitions) 47 | * [SynologyKit](https://github.com/alexiscn/SynologyKit#features) 48 | * [swift 版本变化内容](https://www.hackingwithswift.com/swift) 49 | * [swiftUI 版 IGListKit](https://github.com/GoodOpenRepo/Carbon) 50 | * [Android 版 IGListKit](https://github.com/airbnb/epoxy) 51 | * [airbnb HorizonCalendar](https://github.com/airbnb/HorizonCalendar) 52 | * [MagazineLayout](https://github.com/airbnb/MagazineLayout) 53 | * [表单](https://github.com/ra1028/Former) 54 | * [RTMP推流](https://github.com/cats-oss/VideoCast-Swift) 55 | * [直播技术总结](https://github.com/guoxiaopang/LiveExplanation) 56 | * [弹幕](https://github.com/panghaijiao/HJDanmakuDemo) 57 | * [APNG_Optimizer](https://sourceforge.net/projects/apng/files/APNG_Optimizer/) 58 | * [高仿映客直播](https://github.com/shawn-tangsc/inke-demo) 59 | * [LXPlayerLive](https://github.com/SoftProgramLX/LXPlayerLive) 60 | * [如何给TableView、CollectionView添加动效](https://juejin.cn/post/6844903564704219143) 61 | * [iOS CollectionView 的那些事](https://juejin.cn/post/6844903813300617229) 62 | * [Vision_Demo](https://github.com/bigsen/Vision_Demo) 63 | * [VillagePong cocos2d](https://github.com/EatMyBubbles/VillagePong) 64 | * [DebugMenu](https://github.com/noppefoxwolf/DebugMenu) 65 | * [280多个git仓库](https://github.com/noppefoxwolf?tab=repositories) 66 | * [360多个git仓库](https://github.com/hansemannn) 67 | * [vision qrcode](https://github.com/hansemannn/iOS11-QR-Code-Example) 68 | * [PDTSimpleCalendar](https://github.com/jivesoftware/PDTSimpleCalendar) 69 | * [jivesoftware](https://github.com/jivesoftware) 70 | * [DeveloperErenLiu 学习文章](https://github.com/DeveloperErenLiu) 71 | * [flutter_learning](https://gitee.com/luopengfei66/flutter_learning) 72 | * [银行卡、身份证、门牌号光学识别](https://github.com/evilgix/Evil) 73 | * [微博抖音视频播放样式](https://github.com/1019459067/AVPlayerDemo) 74 | * [TCP-UDP-Socket](https://github.com/1019459067/TCP-UDP-Socket) 75 | * [相机](https://github.com/1019459067/CameraDemo) 76 | * [UITextView中,如何对特殊文本进行整体绑定](https://www.jianshu.com/p/891275b93d29) 77 | * [自定义弹出菜单](https://github.com/kouliang/KLPopMenu) 78 | * [自定义弹出菜单](https://github.com/1019459067/MenuDemo) 79 | * [OC/Swift高级进阶](https://blog.csdn.net/zhonggaorong/category_10772204.html) 80 | * [iOS开发之BLE(一)——理论知识](https://www.jianshu.com/p/fecd0d89d29b) 81 | * [iOS 三方集锦](https://github.com/gaoguanbao/iOSThird-SDK/tree/225d381b6eb871534873b6eb02b38067083f898b) 82 | * [Audio Unit详解(一)实时变调音效处理](https://www.jianshu.com/p/0fe2142fb863) 83 | * [聊天 撤回](https://github.com/sagesse-cn/swift-nornir) 84 | * [播放在线音频](https://github.com/GoodOpenRepo/AudioStreamer) 85 | * [播放在线音频](https://github.com/GoodOpenRepo/FreeStreamer) 86 | * [FMOD IOS 变声](https://github.com/zhaochengfeng/FMODIOS) 87 | * [崔江涛 音频](https://www.cnblogs.com/kenshincui/p/4186022.html#soundEffect) 88 | * [编辑视频](https://github.com/wemmet/RDVEUIDemo) 89 | * [iOS 13-Sign In with Apple](https://www.jianshu.com/p/e1284bd8c72a) 90 | * [ML GAN](https://github.com/TachibanaYoshino?tab=repositories) 91 | * [https://github.com/john-rocky/CoreML-Models](https://github.com/john-rocky/CoreML-Models) 92 | * [hollance/CoreMLHelpers](https://github.com/hollance/CoreMLHelpers) 93 | * [converting-pix2pix-to-coreml-model-ef49c7819102](https://rockyshikoku.medium.com/converting-pix2pix-to-coreml-model-ef49c7819102) 94 | * [demo](https://github.com/Xiaoye220/Demos) 95 | * [Awesome-GAN-Papers](https://github.com/happy-jihye/Awesome-GAN-Papers) 96 | * [一些扩展](https://github.com/tungxuan1656/swift-helper) 97 | * [学习蓝牙流程](https://github.com/NicolasNC/NCBluetooth) 98 | * [tensorflowliteswift 风格迁移](https://github.com/altonelli/FastStyleTransferDemo) 99 | * [SwiftUI接UIkit](https://github.com/EriaWist/My_Article/tree/a4f110036249f6e9aa14394be8c37e70e4f5de5b/Swift/筆記(README是簡介)/Swift-SwiftUI接UIkit) 100 | * [Swift中的@objc、@objcMembers关键字探讨](https://juejin.cn/post/6941575685580750862) 101 | * [Swift 5.5带来了async/await和actor支持](https://juejin.cn/post/6975036374105718820) 102 | 103 | ``` 104 | 105 | /// 视频分解成帧 106 | /// - parameter fileUrl : 视频地址 107 | /// - parameter fps : 自定义帧数 每秒内取的帧数 108 | /// - parameter splitCompleteClosure : 回调 109 | func splitVideoFileUrlFps(splitFileUrl:URL, fps:Float, splitCompleteClosure:@escaping (Bool, [UIImage]) -> Void) { 110 | var splitImages = [UIImage]() 111 | let optDict = NSDictionary(object: NSNumber(value: false), forKey: AVURLAssetPreferPreciseDurationAndTimingKey as NSCopying) 112 | let urlAsset = AVURLAsset(url: splitFileUrl, options: optDict as? [String : Any]) 113 | 114 | let cmTime = urlAsset.duration 115 | let durationSeconds: Float64 = CMTimeGetSeconds(cmTime) //视频总秒数 116 | 117 | var times = [NSValue]() 118 | let totalFrames: Float64 = durationSeconds * Float64(fps) //获取视频的总帧数 119 | var timeFrame: CMTime 120 | 121 | for i in 0...Int(totalFrames) { 122 | timeFrame = CMTimeMake(value: Int64(i), timescale: Int32(fps)) //第i帧, 帧率 123 | let timeValue = NSValue(time: timeFrame) 124 | 125 | times.append(timeValue) 126 | } 127 | 128 | let imgGenerator = AVAssetImageGenerator(asset: urlAsset) 129 | imgGenerator.requestedTimeToleranceBefore = CMTime.zero //防止时间出现偏差 130 | imgGenerator.requestedTimeToleranceAfter = CMTime.zero 131 | 132 | let timesCount = times.count 133 | 134 | //获取每一帧的图片 135 | imgGenerator.generateCGImagesAsynchronously(forTimes: times) { (requestedTime, image, actualTime, result, error) in 136 | 137 | //times有多少次body就循环多少次。。。。 138 | print("current-----\(requestedTime.value) timesCount == \(timesCount)") 139 | print("timeScale-----\(requestedTime.timescale) requestedTime:\(requestedTime.value)") 140 | 141 | var isSuccess = false 142 | switch (result) { 143 | case AVAssetImageGenerator.Result.cancelled: 144 | print("cancelled------") 145 | 146 | case AVAssetImageGenerator.Result.failed: 147 | print("failed++++++") 148 | 149 | case AVAssetImageGenerator.Result.succeeded: 150 | let framImg = UIImage(cgImage: image!) 151 | splitImages.append(framImg) 152 | 153 | if (Int(requestedTime.value) == (timesCount-1)) { //最后一帧时 回调赋值 154 | isSuccess = true 155 | splitCompleteClosure(isSuccess, splitImages) 156 | print("completed \(splitImages.count)") 157 | } 158 | @unknown default: 159 | print("unknown default \(result)") 160 | } 161 | } 162 | } 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | @objc private func videoAction(_ sender: UIButton) { 173 | DispatchQueue.global().async { 174 | if let url = Bundle.main.url(forResource: "iOS", withExtension: "mp4") { 175 | self.splitVideoFileUrlFps(splitFileUrl: url, fps: 10) { [weak self] (success, imgs) in 176 | DispatchQueue.main.async { 177 | self?.imgView.animationImages = imgs 178 | self?.imgView.animationDuration = 6 179 | self?.imgView.startAnimating() 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | 187 | 188 | 189 | 190 | 191 | private lazy var mp4Button: UIButton = btn(title: "解析视频", y: 300, action: #selector(videoAction(_:))) 192 | 193 | 194 | 195 | view.addSubview(mp4Button) 196 | ``` 197 | 198 | * https://github.com/webtoon 199 | * https://www.libhunt.com/r/Cartoon-StyleGAN 200 | * https://github.com/john-rocky/CoreML-StyleGAN 201 | * https://github.com/happy-jihye/Cartoon-StyleGAN 202 | * https://www.gwern.net/Faces#anime-faces 203 | * -------------------------------------------------------------------------------- /Swift/LLDB.md: -------------------------------------------------------------------------------- 1 | ##### p 2 | ``` 3 | print 打印变量、常量、表达式 【不可以打印宏】 4 | ``` 5 | 6 | ##### po 7 | ``` 8 | print objcet ,打印变量、常量、表达式返回的对象等 【不可以打印宏】 9 | ``` 10 | 11 | 12 | ##### expression 13 | ``` 14 | 能动态执行赋值表达式,同时打印出结果【例如执行某个else 情况时的调试】 15 | 16 | (lldb) po i 17 | 5 18 | (lldb) expression i = 10 19 | (int) $5 = 10 20 | (lldb) po i 21 | 10 22 | 23 | // 还可以格式化打印字典,防止横杠模式 24 | expression print(json.dictionaryValue) 25 | ``` 26 | 27 | ##### call 28 | ``` 29 | 动态调用函数,例如在不重新编译情况下修改视图 30 | 31 | (lldb) po cell.contentView.subviews 32 | <__NSArrayM 0x60800005f5f0>( 33 | >, 34 | >, 35 | > 36 | ) 37 | 38 | (lldb) call [label removeFromSuperview] 39 | (lldb) po cell.contentView.subviews 40 | <__NSArrayM 0x600000246de0>( 41 | >, 42 | > 43 | ) 44 | ``` 45 | 46 | ##### bt 47 | ``` 48 | 打印当前线程的堆栈信息,比左侧Debug Navigator更详细 49 | 50 | (lldb) bt 51 | * thread #1: tid = 0x27363, 0x000000010d204125 TestDemo`-[FifthViewController tableView:cellForRowAtIndexPath:](self=0x00007f91f4e153c0, _cmd="tableView:cellForRowAtIndexPath:", tableView=0x00007f91f5889600, indexPath=0xc000000000400016) + 2757 at FifthViewController.m:91, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1 52 | * frame #0: 0x000000010d204125 TestDemo`-[FifthViewController tableView:cellForRowAtIndexPath:](self=0x00007f91f4e153c0, _cmd="tableView:cellForRowAtIndexPath:", tableView=0x00007f91f5889600, indexPath=0xc000000000400016) + 2757 at FifthViewController.m:91 53 | frame #1: 0x0000000111d0a7b5 UIKit`-[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 757 54 | frame #2: 0x0000000111d0aa13 UIKit`-[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74 55 | … 56 | … 57 | (lldb) 58 | ``` 59 | 60 | ##### image 61 | ``` 62 | image list 命令可以列出当前App中的所有module(这个module 在后面符号断点时有用到),可以查看某一个地址对应的代码位置。
除了 image list 还有 image add、image lookup等命令,可以自行查看。 63 | 当遇到crash 时,查看线程栈,只能看到栈帧的地址,使用 image lookup –address 地址 可以方便的定位到这个地址对应的代码行 64 | ``` 65 | 66 | ##### 进模拟器沙盒 67 | 在启动函数打断点,运行到断点后, po NSHomeDirectory(),拷贝地址,终端open path 68 | 69 | 70 | * [https://lldb.llvm.org/](https://lldb.llvm.org/) 71 | * [LLDB命令速查手册](https://easeapi.com/blog/blog/156-lldb.html) -------------------------------------------------------------------------------- /Swift/Swift 派发机制.md: -------------------------------------------------------------------------------- 1 | 编译型语言有三种基础的函数派发方式: 直接派发、函数表派发和消息机制派发。大多数语言都会支持一到两种, Java 默认使用函数表派发, 但你可以通过 final 修饰符修改成直接派发. C++ 默认使用直接派发, 但可以通过加上 virtual 修饰符来改成函数表派发. 而 Objective-C 则总是使用消息机制派发, 但允许开发者使用 C 直接派发来获取性能的提高. 这样的方式非常好, 但也给很多开发者带来了困扰, 2 | 3 | **派发方式 (Types of Dispatch)** 4 | 5 | 程序派发的目的是为了告诉 CPU 需要被调用的函数在哪里, 在我们深入 Swift 派发机制之前, 先来了解一下这三种派发方式, 以及每种方式在动态性和性能之间的取舍. 6 | 7 | **直接派发 (Direct Dispatch)** 8 | 9 | 直接派发是最快的, 不止是因为需要调用的指令集会更少, 并且编译器还能够有很大的优化空间, 例如函数内联等, 但这不在这篇博客的讨论范围. 直接派发也有人称为静态调用. 10 | 11 | 然而, 对于编程来说直接调用也是最大的局限, 而且因为缺乏动态性所以没办法支持继承. 12 | 13 | **函数表派发 (Table Dispatch)** 14 | 15 | 函数表派发是编译型语言实现动态行为最常见的实现方式. 函数表使用了一个数组来存储类声明的每一个函数的指针. 大部分语言把这个称为 "virtual table"(虚函数表), Swift 里称为 "witness table". 每一个类都会维护一个函数表, 里面记录着类所有的函数, 如果父类函数被 override 的话, 表里面只会保存被 override 之后的函数. 一个子类新添加的函数, 都会被插入到这个数组的最后. 运行时会根据这一个表去决定实际要被调用的函数. 16 | 17 | 举个例子, 看看下面两个类: 18 | 19 | ``` 20 | class ParentClass { 21 | func method1() {} 22 | func method2() {} 23 | } 24 | class ChildClass: ParentClass { 25 | override func method2() {} 26 | func method3() {} 27 | } 28 | ``` 29 | 30 | 在这个情况下, 编译器会创建两个函数表, 一个是 ParentClass 的, 另一个是 ChildClass 的: 31 | 32 | ![image](http://s1.51cto.com/wyfs02/M00/8C/D1/wKiom1h46UHCcasbAACMICgrPN4820.png) 33 | 34 | 这张表展示了 ParentClass 和 ChildClass 虚数表里 method1, method2, method3 在内存里的布局. 35 | 36 | 1. let obj = ChildClass() 37 | 2. obj.method2() 38 | 39 | 当一个函数被调用时, 会经历下面的几个过程: 40 | 41 | 1. 读取对象 0xB00 的函数表. 42 | 2. 读取函数指针的索引. 在这里, method2 的索引是 1(偏移量), 也就是 0xB00 + 1. 43 | 3. 跳到 0x222 (函数指针指向 0x222) 44 | 45 | 查表是一种简单, 易实现, 而且性能可预知的方式. 然而, 这种派发方式比起直接派发还是慢一点. 从字节码角度来看, 多了两次读和一次跳转, 由此带来了性能的损耗. 另一个慢的原因在于编译器可能会由于函数内执行的任务导致无法优化. (如果函数带有副作用的话) 46 | 47 | 这种基于数组的实现, 缺陷在于函数表无法拓展. 子类会在虚数函数表的最后插入新的函数, 没有位置可以让 extension 安全地插入函数. 这篇提案很详细地描述了这么做的局限. 48 | 49 | **消息机制派发 (Message Dispatch)** 50 | 51 | 消息机制是调用函数最动态的方式. 也是 Cocoa 的基石, 这样的机制催生了 KVO, UIAppearence 和 CoreData 等功能. 这种运作方式的关键在于开发者可以在运行时改变函数的行为. 不止可以通过 swizzling 来改变, 甚至可以用 isa-swizzling 修改对象的继承关系, 可以在面向对象的基础上实现自定义派发. 52 | 53 | 举个例子, 看看下面两个类: 54 | 55 | ``` 56 | class ParentClass { 57 | dynamic func method1() {} 58 | dynamic func method2() {} 59 | } 60 | class ChildClass: ParentClass { 61 | override func method2() {} 62 | dynamic func method3() {} 63 | } 64 | ``` 65 | 66 | Swift 会用树来构建这种继承关系: 67 | 68 | ![image](http://s4.51cto.com/wyfs02/M00/8C/CD/wKioL1h46Xvw35OZAACVN11YZUA408.png) 69 | 70 | 这张图很好地展示了 Swift 如何使用树来构建类和子类. 71 | 72 | 当一个消息被派发, 运行时会顺着类的继承关系向上查找应该被调用的函数. 如果你觉得这样做效率很低, 它确实很低! 然而, 只要缓存建立了起来, 这个查找过程就会通过缓存来把性能提高到和函数表派发一样快. 但这只是消息机制的原理, 这里有一篇文章很深入的讲解了具体的技术细节. 73 | 74 | **Swift 的派发机制** 75 | 76 | 那么, 到底 Swift 是怎么派发的呢? 我没能找到一个很简明扼要的答案, 但这里有四个选择具体派发方式的因素存在: 77 | 78 | 1. 声明的位置 79 | 2. 引用类型 80 | 3. 特定的行为 81 | 4. 显式地优化 (Visibility Optimizations) 82 | 83 | 在解释这些因素之前, 我有必要说清楚, Swift 没有在文档里具体写明什么时候会使用函数表什么时候使用消息机制. 唯一的承诺是使用 dynamic 修饰的时候会通过 Objective-C 的运行时进行消息机制派发. 下面我写的所有东西, 都只是我在 Swift 3.0 里测试出来的结果, 并且很可能在之后的版本更新里进行修改. 84 | 85 | **声明的位置 (Location Matters)** 86 | 87 | 在 Swift 里, 一个函数有两个可以声明的位置: 类型声明的作用域, 和 extension. 根据声明类型的不同, 也会有不同的派发方式. 88 | 89 | ``` 90 | class MyClass { 91 | func mainMethod() {} 92 | } 93 | extension MyClass { 94 | func extensionMethod() {} 95 | } 96 | ``` 97 | 98 | 上面的例子里, mainMethod 会使用函数表派发, 而 extensionMethod 则会使用直接派发. 当我第一次发现这件事情的时候觉得很意外, 直觉上这两个函数的声明方式并没有那么大的差异. 下面是我根据类型, 声明位置总结出来的函数派发方式的表格. 99 | 100 | ![image](http://s1.51cto.com/wyfs02/M01/8C/D1/wKiom1h46b2idH9ZAADm5nJHbF0988.png) 101 | 102 | 这张表格展示了默认情况下 Swift 使用的派发方式. 103 | 104 | 总结起来有这么几点: 105 | 106 | * 值类型总是会使用直接派发, 简单易懂 107 | * 而协议和类的 extension 都会使用直接派发 108 | * NSObject 的 extension 会使用消息机制进行派发 109 | * NSObject 声明作用域里的函数都会使用函数表进行派发. 110 | * 协议里声明的, 并且带有默认实现的函数会使用函数表进行派发 111 | 112 | **引用类型 (Reference Type Matters)** 113 | 114 | 引用的类型决定了派发的方式. 这很显而易见, 但也是决定性的差异. 一个比较常见的疑惑, 发生在一个协议拓展和类型拓展同时实现了同一个函数的时候. 115 | 116 | ``` 117 | protocol MyProtocol { 118 | } 119 | struct MyStruct: MyProtocol { 120 | } 121 | extension MyStruct { 122 | func extensionMethod() { 123 | print("结构体") 124 | } 125 | } 126 | extension MyProtocol { 127 | func extensionMethod() { 128 | print("协议") 129 | } 130 | } 131 | 132 | let myStruct = MyStruct() 133 | let proto: MyProtocol = myStruct 134 | 135 | myStruct.extensionMethod() // -> “结构体” 136 | proto.extensionMethod() // -> “协议” 137 | ``` 138 | 139 | 刚接触 Swift 的人可能会认为 proto.extensionMethod() 调用的是结构体里的实现. 但是, 引用的类型决定了派发的方式, 协议拓展里的函数会使用直接调用. 如果把 extensionMethod 的声明移动到协议的声明位置的话, 则会使用函数表派发, 最终就会调用结构体里的实现. 并且要记得, 如果两种声明方式都使用了直接派发的话, 基于直接派发的运作方式, 我们不可能实现预想的 override 行为. 这对于很多从 Objective-C 过渡过来的开发者是反直觉的. 140 | 141 | Swift JIRA(缺陷跟踪管理系统) 也发现了几个 bugs, Swfit-Evolution 邮件列表里有一大堆讨论, 也有一大堆博客讨论过这个. 但是, 这好像是故意这么做的, 虽然官方文档没有提过这件事情 142 | 143 | **指定派发方式 (Specifying Dispatch Behavior)** 144 | 145 | Swift 有一些修饰符可以指定派发方式. 146 | 147 | **final** 148 | 149 | final 允许类里面的函数使用直接派发. 这个修饰符会让函数失去动态性. 任何函数都可以使用这个修饰符, 就算是 extension 里本来就是直接派发的函数. 这也会让 Objective-C 的运行时获取不到这个函数, 不会生成相应的 selector. 150 | 151 | **dynamic** 152 | 153 | dynamic 可以让类里面的函数使用消息机制派发. 使用 dynamic, 必须导入 Foundation 框架, 里面包括了 NSObject 和 Objective-C 的运行时. dynamic 可以让声明在 extension 里面的函数能够被 override. dynamic 可以用在所有 NSObject 的子类和 Swift 的原声类. 154 | 155 | **@objc & @nonobjc** 156 | 157 | @objc 和 @nonobjc 显式地声明了一个函数是否能被 Objective-C 的运行时捕获到. 使用 @objc 的典型例子就是给 selector 一个命名空间 @objc(abc_methodName), 让这个函数可以被 Objective-C 的运行时调用. @nonobjc 会改变派发的方式, 可以用来禁止消息机制派发这个函数, 不让这个函数注册到 Objective-C 的运行时里. 我不确定这跟 final 有什么区别, 因为从使用场景来说也几乎一样. 我个人来说更喜欢 final, 因为意图更加明显. 158 | 159 | 译者注: 我个人感觉, 这这主要是为了跟 Objective-C 兼容用的, final 等原生关键词, 是让 Swift 写服务端之类的代码的时候可以有原生的关键词可以使用. 160 | 161 | **final @objc** 162 | 163 | 可以在标记为 final 的同时, 也使用 @objc 来让函数可以使用消息机制派发. 这么做的结果就是, 调用函数的时候会使用直接派发, 但也会在 Objective-C 的运行时里注册响应的 selector. 函数可以响应 perform(selector:) 以及别的 Objective-C 特性, 但在直接调用时又可以有直接派发的性能. 164 | 165 | **@inline** 166 | 167 | Swift 也支持 @inline, 告诉编译器可以使用直接派发. 有趣的是, dynamic @inline(__always) func dynamicOrDirect() {} 也可以通过编译! 但这也只是告诉了编译器而已, 实际上这个函数还是会使用消息机制派发. 这样的写法看起来像是一个未定义的行为, 应该避免这么做. 168 | 169 | **修饰符总结 (Modifier Overview)** 170 | 171 | ![image](http://s2.51cto.com/wyfs02/M02/8C/CD/wKioL1h46ifxcQWJAAEWw-OR9zs317.png) 172 | 173 | 这张图总结这些修饰符对于 Swift 派发方式的影响. 174 | 175 | 如果你想查看上面所有例子的话, 请看这里. 176 | 177 | **可见的都会被优化 (Visibility Will Optimize)** 178 | 179 | Swift 会尽最大能力去优化函数派发的方式. 例如, 如果你有一个函数从来没有 override, Swift 就会检车并且在可能的情况下使用直接派发. 这个优化大多数情况下都表现得很好, 但对于使用了 target / action 模式的 Cocoa 开发者就不那么友好了. 例如: 180 | 181 | ``` 182 | override func viewDidLoad() { 183 | super.viewDidLoad() 184 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "登录", style: .plain, target: nil, action: #selector(ViewController.signInAction)) 185 | } 186 | private func signInAction() {} 187 | ``` 188 | 189 | 这里编译器会抛出一个错误: Argument of '#selector' refers to a method that is not exposed to Objective-C (Objective-C 无法获取 #selector 指定的函数). 你如果记得 Swift 会把这个函数优化为直接派发的话, 就能理解这件事情了. 这里修复的方式很简单: 加上 @objc 或者 dynamic 就可以保证 Objective-C 的运行时可以获取到函数了. 这种类型的错误也会发生在 UIAppearance 上, 依赖于 proxy 和 NSInvocation 的代码. 190 | 191 | 另一个需要注意的是, 如果你没有使用 dynamic 修饰的话, 这个优化会默认让 KVO 失效. 如果一个属性绑定了 KVO 的话, 而这个属性的 getter 和 setter 会被优化为直接派发, 代码依旧可以通过编译, 不过动态生成的 KVO 函数就不会被触发. 192 | 193 | Swift 的博客有一篇很赞的文章描述了相关的细节, 和这些优化背后的考虑. 194 | 195 | **派发总结 (Dispatch Summary)** 196 | 197 | 这里有一大堆规则要记住, 所以我整理了一个表格: 198 | 199 | ![image](http://s5.51cto.com/wyfs02/M01/8C/CD/wKioL1h46lfgLlkQAAD7xm29e6c296.png) 200 | 201 | 这张表总结引用类型, 修饰符和它们对于 Swift 函数派发的影响 202 | 203 | **NSObject 以及动态性的损失 (NSObject and the Loss of Dynamic Behavior)** 204 | 205 | 不久之前还有一群 Cocoa 开发者讨论动态行为带来的问题. 这段讨论很有趣, 提了一大堆不同的观点. 我希望可以在这里继续探讨一下, 有几个 Swift 的派发方式我觉得损害了动态性, 顺便说一下我的解决方案. 206 | 207 | **NSObject 的函数表派发 (Table Dispatch in NSObject)** 208 | 209 | 上面, 我提到 NSObject 子类定义里的函数会使用函数表派发. 但我觉得很迷惑, 很难解释清楚, 并且由于下面几个原因, 这也只带来了一点点性能的提升: 210 | 211 | * 大部分 NSObject 的子类都是在 obj_msgSend 的基础上构建的. 我很怀疑这些派发方式的优化, 实际到底会给 Cocoa 的子类带来多大的提升. 212 | * 大多数 Swift 的 NSObject 子类都会使用 extension 进行拓展, 都没办法使用这种优化. 213 | 214 | 最后, 有一些小细节会让派发方式变得很复杂. 215 | 216 | **派发方式的优化破坏了 NSObject 的功能 (Dispatch Upgrades Breaking NSObject Features)** 217 | 218 | 性能提升很棒, 我很喜欢 Swift 对于派发方式的优化. 但是, UIView 子类颜色的属性理论上性能的提升破坏了 UIKit 现有的模式. 219 | 220 | 原文: However, having a theoretical performance boost in my UIView subclass color property breaking an established pattern in UIKit is damaging to the language. 221 | 222 | **NSObject 作为一个选择 (NSObject as a Choice)** 223 | 224 | 使用静态派发的话结构体是个不错的选择, 而使用消息机制派发的话则可以考虑 NSObject. 现在, 如果你想跟一个刚学 Swift 的开发者解释为什么某个东西是一个 NSObject 的子类, 你不得不去介绍 Objective-C 以及这段历史. 现在没有任何理由去继承 NSObject 构建类, 除非你需要使用 Objective-C 构建的框架. 225 | 226 | 目前, NSObject 在 Swift 里的派发方式, 一句话总结就是复杂, 跟理想还是有差距. 我比较想看到这个修改: 当你继承 NSObject 的时候, 这是一个你想要完全使用动态消息机制的表现. 227 | 228 | 显式的动态性声明 (Implicit Dynamic Modification) 229 | 230 | 另一个 Swift 可以改进的地方就是函数动态性的检测. 我觉得在检测到一个函数被 #selector 和 #keypath 引用时要自动把这些函数标记为 dynamic, 这样的话就会解决大部分 UIAppearance 的动态问题, 但也许有别的编译时的处理方式可以标记这些函数. 231 | 232 | **Error 以及 Bug (Errors and Bugs)** 233 | 234 | 为了让我们对 Swift 的派发方式有更多了解, 让我们来看一下 Swift 开发者遇到过的 error. 235 | 236 | **SR-584** 237 | 238 | 这个 Swift bug 是 Swift 函数派发的一个功能. 存在于 NSObject 子类声明的函数 (函数表派发), 以及声明在 extension 的函数(消息机制派发) 中. 为了更好地描述这个情况, 我们先来创建一个类: 239 | 240 | ``` 241 | class Person: NSObject { 242 | func sayHi() { 243 | print("Hello") 244 | } 245 | } 246 | func greetings(person: Person) { 247 | person.sayHi() 248 | } 249 | greetings(person: Person()) // prints 'Hello' 250 | ``` 251 | 252 | greetings(person:) 函数使用函数表派发来调用 sayHi(). 就像我们看到的, 期望的, "Hello" 会被打印. 没什么好讲的地方, 那现在让我们继承 Persion: 253 | 254 | ``` 255 | class MisunderstoodPerson: Person {} 256 | extension MisunderstoodPerson { 257 | override func sayHi() { 258 | print("No one gets me.") 259 | } 260 | } 261 | ``` 262 | 263 | 8. greetings(person: MisunderstoodPerson()) // prints 'Hello' 264 | 265 | 可以看到, sayHi() 函数是在 extension 里声明的, 会使用消息机制进行调用. 当 greetings(person:) 被触发时, sayHi() 会通过函数表被派发到 Person 对象, 而 misunderstoodPerson 重写之后会是用消息机制, 而 MisunderstoodPerson 的函数表依旧保留了 Person 的实现, 紧接着歧义就产生了. 266 | 267 | 在这里的解决方法是保证函数使用相同的消息派发机制. 你可以给函数加上 dynamic 修饰符, 或者是把函数的实现从 extension 移动到类最初声明的作用域里. 268 | 269 | 理解了 Swift 的派发方式, 就能够理解这个行为产生的原因了, 虽然 Swift 不应该让我们遇到这个问题. 270 | 271 | **SR-103** 272 | 273 | 这个 Swift bug 触发了定义在协议拓展的默认实现, 即使是子类已经实现这个函数的情况下. 为了说明这个问题, 我们先定义一个协议, 并且给里面的函数一个默认实现: 274 | 275 | ``` 276 | protocol Greetable { 277 | func sayHi() 278 | } 279 | extension Greetable { 280 | func sayHi() { 281 | print("Hello") 282 | } 283 | } 284 | func greetings(greeter: Greetable) { 285 | greeter.sayHi() 286 | } 287 | ``` 288 | 289 | 现在, 让我们定义一个遵守了这个协议的类. 先定义一个 Person 类, 遵守 Greetable 协议, 然后定义一个子类 LoudPerson, 重写 sayHi() 方法. 290 | 291 | ``` 292 | class Person: Greetable { 293 | } 294 | class LoudPerson: Person { 295 | func sayHi() { 296 | print("HELLO") 297 | } 298 | } 299 | ``` 300 | 301 | 你们发现 LoudPerson 实现的函数前面没有 override 修饰, 这是一个提示, 也许代码不会像我们设想的那样运行. 在这个例子里, LoudPerson 没有在 Greetable 的协议记录表 (Protocol Witness Table) 里成功注册, 当 sayHi() 通过 Greetable 协议派发时, 默认的实现就会被调用. 302 | 303 | 解决的方法就是, 在类声明的作用域里就要提供所有协议里定义的函数, 即使已经有默认实现. 或者, 你可以在类的前面加上一个 final 修饰符, 保证这个类不会被继承. 304 | 305 | Doug Gregor 在 Swift-Evolution 邮件列表里提到, 通过显式地重新把函数声明为类的函数, 就可以解决这个问题, 并且不会偏离我们的设想. 306 | 307 | **其它 bug (Other bugs)** 308 | 309 | Another bug that I thought I’d mention is SR-435. It involves two protocol extensions, where one extension is more specific than the other. The example in the bug shows one un-constrained extension, and one extension that is constrained to Equatable types. When the method is invoked inside a protocol, the more specific method is not called. I’m not sure if this always occurs or not, but seems important to keep an eye on. 310 | 311 | 另外一个 bug 我在 SR-435 里已经提过了. 当有两个协议拓展, 而其中一个更加具体时就会触发. 例如, 有一个不受约束的 extension, 而另一个被 Equatable 约束, 当这个方法通过协议派发, 约束比较多的那个 extension 的实现则不会被调用. 我不太确定这是不是百分之百能复现, 但有必要留个心眼. 312 | 313 | If you are aware of any other Swift dispatch bugs, drop me a line and I’ll update this blog post. 314 | 315 | 如果你发现了其它 Swift 派发的 bug 的话, @一下我我就会更新到这篇博客里. 316 | 317 | **有趣的 Error (Interesting Error)** 318 | 319 | 有一个很好玩的编译错误, 可以窥见到 Swift 的计划. 就像之前说的, 类拓展使用直接派发, 所以你试图 override 一个声明在 extension 里的函数的时候会发生什么? 320 | 321 | ``` 322 | class MyClass { 323 | } 324 | extension MyClass { 325 | func extensionMethod() {} 326 | } 327 | 328 | class SubClass: MyClass { 329 | override func extensionMethod() {} 330 | } 331 | 332 | ``` 333 | 334 | 上面的代码会触发一个编译错误 Declarations in extensions can not be overridden yet(声明在 extension 里的方法不可以被重写). 这可能是 Swift 团队打算加强函数表派发的一个征兆. 又或者这只是我过度解读, 觉得这门语言可以优化的地方. 335 | 336 | **致谢 Thanks** 337 | 338 | 我希望了解函数派发机制的过程中你感受到了乐趣, 并且可以帮助你更好的理解 Swift. 虽然我抱怨了 NSObject 相关的一些东西, 但我还是觉得 Swift 提供了高性能的可能性, 我只是希望可以有足够简单的方式, 让这篇博客没有存在的必要. 339 | 340 | 转载地址:[https://yq.aliyun.com/articles/181601](https://yq.aliyun.com/articles/181601) -------------------------------------------------------------------------------- /Swift/Swift002.md: -------------------------------------------------------------------------------- 1 | # Swift2-运算符和流程控制语句 2 | 3 | > ## 运算符 4 | 5 | Swift支持大多数标准C运算符,并有所改进。 6 | 7 | * 赋值运算符(=)不返回值,以防止在相等运算(==)意图时错误地使用它。 8 | * 算术运算符(+,-,*,/,%等)检测和禁止值溢出,避免数据比类型允许范围更大或更小 9 | * 在面对数值溢出行为可以选择使用溢出运算符,在溢出运算符中描述。 10 | * 提供了在C中不存在的区间运算符,如a.. < >= <= (Swift还提供了恒等运算符 === !==) 29 | * 三元条件运算符 ?: (可以将if else 进行简化) 30 | * Nil-Coalescing运算符 (a??b 表示 (a!=nil)?(a!):b, a!表示必然有值强制解包,a不为nil则不管b,这也称为短路评估) 31 | * 区间运算符 (a...b 区间表示[a,b],a.. ## 流程控制语句 74 | 75 | ### if... (if...else...) 76 | 77 | ``` 78 | public class func testIf() { 79 | let a = 1 80 | let b = 2 81 | // 可以不带括号 82 | if a < b { 83 | print("a>b") 84 | } 85 | } 86 | let settingURL = URL(string: UIApplication.openSettingsURLString)! 87 | if UIApplication.shared.canOpenURL(settingURL) { 88 | if #available(iOS 10, *) { 89 | UIApplication.shared.open(settingURL, options: [:], completionHandler: nil) 90 | } else { 91 | UIApplication.shared.openURL(settingURL) 92 | } 93 | } 94 | ``` 95 | 96 | ### switch 97 | 98 | ``` 99 | public class func testSwitch() { 100 | 101 | // Swift中不需在用break跳出switch。若想用C风格的落入特性,需给case分支插入fallthrough语句 102 | let fruit = "apple" 103 | switch fruit { 104 | case "apple": print("good"); fallthrough // 用分号结束写到同一行 105 | case "banana","orange": print("great") 106 | default: print("bad") 107 | } 108 | 109 | // case分支还可以进行区间匹配 110 | let age = 5 111 | switch age { 112 | case 0...11: print("少儿") 113 | case 12...18: print("少年") 114 | default: print("其他") 115 | } 116 | 117 | // case分支同样支持单侧区间匹配 118 | let num = -5 119 | switch num { 120 | case ..<0: print("负数") 121 | case 0...: print("正数") 122 | default: print("0") 123 | } 124 | 125 | // 使用元组匹配(如:判断属于哪个象限) 126 | let point = (2,2) 127 | switch point { 128 | case (0,0): print("坐标在原点") 129 | case (_,0): print("坐标在x轴上") 130 | case (0,_): print("坐标在y轴上") 131 | default: print("在象限区域") 132 | } 133 | 134 | // case中还可以使用where关键字来做额外的判断条件 135 | let height = 1.72 136 | switch height{ 137 | case 1...3 where height == 1.72: print("case 1") 138 | case 1...3 where height == 2: print("case 2") 139 | default: print("default") 140 | } 141 | 142 | // 值绑定 143 | let anotherPoint = (2, 0) 144 | switch anotherPoint { 145 | case (let x, 0): 146 | print("on the x-axis with an x value of \(x)") 147 | case (0, let y): 148 | print("on the y-axis with a y value of \(y)") 149 | case let (x, y): 150 | print("somewhere else at (\(x), \(y))") 151 | } 152 | // on the x-axis with an x value of 2 153 | 154 | // 复合案例 155 | let someCharacter: Character = "e" 156 | switch someCharacter { 157 | case "a", "e", "i", "o", "u": 158 | print("\(someCharacter) is a vowel") 159 | case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", 160 | "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": 161 | print("\(someCharacter) is a consonant") 162 | default: 163 | print("\(someCharacter) is not a vowel or a consonant") 164 | } 165 | // e is a vowel 166 | 167 | // 复合案例 结合 值绑定 168 | let stillAnotherPoint = (9, 0) 169 | switch stillAnotherPoint { 170 | case (let distance, 0), (0, let distance): 171 | print("On an axis, \(distance) from the origin") 172 | default: 173 | print("Not on an axis") 174 | } 175 | // On an axis, 9 from the origin 176 | 177 | // 超级用法 178 | 179 | indirect enum DDYCode { 180 | case integerCode(Int) 181 | case stringcode(String) 182 | static func == (firstCode: DDYCode, secondCode: DDYCode) -> Bool { 183 | switch (firstCode, secondCode) { 184 | case (let . integerCode(a1), let . integerCode(a2)): 185 | return a1 == a2 186 | 187 | case (let . stringcode(str1), let . stringcode(str2)): 188 | return str1 == str2 189 | 190 | default: 191 | return false 192 | } 193 | } 194 | } 195 | } 196 | ``` 197 | 198 | ### if-case-let 199 | 200 | ``` 201 | case let .success(data) where data.count > 100: 202 | 203 | if case let .success(data) = result, data.count > 100 { ... } 204 | ``` 205 | 206 | ### for 207 | 208 | ``` 209 | public class func testFor() { 210 | // C-style for statement has been removed in Swift 3 211 | //for var i=1; i<100; i++ { 212 | // print("\(i)") 213 | //} 214 | // for-in 215 | for chare in "Google" { 216 | print(chare) 217 | } 218 | // forEach 219 | (1...10).forEach { 220 | print($0) 221 | } 222 | } 223 | ``` 224 | 225 | ### 控制转移语句 226 | 227 | * continue 228 | 229 | continue语句告诉循环停止当前循环不再往下执行,进入下次循环。即“完成了当前的循环迭代”而没有离开循环。 230 | 231 | ``` 232 | public class func testContinue() { 233 | let startStr = "www.google.com www.apple.com" 234 | var endStr = "" 235 | let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "] 236 | for character in startStr { 237 | if charactersToRemove.contains(character) { 238 | continue 239 | } 240 | endStr.append(character) 241 | } 242 | print(endStr) // www.ggl.cmwww.ppl.cm 243 | } 244 | ``` 245 | 246 | * break 247 | 248 | break语句立即结束整个控制流语句的执行,尽管break在Swift中不需要,但仍可用来中断匹配。 249 | 250 | ``` 251 | public class func testBreak() { 252 | let numberSymbol: Character = "三" 253 | var possibleIntegerValue: Int? 254 | switch numberSymbol { 255 | case "1", "١", "一", "๑": 256 | possibleIntegerValue = 1 257 | case "2", "٢", "二", "๒": 258 | possibleIntegerValue = 2 259 | case "3", "٣", "三", "๓": 260 | possibleIntegerValue = 3 261 | case "4", "٤", "四", "๔": 262 | possibleIntegerValue = 4 263 | default: 264 | break 265 | } 266 | if let integerValue = possibleIntegerValue { 267 | print("The integer value of \(numberSymbol) is \(integerValue).") 268 | } else { 269 | print("An integer value could not be found for \(numberSymbol).") 270 | } 271 | // The integer value of 三 is 3. 272 | } 273 | ``` 274 | 275 | * fallthrough 276 | 277 | 在Swift中,switch语句不会落入每个案例的底部并进入下一个案例。也就是说,switch一旦第一个匹配的案例完成,整个语句就完成了它的执行。相反,C要求break在每个switch案例的末尾插入一个明确的语句,以防止通过。避免默认的下降意味着Swift switch语句比C中的对应语句更简洁和可预测,因此它们避免switch错误地执行多个案例。 278 | 如果需要C样式的直通行为,则可以使用fallthrough关键字逐个选择加入此行为。以下示例fallthrough用于创建数字的文本描述。 279 | 280 | * return 281 | 282 | 用于提前退出,不再执行代码段中代码 283 | 284 | ``` 285 | public class func testReturn(_ fruit:[String: String]) { 286 | guard let price = fruit["price"] else { 287 | return 288 | } 289 | print("price is \(price)") 290 | } 291 | ``` 292 | 293 | * throw 294 | 295 | ### 守护关键字 guard 296 | 297 | 其实是if...else...变种,满足guard语句的条件则跳过else向下执行,否则执行else中语句 298 | else分支必须转移控制以退出guard语句出现的代码块,如return,break,continue,或throw,也可以调用一个函数或方法不返回,如fatalError()。 299 | 300 | ``` 301 | public class func testReturn(_ fruit:[String: String]) { 302 | guard let price = fruit["price"] else { 303 | return 304 | } 305 | guard let name = fruit["name"] else { 306 | fatalError("致命错误:不存在name键值") 307 | } 308 | print("price is \(price)") 309 | } 310 | ``` 311 | 312 | 自定义致命错误fatalError 313 | 314 | ``` 315 | public class func testFatalError(){ 316 | // (1) fatal error发生时,defer是不会执行的 317 | // (2) catch 不到 fatal error 318 | defer { 319 | print("defer here") // 不执行 320 | } 321 | do { 322 | try _throwsMyFatalError() // 产生fatal error 323 | } catch let err { 324 | print("in MyFatalError catch section \(err)") // 这一行进不了 325 | } 326 | } 327 | private class func _throwsMyFatalError() throws { 328 | fatalError("my fatal error here!") 329 | } 330 | ``` 331 | 332 | ### 标签语句 333 | 334 | 在Swift中,可以在其他循环和条件语句中嵌套循环和条件语句,以创建复杂的控制流结构。但是,循环和条件语句都可以使用break语句过早地结束执行。因此,有时候明确要求break语句终止的循环或条件语句是有用的。类似地,如果有多个嵌套循环,那么明确该continue语句应该影响哪个循环可能很有用。 335 | 要实现这些目标,可以使用语句标签标记循环语句或条件语句。使用条件语句,可以使用带标签语句的break语句来结束带标签语句的执行。使用循环语句,可以使用带有标签语句的break、continue语句来结束或继续执行带标签的语句。 336 | 标签语句通过在语句相同行上关键字前放置标签名来指示,后跟冒号。这是while循环的这种语法的一个例子,然而所有循环和switch语句的原理是相同的: 337 | 338 | ``` 339 | public class func testLabel() { 340 | let finishIndex = 25 341 | var currentIndex = 0 342 | var diceRoll = 0 343 | gameLoop: while currentIndex != finishIndex { 344 | diceRoll += 1 345 | if diceRoll == 7 { diceRoll = 1 } 346 | switch currentIndex + diceRoll { 347 | case finishIndex: 348 | break gameLoop 349 | case let newSquare where newSquare > finishIndex: 350 | continue gameLoop 351 | default: 352 | currentIndex += diceRoll 353 | } 354 | } 355 | } 356 | ``` 357 | 358 | ### 运算符重载 359 | 360 | 让已有的运算符对自定义的类(结构)进行运算或重新定义已有运算符的运算规则,这种机制被称为运算符重载。 361 | 362 | * 通过重载加号运算符,使自定义的两个坐标结构体对象实现相加 363 | 364 | ``` 365 | struct CenterPointer{ 366 | var x=0, y=0 367 | } 368 | 369 | func + (left:CenterPointer, right:CenterPointer) -> CenterPointer{ 370 | return CenterPointer(x:left.x+right.x, y:left.y+right.y) 371 | } 372 | 373 | let pointer1 = CenterPointer(x:2, y:3) 374 | let pointer2 = CenterPointer(x:4, y:5) 375 | let pointer3 = pointer1 + pointer2 376 | ``` 377 | 378 | * 重载判断运算符,实现判断自定义类型是否相等 379 | 380 | ``` 381 | func == (left:CenterPointer, right:CenterPointer) -> Bool { 382 | return (left.x == right.x) && (left.y == right.y) 383 | } 384 | 385 | func != (left:CenterPointer, right:CenterPointer) -> Bool { 386 | return !(left == right) 387 | } 388 | ``` 389 | 390 | * 组合运算符,即将其他运算符和赋值运算符组合在一起,注意把运算符左参数设置成inout类型 391 | 392 | ``` 393 | func += (left:inout CenterPointer, right:CenterPointer){ 394 | left = left + right 395 | } 396 | 397 | var pointer1 = CenterPointer(x:2, y:3) 398 | var pointer2 = CenterPointer(x:4, y:5) 399 | pointer1 += pointer2 400 | ``` 401 | 402 | * [extension及fatalError](https://www.cnblogs.com/Jepson1218/p/5277682.html) 403 | * [swift-if-case-let](https://useyourloaf.com/blog/swift-if-case-let/) 404 | 405 | [上一页 Swift1-数据类型 元组 枚举 结构体](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift001.md) 406 | [下一页 Swift3-访问修饰词 函数 闭包](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift003.md) -------------------------------------------------------------------------------- /Swift/Swift003.md: -------------------------------------------------------------------------------- 1 | # Swift3-访问修饰词 函数 闭包 2 | 3 | > ## 访问限制词 4 | 5 | 在 Swift 语言中,访问修饰符有五种,分别为 fileprivate,private,internal,public 和 open。 6 | 权限从高到底 open > public > interal > fileprivate > private 7 | 8 | * private 访问级别所修饰的属性或者方法只能在当前类里访问(swift4开始 extension 里也可以访问 private 的属性)。 9 | * fileprivate 访问级别所修饰的属性或者方法在当前的 Swift 源文件里可以访问。 10 | * internal 访问级别所修饰的属性或方法在源代码所在的整个模块都可以访问。 11 | - 如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。 12 | - 如果是 App 代码,也是在整个 App 代码,也是在整个 App 内部可以访问。 13 | - 默认访问级别,internal修饰符可写可不写 14 | * public 可以被任何人访问。但其他 module 中不可以被重写(override)和继承,而在 module 内可以。 15 | * open 可以被任何人使用,包括 override 和继承 16 | 17 | 18 | > ## 函数 19 | 20 | * 一个完整的函数由 访问修饰符+类型+func+方法名+参数标签+参数名称+返回值 组成 21 | * 面向对象上我们习惯称 方法 22 | 23 | 24 | ``` 25 | // 定义 26 | // 多个参数可以(但最好不要)有相同的参数标签,不可以有相同的参数名称 27 | // "_"下划线用于隐藏标签 28 | // 参数可以写进固定值 29 | // 返回值有多个时可以利用元组,也可以用数组,字典的形式 30 | public class func test(_ fruit: String, priceLabel price: Float, count: Int, shop: String = "超市") -> (String, Float, Int) { 31 | return (fruit, price, count) 32 | } 33 | // 使用 34 | test2("pear", priceLabel: 1.25, count: 1) 35 | ``` 36 | 37 | * 类方法 38 | 39 | ``` 40 | public class func test1() { 41 | print("类方法") 42 | } 43 | ``` 44 | 45 | * 静态方法 46 | 47 | ``` 48 | public class func test2() { 49 | print("静态方法") 50 | } 51 | ``` 52 | 53 | * 实例方法(对象方法) 54 | 55 | ``` 56 | func test3() { 57 | print("实例方法") 58 | } 59 | ``` 60 | 61 | * 无参数无返回值(Void而已,省略) 62 | 63 | ``` 64 | func test4() { 65 | print("无参数无返回值") 66 | } 67 | ``` 68 | 69 | * 可变形参(可变参数:参数数量不定) 70 | 71 | ``` 72 | func test5(nums: Int...) { 73 | let sum = nums.reduce(0) { (result, num) -> Int in 74 | return result + num 75 | } 76 | print("\(nums) 的和: \(sum)") 77 | } 78 | test5(nums: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 79 | ``` 80 | 81 | * 输入输出参数 82 | 83 | 默认情况下,函数参数是常量。尝试从该函数体内更改函数参数的值会导致编译时错误。这意味着无法更改参数的值。 84 | 通过将inout关键字放在参数类型之前来编写输入输出参数。一个输入输出参数传递进函数的值,可以由函数修改并替换原来的值。 85 | 将变量作为输入输出参数的参数传递。不能传递常量或文字值作为参数,因为不能修改常量和文字。当使用输入输出参数调用函数时在变量的名称前直接放置一个&,以指示该函数可以修改它。 86 | 输入输出参数不能具有默认值,并且可变参数不能标记为inout。 87 | 88 | 89 | ``` 90 | func inoutFunc(argument1: inout Int, argument2: inout Int) { 91 | let temp = argument1 92 | argument1 = argument2 93 | argument2 = temp 94 | } 95 | var inout1 = 1 96 | var inout2 = 2 97 | inoutFunc(argument1: &inout1, argument2: &inout2) 98 | print("inout1: \(inout1), inout2: \(inout2)") 99 | ``` 100 | 101 | ### 函数类型 102 | 103 | 每个函数都有一个特定的函数类型,由函数的参数类型和返回类型组成。形式上表示为:(参数类型) -> 返回值类型。 104 | 105 | * 函数类型作为参数类型 106 | 107 | 可以使用函数类型,作为另一个函数的参数类型。这使得可以为函数调用者保留函数实现的某些方面,以便在调用函数时提供: 108 | 109 | ``` 110 | func testAdd(a:Int, b:Int) -> Int { 111 | return a + b 112 | } 113 | func testFuncParam(_ addFunction: (Int, Int) -> Int, a:Int, b:Int) { 114 | print("Result:\(addFunction(a,b))") 115 | } 116 | func testBasic() { 117 | testFuncParam(testAdd, a: 5 ,b: 7) 118 | } 119 | ``` 120 | 121 | * 函数类型作为返回类型 122 | 123 | 可以使用函数类型作为另一个函数的返回类型。通过在返回函数的返回箭头(->)之后立即编写完整的函数类型来完成此操作: 124 | 125 | ``` 126 | // 定义个自增函数 127 | func testIncrease(input:Int) -> Int { 128 | return input + 1 129 | } 130 | // 定义个自减函数 131 | func testReduce(input:Int) -> Int { 132 | return input - 1 133 | } 134 | // 定义一个返回函数类型的函数 135 | func testFunctionBack(backwards:Bool) -> (Int) -> Int { 136 | return backwards ? testReduce : testIncrease 137 | } 138 | ``` 139 | 140 | * 嵌套函数 141 | 142 | 在函数体内定义的函数称为嵌套函数,嵌套函数对外界是隐藏的,但它们的封闭函数仍然可以调用它们。封闭函数也可以返回其嵌套函数,以允许嵌套函数在另一个范围内使用: 143 | 144 | ``` 145 | func sumFunc() -> (Int...) -> Int { 146 | func sumInts(nums: Int...) -> Int { 147 | return nums.reduce(0) { (result, item) -> Int in 148 | result + item 149 | } 150 | } 151 | return sumInts(nums:) 152 | } 153 | print(sumFunc()(1, 2, 3, 4, 5)) 154 | ``` 155 | 156 | * 构造函数和析构函数 157 | 158 | ``` 159 | // 属性 160 | var name: String 161 | // 构造函数(初始化) 162 | init(newName:String){ 163 | self.name = newName 164 | print("My name is \(newName)") 165 | } 166 | // 析构函数(反初始化,功能跟oc的dealloc一样做善后) 167 | deinit { 168 | name = "" 169 | } 170 | ``` 171 | 172 | * class与staitc关键字的区别与使用 173 | 174 | - static 可以在类、结构体、枚举、扩展中使用。而 class 只能在类中使用。 175 | - static 可以修饰存储属性,static 修饰的存储属性称为静态变量(常量)。而 class 不能修饰存储属性。 176 | - static 修饰的计算属性不能被重写。而 class 修饰的可以被重写。 177 | - static 修饰的静态方法不能被重写。而 class 修饰的类方法可以被重写。 178 | - class 修饰的计算属性被重写时,可以使用 static 让其变为静态属性。 179 | - class 修饰的类方法被重写时,可以使用 static 让方法变为静态方法。 180 | 181 | > ## 闭包 182 | 183 | 闭包是swift中非常重要的一个知识点,类似于objective-c中的block。 184 | 闭包的本质是代码块,是函数的升级版,函数是有名称、可复用的代码块,闭包则是比函数更加灵活的匿名代码块。 185 | (函数相当于一个特殊的闭包(有名字的闭包),或者说闭包是一个特殊的函数(带有自动变量的匿名函数)) 186 | 187 | 由上面括号内语句, 可以归纳三种闭包形式: 188 | 189 | * 全局函数:具名函数,但不捕获任何值 190 | * 嵌套函数:在函数内部嵌套定义具名函数,可捕获包含函数中的值 191 | * 闭包表达式:匿名函数类型实例,不具名的代码块,轻量级语法,可捕获上下文的值 192 | 193 | 其中闭包表达式才是我们一般意义上说的闭包 194 | 195 | Swift的闭包表达式具有干净,清晰的风格,闭包的优势包括: 196 | 197 | * 从上下文中推断参数和返回值类型 198 | * 单表达式闭包的隐式返回 199 | * 速记参数名称 200 | * 尾随闭包语法 201 | 202 | 闭包定义的类型 203 | 204 | ``` 205 | { (参数1,参数2... )->返回值类型 in 206 | 语句块 207 | } 208 | ``` 209 | 210 | 闭包表达式以及简化形式: 211 | 212 | ``` 213 | import UIKit 214 | /// 起别名 215 | typealias DDYTestClosureTypealias = (String, Bool) -> Void 216 | 217 | class ClosureTest: NSObject { 218 | 219 | public class func testBasic() { 220 | testTypealias { (city: String, success: Bool) in 221 | print("\(city) \(success)") // Beijing true 222 | } 223 | testParams() 224 | } 225 | 226 | private class func testTypealias(_ closure: DDYTestClosureTypealias) { 227 | closure("Beijing", true); 228 | } 229 | 230 | private class func testParams() { 231 | let numbersArray = [1, 3, 5, 9, 7, 2, 8, 6, 0] 232 | 233 | // 参数加类型 234 | let sorted1 = numbersArray.sorted { (num1: Int, num2: Int) -> Bool in 235 | return num1 > num2 // 降序 236 | } 237 | // 参数省略类型(编译器推断类型) 238 | let sorted2 = numbersArray.sorted { num1, num2 in 239 | return num1 < num2 // 升序 240 | } 241 | // 省略参数名和类型,用$加数字引用每个参数 242 | let sorted3 = numbersArray.sorted { 243 | return $1 > $0 244 | } 245 | // 如果闭包只包含一行代码,省略return都可以 246 | let sorted4 = numbersArray.sorted { 247 | $0 > $1 248 | } 249 | // 闭包还可以存储在变量中,类似函数一样调用 250 | let comparator = {(a: Int, b: Int) in a Void) { 275 | paramClosure("输出"+paramStr) 276 | } 277 | private class func testCloseClosurePrint() { 278 | // 普通调用 279 | testCloseClosure(paramStr: "1 普通调用", paramClosure: { (closureStr) in 280 | print(closureStr) 281 | }) 282 | // 尾随闭包 283 | testCloseClosure(paramStr: "2 尾随闭包") { (closureStr) in 284 | print(closureStr) 285 | } 286 | // 输出1 普通调用 287 | // 输出2 尾随闭包 288 | } 289 | ``` 290 | 291 | * 自动闭包:不接受任何参数,直接返回表达式的值。允许延迟计算。 292 | (自动闭包就是将一个非闭包形式的表达参数 自动转化为闭包的表达形式并且传给一个普通函数表达形式) 293 | 294 | ``` 295 | var cities = ["Beijing","Shanghai","New York", "Paris","London"] 296 | print(cities.count) //此时的城市数是5 297 | 298 | let filter = { cities.removeLast() } //()->String 将闭包赋值给filter,此时还没有执行removeLast() 299 | 300 | print(cities.count) //由于上一句并没有执行removeLast()此时的城市数还是5 301 | 302 | print("Deleting \(filter())!") //执行了filter的闭包。 显示Deleting London! 303 | 304 | print(cities.count) //此时的城市数是4, London被删除掉了 305 | ``` 306 | 307 | 函数类型与闭包的变量捕获 308 | 函数类型和闭包可以捕获其所在上下文的任何值: 309 | * 函数参数 310 | * 局部变量 311 | * 对象实例属性 312 | * 全局变量 313 | * 类的类型属性 314 | 315 | ``` 316 | //捕获实例属性 317 | class Rectangle{ 318 | var width = 0 319 | var length = 0 320 | 321 | func getComputHandler()-> ()->Int { 322 | return { 323 | return self.width*self.length //这个闭包就捕获了实例属性self.width和self.length 324 | } 325 | } 326 | } 327 | 328 | //捕获参数或局部变量 329 | func addHandler(step: Int)-> ()->Int{ 330 | var sum = 0 331 | return{ 332 | sum +=step //这里捕获了参数step和局部变量sum 333 | return sum 334 | } 335 | } 336 | 337 | let addByTen = addHandler(10) 338 | 339 | print(addByTen()) //显示结果 10 340 | print(addByTen()) //显示结果 20 341 | print(addByTen()) //显示结果 30 342 | ``` 343 | 344 | 如果捕获值生存周期小于闭包对象(参数和局部变量),系统会将被捕获的值封装在一个临时对象里,然后再闭包对象上创建一个对象指针,指向该临时对象。 345 | 346 | 临时对象和闭包对象之间是强引用关系,生存周期跟随闭包对象。 347 | 348 | 起别名 349 | 350 | ``` 351 | // 光强检测闭包起别名 352 | typealias DDYQRBrightnessClosure = (_ brightnessValue: Double) -> Void 353 | 354 | // 光强检测数据闭包回调 355 | public var brightnessClosure: DDYQRBrightnessClosure? 356 | ``` 357 | 358 | 使用闭包需要注意内存管理,当闭包为参数时如果以脱离函数则需要声明为逃逸闭包(类型前加@escaping) 359 | 360 | 逃逸闭包 361 | 362 | 当一个闭包作为参数传到一个函数中,但是该闭包要在函数返回之后才被执行,于是就称这样的闭包为逃逸闭包。也就是说闭包逃离了函数的作用域。写法是在这个闭包参数前加一个@escaping用来指明这个闭包是允许逃逸出该函数的。 363 | 364 | ``` 365 | //定义数组,里面的元素都是闭包类型的 366 | var callBackArray : [(Int)->Void] = [] 367 | //定义一个接收闭包的函数 368 | private func testEscapingClosure() { 369 | testEscapingClosureChange() 370 | print("111: \(closureX)") 371 | callBackArray.first?(5) 372 | print("222: \(closureX)") 373 | let closure = callBackArray.first 374 | closure?(6) 375 | print("333: \(closureX)") 376 | } 377 | 378 | var closureX = 10 379 | private func testEscapingClosureChange() { 380 | testEscapingClosureCallBack { (closureParam) in 381 | self.closureX = self.closureX+closureParam 382 | } 383 | } 384 | private func testEscapingClosureCallBack(callBack:@escaping (Int)-> Void) { 385 | callBackArray.append(callBack) 386 | } 387 | ``` 388 | 389 | 390 | 逃逸闭包实际应用 391 | 392 | ``` 393 | /// 相机权限 394 | /// 395 | /// - Parameter closure: 闭包回调 396 | public static func cameraAuth(_ closure: @escaping (Bool) -> Void) -> Void { 397 | let authStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) 398 | switch authStatus { 399 | case .notDetermined: 400 | AVCaptureDevice.requestAccess(for: AVMediaType.video) { (granted: Bool) in 401 | DispatchQueue.main.async { 402 | closure(granted) 403 | } 404 | } 405 | break 406 | case .authorized: 407 | DispatchQueue.main.async { 408 | closure(true) 409 | } 410 | break 411 | default: // case .restricted // case .denied 412 | DispatchQueue.main.async { 413 | closure(false) 414 | } 415 | break 416 | } 417 | } 418 | ``` 419 | 420 | [解决循环引用的三种方式](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html) 421 | 422 | ``` 423 | // 1、可以使用weak关键字将对象之间的联系变为弱引用 424 | weak var weakself = self 425 | 426 | // 2、第一种方式的简化 427 | [weak self] 428 | 429 | // 3、使用unowned解决 430 | [unowned self] 431 | // 但是该方法十分危险,要确保数据一定有值,否则会Crash 432 | ``` 433 | 434 | 435 | 436 | [闭包资料](http://www.cocoachina.com/articles/23496) 437 | [闭包资料](https://www.runoob.com/swift/swift-closures.html) 438 | 439 | 440 | [上一页 Swift2-运算符和流程控制语句](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift002.md) 441 | [下一页 Swift4-类 属性 协议 范型 扩展](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift004.md) -------------------------------------------------------------------------------- /Swift/Swift004.md: -------------------------------------------------------------------------------- 1 | # Swift4-类 属性 协议 范型 扩展 2 | 3 | > ## 类 4 | 5 | 类和结构体区别: 6 | 7 | + 1,类可以继承,结构体不可以 8 | + 2,可以让一个类的实例来反初始化,释放存储空间,结构体做不到 9 | + 3,类的对象是引用类型,而结构体是值类型。所以类的赋值是传递引用,结构体则是传值。 10 | 11 | 相同点: 12 | + 1,类和结构体都可以扩展 13 | + 2,定义属性用于储存值 14 | + 3,定义方法用于提供功能 15 | + 4,定义下标用于通过下标语法访问值 16 | + 5,定义初始化器用于生成初始化值 17 | 18 | 类的构造方法(初始化方法) 19 | 20 | ``` 21 | /******************************** 22 | 使用默认构造函数 23 | ********************************/ 24 | class Student{ 25 | //类属性 26 | var name:String = "" 27 | var number:Int = 0 28 | } 29 | var student = Student() 30 | 31 | /******************************** 32 | 自定义构造函数 33 | ********************************/ 34 | class Person{ 35 | //类属性 36 | var name:String 37 | var age:Int 38 | 39 | //类构造函数 40 | init(newName:String, newAge:Int){ 41 | self.name = newName 42 | self.age = newAge 43 | } 44 | 45 | //成员函数(实例方法) 46 | func say() -> String{ 47 | return "我叫\(name)" 48 | } 49 | } 50 | var p = Person(newName: "hangge",newAge: 32) 51 | print(p.say()) 52 | ``` 53 | 类的析构方法(反初始化方法) 54 | 55 | ``` 56 | class DBClass{ 57 | var conn:Connection? = Connection() 58 | deinit{ 59 | //可以做一些清理工作 60 | self.conn!.close() 61 | self.conn = nil 62 | } 63 | } 64 | 65 | var db:DBClass? = DBClass() 66 | db = nil //设置nil后即可执行deinit()方法 67 | ``` 68 | 69 | > ## 属性 70 | 71 | 属性分:计算属性,存储属性,类型属性 72 | 73 | * 计算属性 74 | 75 | 计算(而不是存储)一个值,用于类、结构体和枚举,以及扩展 76 | 77 | 计算属性,即使用get和set来间接获取/改变其他属性的值 78 | 79 | 执行函数返回其他内存地址,计算型属性本身不占用内存空间 80 | 81 | ``` 82 | class Calcuator{ 83 | var a:Int = 1; 84 | var b:Int = 1; 85 | 86 | var sum:Int{ 87 | get { 88 | return a + b 89 | } 90 | set(val) { 91 | b = val - a 92 | } 93 | } 94 | } 95 | let cal = Calcuator(); 96 | print(cal.sum) //2 97 | cal.sum = 5 98 | print(cal.b) //4 99 | ``` 100 | 101 | 对于set有简写方法,简写时,新赋的值默认为newValue 102 | ``` 103 | class Calcuator{ 104 | var a:Int = 1; 105 | var b:Int = 1; 106 | 107 | var sum:Int{ 108 | get{ 109 | return a + b 110 | } 111 | set{ 112 | b = newValue - a 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | 只读计算属性 119 | 120 | 只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点(.)运算符访问,但不能设置新的值。 121 | 122 | 如果只要get,不要set方法时可以简写成如下代码 123 | 124 | ``` 125 | class Calcuator{ 126 | var a:Int = 1; 127 | var b:Int = 1; 128 | 129 | var sum:Int{ 130 | return a + b 131 | } 132 | } 133 | ``` 134 | 135 | * 存储属性 136 | 137 | 存储常量或变量作为实例的一部分,用于类和结构体 138 | 139 | 存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义),需要开辟空间,以存储数据。 140 | 可以在定义存储属性的时候指定默认值,也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值 141 | 142 | ``` 143 | struct Number { 144 | var digits: Int 145 | let pi = 3.1415 146 | } 147 | 148 | var n = Number(digits: 12345) 149 | n.digits = 67 150 | 151 | print("\(n.digits)") 152 | print("\(n.pi)") 153 | ``` 154 | 155 | 延迟存储属性(懒加载属性) 156 | 157 | 延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy var来标示一个延迟存储属性。 158 | 159 | ``` 160 | class sample { 161 | lazy var no = number() // `var` 关键字是必须的 162 | lazy var scanBackView: UIImageView = { 163 | let imageView = UIImageView.init(frame: self.bounds) 164 | return imageView 165 | }() 166 | } 167 | ``` 168 | 169 | 属性观察器(属性观察者) 170 | 171 | 属性观察器,类似于触发器。用来监视属性的除初始化之外的属性值变化,当属性值发生改变时可以对此作出响应。有如下特点: 172 | 1,不仅可以在属性值改变后触发didSet,也可以在属性值改变前触发willSet。 173 | 2,给属性添加观察者必须要声明清楚属性类型,否则编译器报错。 174 | 3,willSet可以带一个newName的参数,没有的话,该参数默认命名为newValue。 175 | 4,didSet可以带一个oldName的参数,表示旧的属性,不带的话默认命名为oldValue。 176 | 5,属性初始化时,willSet和didSet不会调用。只有在初始化上下文之外,当设置属性值时才会调用。 177 | 6,即使是设置的值和原来值相同,willSet和didSet也会被调用 178 | 179 | 可以为除了延迟存储属性(懒加载属性)之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。 180 | 181 | ``` 182 | class Samplepgm { 183 | var counter: Int = 0{ 184 | willSet(newTotal){ 185 | print("计数器: \(newTotal)") 186 | } 187 | didSet{ 188 | if counter > oldValue { 189 | print("新增数 \(counter - oldValue)") 190 | } 191 | } 192 | } 193 | } 194 | let NewCounter = Samplepgm() 195 | NewCounter.counter = 100 196 | NewCounter.counter = 800 197 | /** 198 | 计数器: 100 199 | 新增数 100 200 | 计数器: 800 201 | 新增数 700 202 | */ 203 | ``` 204 | 205 | 类型属性 206 | 207 | 类型属性:属于类型固有的,实例不能调用 208 | 使用关键字 static 来定义值类型的类型属性,关键字 class 来为类定义类型属性。 209 | 210 | ``` 211 | struct Structname { 212 | static var storedTypeProperty = " " 213 | static var computedTypeProperty: Int { 214 | // 这里返回一个 Int 值 215 | } 216 | } 217 | 218 | enum Enumname { 219 | static var storedTypeProperty = " " 220 | static var computedTypeProperty: Int { 221 | // 这里返回一个 Int 值 222 | } 223 | } 224 | 225 | class Classname { 226 | class var computedTypeProperty: Int { 227 | // 这里返回一个 Int 值 228 | } 229 | } 230 | ``` 231 | 232 | 233 | > ## 协议 234 | 235 | 协议规定了用来实现某一特定功能所必需的方法和属性。 236 | 任意能够满足协议要求的类型被称为遵循这个协议。 237 | 类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。 238 | 239 | Swift中协议类似于别的语言里的接口,协议里只做方法的声明,包括方法名、返回值、参数等信息,而没有具体的方法实现。 240 | 241 | 242 | ``` 243 | protocol ddyBasePersonProtocol { 244 | // 协议中通常用var声明变量属性,然后指明读写权限,不用指定存储型属性还是计算型属性 245 | var nickName: String { get set } 246 | // 只读属性 247 | var age: Int { get } 248 | // 类型方法 249 | static func runMethod() 250 | // 实例方法 251 | func eatMethod(food: String) -> Bool 252 | // 突变方法 253 | mutating func changeNickNameMethod(_ newNickName: String) 254 | } 255 | // 值类型(协议,结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,表示可以在该方法实现中修改它所属的实例及其实例属性的值。 256 | 257 | protocol ddyStudentProtocol: ddyBasePersonProtocol { 258 | func studyMethod() 259 | } 260 | 261 | struct student: ddyStudentProtocol { 262 | func studyMethod() { 263 | print("studyMethod") 264 | } 265 | 266 | var nickName: String 267 | 268 | var age: Int 269 | 270 | static func runMethod() { 271 | print("runMethod") 272 | } 273 | 274 | mutating func changeNickNameMethod(_ newNickName: String) { 275 | self.nickName = newNickName 276 | } 277 | 278 | func eatMethod(food: String) -> Bool { 279 | return food == "dinner" ? true : false 280 | } 281 | } 282 | 283 | class ProtocolTest: NSObject { 284 | 285 | public class func testBasic() { 286 | student.runMethod() 287 | var liLei = student(nickName: "xiao li", age: 18) 288 | liLei.studyMethod() 289 | let isEated = liLei.eatMethod(food: "dinner") 290 | liLei.changeNickNameMethod("Lao Li") 291 | print("\(liLei.age) \(liLei.nickName) \(isEated)") 292 | } 293 | } 294 | // runMethod 295 | // studyMethod 296 | // 18 Lao Li true 297 | // image 298 | // image 299 | // image 300 | ``` 301 | 302 | * 对构造器的规定 303 | 304 | 协议可以要求它的遵循者实现指定的构造器(在协议的定义里写下构造器的声明,但不需实现) 305 | 306 | * 协议类型 307 | 308 | 尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。 309 | 310 | 协议可以像其他普通类型一样使用,使用场景: 311 | 312 | 作为函数、方法或构造器中的参数类型或返回值类型 313 | 作为常量、变量或属性的类型 314 | 作为数组、字典或其他容器中的元素类型 315 | 316 | > ## 范型 317 | 318 | 319 | * Swift 提供了泛型让你写出灵活且可重用的函数和类型。 320 | * Swift 标准库是通过泛型代码构建出来的。 321 | * Swift 的数组和字典类型都是泛型集。 322 | 323 | 324 | ``` 325 | // 交换两个字符串 326 | public class func swapTwoString(_ value1: inout String,_ value2: inout String) { 327 | (value1, value2) = (value2, value1) 328 | } 329 | // 交换两个浮点数 330 | public class func swapTwoDouble(_ value1: inout Double,_ value2: inout Double) { 331 | (value1, value2) = (value2, value1) 332 | } 333 | // 以上函数功能相同只是类型不同,所以使用范型来避免重复代码 334 | public class func swapTwoValues(_ value1: inout T,_ value2: inout T) { 335 | (value1, value2) = (value2, value1) 336 | } 337 | ``` 338 | 339 | 函数名后面跟着用尖括号括起来占位类型名 T,尖括号告诉Swift编译器那个 T 是函数定义内的一个占位类型名,因此编译器不会去查找名为 T 的实际类型。 340 | 341 | 342 | * 范型类型 343 | 344 | Swift 允许定义自己的泛型类型 345 | 346 | ``` 347 | // 模仿栈操作 348 | struct DDYStack { 349 | var items = [Element]() 350 | mutating func push(_ item: Element) { 351 | items.append(item) 352 | } 353 | mutating func pop() -> Element { 354 | return items.removeLast() 355 | } 356 | } 357 | 358 | public class func testGenerics() { 359 | var stackString = DDYStack() 360 | stackString.push("one") 361 | stackString.push("two") 362 | let removedElement = stackString.pop() 363 | print("\(stackString) \(removedElement)") 364 | 365 | var stackDouble = DDYStack() 366 | stackDouble.push(3.14) 367 | print("\(stackDouble)") 368 | } 369 | ``` 370 | 371 | * 扩展泛型类型 372 | 373 | 当用extension扩展一个泛型类型的时候,并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 374 | 375 | ``` 376 | extension Stack { 377 | var topItem: Element? { 378 | return items.isEmpty ? nil : items[items.count - 1] 379 | } 380 | } 381 | ``` 382 | 383 | * 类型约束 384 | 类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。 385 | 386 | * 类型约束语法 387 | 你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同): 388 | 389 | ``` 390 | // 第一个类型参数T(T必须是DDYClass子类的类型约束,第二个参数U(符合DDYProtocol协议的类型约束) 391 | func ddyFunc(someT: T, someU: U) { 392 | // TODO: 393 | } 394 | ``` 395 | 396 | * 关联类型 397 | 398 | associatedtype关键字设置关联类型实例 399 | 400 | ``` 401 | // 遵循该协议就要满足协议内的条件内容 402 | protocol DDYBaseStackProtocol { 403 | // 使用associatedtype关键字来设置关联类型实例 404 | associatedtype DDYItemType 405 | // 添加一个新元素到容器 406 | mutating func append(_ item: DDYItemType) 407 | // 获取容器中元素个数 408 | var count: Int { get } 409 | // 通过索引类型下标检索容器中每一个元素 410 | subscript(i: Int) -> DDYItemType { get } 411 | } 412 | 413 | // 模仿栈操作 414 | struct DDYStack: DDYBaseStackProtocol { 415 | // 原始部分 416 | var items = [Element]() 417 | mutating func push(_ item: Element) { 418 | items.append(item) 419 | } 420 | mutating func pop() -> Element { 421 | return items.removeLast() 422 | } 423 | // DDYBaseStackProtocol协议实现部分 424 | mutating func append(_ item: Element) { 425 | self.push(item) 426 | } 427 | var count: Int { 428 | return items.count 429 | } 430 | subscript(i: Int) -> Element { 431 | return items[i] 432 | } 433 | } 434 | 435 | // 使用 436 | var stackInt = DDYStack() 437 | stackInt.push(1) 438 | stackInt.append(2) 439 | print("打印 \(stackInt.count) \(stackInt.items) \(stackInt[1])") 440 | ``` 441 | 442 | > ## 扩展 443 | 444 | 扩展(extension)就是向一个已有的类、结构体或枚举类型添加新功能,但不能重写已有的功能。 445 | 446 | Swift 中的扩展可以: 447 | 448 | * 添加计算型属性和计算型静态属性 449 | * 定义实例方法和类型方法 450 | * 提供新的构造器 451 | * 定义下标 452 | * 定义和使用新的嵌套类型 453 | * 使一个已有类型符合某个协议 454 | 455 | ``` 456 | extension MyClass: MyProtocolOne, MyProtocolTwo { 457 | // 协议实现 458 | } 459 | ``` 460 | 461 | 注意:扩展不能直接添加存储属性 462 | 463 | 间接方式用扩展给类(结构体,枚举)添加存储属性 464 | 465 | ``` 466 | extension UILabel { 467 | 468 | var edgeInsets: UIEdgeInsets { 469 | get { 470 | guard let edgeInsets = objc_getAssociatedObject(self, &edgeInsetsKey) as? UIEdgeInsets else { 471 | return UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) 472 | } 473 | return edgeInsets 474 | } 475 | set { 476 | objc_setAssociatedObject(self, &edgeInsetsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC) 477 | } 478 | } 479 | 480 | public static func ddySwizzleMethod() { 481 | ddySwizzle(#selector(draw(_:)), newSel: #selector(ddyDraw(_:))) 482 | ddySwizzle(#selector(getter: UILabel.intrinsicContentSize), newSel: #selector(getter: UILabel.ddyIntrinsicContentSize)) 483 | } 484 | private static func ddySwizzle(_ oldSel: Selector, newSel: Selector) { 485 | guard let m1 = class_getInstanceMethod(self, oldSel) else { 486 | return 487 | } 488 | guard let m2 = class_getInstanceMethod(self, newSel) else { 489 | return 490 | } 491 | 492 | if (class_addMethod(self, newSel, method_getImplementation(m2), method_getTypeEncoding(m2))) { 493 | class_replaceMethod(self, newSel, method_getImplementation(m1), method_getTypeEncoding(m1)) 494 | } else { 495 | method_exchangeImplementations(m1, m2) 496 | } 497 | } 498 | 499 | @objc func ddyDraw(_ rect: CGRect) { 500 | 501 | } 502 | 503 | @objc var ddyIntrinsicContentSize: CGSize { 504 | get { 505 | let superSize = super.intrinsicContentSize 506 | return CGSize(width: superSize.width + edgeInsets.left + edgeInsets.right, height: superSize.height + edgeInsets.top + edgeInsets.bottom) 507 | } 508 | } 509 | } 510 | ``` 511 | 512 | 513 | [where语句](http://www.hangge.com/blog/cache/detail_1826.html) 514 | 515 | 类型约束能够确保类型符合泛型函数或类的定义约束。 516 | 在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。 517 | 518 | ``` 519 | 520 | // 扩展的protocol 用于扩展具体功能 521 | protocol DDYBaseExtensionProtocol { 522 | associatedtype DDYT 523 | /// 访问原来的value 524 | var ddyValue: DDYT { get } 525 | } 526 | 527 | // 扩展点protocol 作为扩展和访问点 528 | protocol DDYBaseExtensionPoint { 529 | associatedtype DDYT 530 | /// 访问扩展功能 531 | var ddy: DDYT { get } 532 | } 533 | 534 | // 扩展基础实现 535 | public final class DDYBaseExtensionImpl: DDYBaseExtensionProtocol { 536 | 537 | typealias DDYT = T 538 | 539 | public let ddyValue: T 540 | 541 | public init(ddyValue: T) { 542 | self.ddyValue = ddyValue 543 | } 544 | } 545 | 546 | extension String: DDYBaseExtensionPoint { 547 | var ddy: DDYBaseExtensionImpl { 548 | return DDYBaseExtensionImpl.init(ddyValue: self) 549 | } 550 | } 551 | 552 | extension DDYBaseExtensionProtocol where DDYT == String { 553 | internal func ddyTestTestTest() -> String { 554 | return ddyValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 555 | } 556 | } 557 | 558 | 559 | class ExtensionWhereTest: NSObject { 560 | 561 | public class func testBasic() { 562 | ExtensionWhereTest().testExtensionProtocol() 563 | } 564 | 565 | private func testExtensionProtocol() { 566 | let testStr: String = " 1234567890 " 567 | let finalStr = testStr.ddy.ddyTestTestTest() 568 | print("\(finalStr)") 569 | } 570 | } 571 | ``` 572 | 573 | 574 | 575 | [swift泛型整理](https://www.jianshu.com/p/36448c6312a3) 576 | 577 | [.](https://www.runoob.com/swift/swift-extensions.html) 578 | [.](https://www.jianshu.com/p/325aa6168013) 579 | 580 | 581 | [上一页 Swift3-访问修饰词 函数 闭包](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift003.md) 582 | [下一页 Swift5-转型 可选链 其他知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift005.md) 583 | -------------------------------------------------------------------------------- /Swift/Swift005.md: -------------------------------------------------------------------------------- 1 | # Swift5-转型 可选链 其他知识点 2 | 3 | > ## 转型 4 | 5 | is 用于检测值的类型,as 用于转换类型。 6 | 7 | ``` 8 | import UIKit 9 | 10 | class MyBaseClass { 11 | var baseName: String 12 | init(name: String) { 13 | self.baseName = name 14 | } 15 | } 16 | 17 | class MyAppleClass: MyBaseClass { 18 | var myPrice: Float 19 | init(name: String, price: Float) { 20 | self.myPrice = price 21 | super.init(name: name) 22 | } 23 | } 24 | 25 | class MyPenClass: MyBaseClass { 26 | var myColor: UIColor 27 | init(name: String, color: UIColor) { 28 | self.myColor = color 29 | super.init(name: name) 30 | } 31 | } 32 | 33 | class CastingTest: NSObject { 34 | 35 | lazy var myClassArray = [MyAppleClass(name: "富士", price: 2.5), 36 | MyPenClass(name: "好得利", color: UIColor.blue), 37 | MyAppleClass(name: "金帅", price: 3.3)] 38 | 39 | public class func testBasic() { 40 | CastingTest().testIs() 41 | CastingTest().testAs() 42 | } 43 | 44 | private func testIs() { 45 | 46 | for item in myClassArray { 47 | if item is MyAppleClass { 48 | let apple = item as! MyAppleClass 49 | print("\(apple.baseName) \(apple.myPrice)") 50 | } else if item is MyPenClass { 51 | let pen = item as! MyPenClass 52 | print("\(pen.baseName) \(pen.myColor)") 53 | } 54 | } 55 | } 56 | 57 | private func testAs() { 58 | // 向下转型,用类型转换操作符(as? 或 as!) 59 | // 当不确定向下转型是否成功时,用类型转换的条件形式(as?)。 60 | // 只确定向下转型一定会成功时,才使用强制形式(as!)。当试图向下转型为一个不正确的类型时,强制形式会触发一个运行时错误。 61 | for item in myClassArray { 62 | if let apple = item as? MyAppleClass { 63 | print("\(apple.baseName) \(apple.myPrice)") 64 | } else if let pen = item as? MyPenClass { 65 | print("\(pen.baseName) \(pen.myColor)") 66 | } 67 | } 68 | } 69 | 70 | } 71 | ``` 72 | 73 | > ## 可选链 74 | 75 | 通过在属性、方法、或下标脚本的可选值后面放一个问号(?),即可定义一个可选链。 76 | 77 | * 如果目标有值,调用就会成功,返回该值 78 | * 如果目标为nil,调用将返回nil 79 | 80 | 感叹号(!)强制展开方法,属性,下标脚本可选链 81 | 82 | * 如果目标有值,调用就会成功,返回该值 83 | * 如果目标为nil,强制展开则报错 84 | 85 | fatal error: unexpectedly found nil while unwrapping an Optional value 86 | 87 | > ## 其他知识点 88 | 89 | 父类又叫超类 90 | 如果要重写父类中的方法,需要加 override 91 | 如果要访问父类中的方法(子类对象访问父类方法)用super(此时super表示self当前对象) 92 | 93 | 重写属性 94 | 你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。 95 | 96 | 子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。所以你在重写一个属性时,必需将它的名字和类型都写出来。 97 | 98 | 注意点: 99 | 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。 100 | 如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过 super.someProperty来返回继承来的值,其中someProperty是你要重写的属性的名字。 101 | 102 | 重写属性观察器 103 | 你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会监测到。 104 | 注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器 105 | 106 | 防止重写 107 | 我们可以使用 final 关键字防止它们被重写。 108 | 如果你重写了final方法,属性或下标脚本,在编译时会报错。 109 | 你可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,否则会报编译错误。 110 | 111 | \_\_weak 与\_\_unretained有何区别? 112 | \_\_weak修饰的弱引用,如果指向的对象被销毁,那么指针会立马指向nil 113 | \_\_unretained修饰的弱引用,如果指向的对象被销毁,它的指针依然会指向之前的内存地址,很容易产生野指针(僵尸对象) 114 | 115 | swift中[Error](https://developer.apple.com/documentation/swift/error) 116 | 117 | ``` 118 | public protocol Error { 119 | } 120 | 121 | extension Error { 122 | } 123 | 124 | extension Error where Self.RawValue : SignedInteger { 125 | } 126 | 127 | extension Error where Self.RawValue : UnsignedInteger { 128 | } 129 | ``` 130 | 131 | 苹果文档第一个例子 132 | 133 | ``` 134 | // 枚举对Int值处理可能的Error分类 135 | enum IntParsingError: Error { 136 | case overflow 137 | case invalidInput(Character) 138 | } 139 | 140 | // 扩展Int 141 | extension Int { 142 | init(validating input: String) throws { 143 | // ... 144 | let c = _nextCharacter(from: input) 145 | if !_isValid(c) { 146 | throw IntParsingError.invalidInput(c) 147 | } 148 | // ... 149 | } 150 | } 151 | 152 | // 具体使用(捕获) 153 | do { 154 | let price = try Int(validating: "$100") 155 | } catch IntParsingError.invalidInput(let invalid) { 156 | print("Invalid character: '\(invalid)'") 157 | } catch IntParsingError.overflow { 158 | print("Overflow error") 159 | } catch { 160 | print("Other error") 161 | } 162 | // Prints "Invalid character: '$'" 163 | ``` 164 | 165 | 第二个例子 166 | 167 | ``` 168 | // 自定义错误输出结构体 169 | struct XMLParsingError: Error { 170 | enum ErrorKind { 171 | case invalidCharacter 172 | case mismatchedTag 173 | case internalError 174 | } 175 | 176 | let line: Int 177 | let column: Int 178 | let kind: ErrorKind 179 | } 180 | // 抛出错误 181 | func parse(_ source: String) throws -> XMLDoc { 182 | // ... 183 | throw XMLParsingError(line: 19, column: 5, kind: .mismatchedTag) 184 | // ... 185 | } 186 | // 具体应用 187 | do { 188 | let xmlDoc = try parse(myXMLData) 189 | } catch let e as XMLParsingError { 190 | print("Parsing error: \(e.kind) [\(e.line):\(e.column)]") 191 | } catch { 192 | print("Other error: \(error)") 193 | } 194 | // Prints "Parsing error: mismatchedTag [19:5]" 195 | ``` 196 | 197 | Alamofire中的错误示例 198 | 199 | Alamofire中的错误处理,用单独的一个文件AFError来管理使用中的错误,其中自定义了枚举 200 | 201 | ``` 202 | public enum AFError: Error { 203 | encoding process. 204 | public enum ParameterEncodingFailureReason { 205 | case missingURL 206 | case jsonEncodingFailed(error: Error) 207 | case propertyListEncodingFailed(error: Error) 208 | } 209 | 210 | 211 | public enum MultipartEncodingFailureReason { 212 | case bodyPartURLInvalid(url: URL) 213 | case bodyPartFilenameInvalid(in: URL) 214 | case bodyPartFileNotReachable(at: URL) 215 | case bodyPartFileNotReachableWithError(atURL: URL, error: Error) 216 | case bodyPartFileIsDirectory(at: URL) 217 | case bodyPartFileSizeNotAvailable(at: URL) 218 | case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) 219 | case bodyPartInputStreamCreationFailed(for: URL) 220 | 221 | case outputStreamCreationFailed(for: URL) 222 | case outputStreamFileAlreadyExists(at: URL) 223 | case outputStreamURLInvalid(url: URL) 224 | case outputStreamWriteFailed(error: Error) 225 | 226 | case inputStreamReadFailed(error: Error) 227 | } 228 | 229 | 230 | public enum ResponseValidationFailureReason { 231 | case dataFileNil 232 | case dataFileReadFailed(at: URL) 233 | case missingContentType(acceptableContentTypes: [String]) 234 | case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) 235 | case unacceptableStatusCode(code: Int) 236 | } 237 | 238 | 239 | public enum ResponseSerializationFailureReason { 240 | case inputDataNil 241 | case inputDataNilOrZeroLength 242 | case inputFileNil 243 | case inputFileReadFailed(at: URL) 244 | case stringSerializationFailed(encoding: String.Encoding) 245 | case jsonSerializationFailed(error: Error) 246 | case propertyListSerializationFailed(error: Error) 247 | } 248 | 249 | case invalidURL(url: URLConvertible) 250 | case parameterEncodingFailed(reason: ParameterEncodingFailureReason) 251 | case multipartEncodingFailed(reason: MultipartEncodingFailureReason) 252 | case responseValidationFailed(reason: ResponseValidationFailureReason) 253 | case responseSerializationFailed(reason: ResponseSerializationFailureReason) 254 | } 255 | ``` 256 | 257 | 作者还将AFError进行了扩展,增加部分判断的方法,比如判断是否是无效的URL: 258 | 259 | ``` 260 | // MARK: - Error Booleans 261 | 262 | extension AFError { 263 | /// Returns whether the AFError is an invalid URL error. 264 | public var isInvalidURLError: Bool { 265 | if case .invalidURL = self { return true } 266 | return false 267 | } 268 | } 269 | ``` 270 | 271 | 同时拓展中也遵守了LocalizedError协议来重写errorDescription属性,并且对已经分类enum的自定义错误类型进行拓展重写了属性localizedDescription,这样就构成了项目中完整的错误处理机制。比如在Alamofire编码解析方法方法encode中,有这么一段代码 272 | 273 | ``` 274 | do { 275 | let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) 276 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 277 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 278 | } 279 | urlRequest.httpBody = data 280 | } catch { 281 | throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 282 | } 283 | ``` 284 | 285 | 其中就是对于JSON序列化时,通过 do { try } catch 的方式来捕获错误,如果捕获到,将抛出已经自定义好的 AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 类型错误。 286 | 287 | 致命错误 288 | 289 | 喵神举例的两个使用的场景: 290 | 291 | 1.父类中的某些方法,不想让别人调用,可以在方法中加上fatalError,这样子类如果想到用必须重写 292 | 2.对于其他一切我们不希望别人随意调用,但是又不得不去实现的方法,我们都应该使用 fatalError 来避免任何可能的误会。 293 | 294 | ``` 295 | required init?(coder aDecoder: NSCoder) { 296 | fatalError("init(coder:) has not been implemented") 297 | } 298 | ``` 299 | 300 | [原文请移步](https://www.jianshu.com/p/3f3c32ed4aa4) 301 | 302 | 303 | 图片放入工程两个方式 304 | 305 | 1.Assets.xcassets内,同时生成描述文件Contents.json。且在打包后以Assets.car的形式存在,不能直接打开(据说工具ThemeEngine可以打开)。 306 | 以此方式放入的图片并不在mainBundle中,不能使用contentOfFile这样的API来加载图片,interface builder中使用图片时不需要后缀和倍数标识(@2x这样的) 307 | 2.在工程建立专门放图片的文件夹,然后放入图片 308 | 309 | Assets优势: 310 | 311 | 1.性能好,节省Disk。Asset Catalogs会用一个高度优化的特殊格式来存所有图片,而不是一个一个的单独的图片资源,会更少的涉及频繁Disk I/O操作,且会按需下载适合你机型的合适分辨率的图片资源; 312 | 2.相对安全。图片资源得到一定程度保护(Asset.car不易打开) 313 | 314 | 315 | swift中Data获取bytes数组 316 | 317 | 方法1:使用 [UInt8] 新的构造函数 318 | 319 | ``` 320 | /// 使用(data as NSData).bytes 并不优雅 毕竟要尽量脱离OC的框架 321 | /// data是结构体 使用[UInt8]构造方法得到data的byte数组 322 | let bytes = [UInt8](data) 323 | ``` 324 | 325 | 方法2:通过 Pointer 指针获取 326 | 327 | ``` 328 | let bytes = data.withUnsafeBytes { 329 | [UInt8](UnsafeBufferPointer(start: $0, count: data.count)) 330 | } 331 | print(bytes) 332 | ``` 333 | 334 | 335 | 封包与拆包 336 | 337 | * 拆包概念 338 | 就是一个可选类型有值的时候,打印结果带有可选类型(Optional)标记,当我们去掉可选类型(Optional)的过程就成为拆包,例如:将Optional String类型强制转换为String类型的过程,就是一种拆包过程 339 | 340 | * 强制拆包 341 | 当我们在进行拆包过程中,如果对可选类型(Optional)是否有值不做处理,进行拆包的过程,就是一种强制拆包的过程,如果某一个可选类型(Optional)没有值,而我们又进行强制拆包的操作,就会崩溃(fatal error: unexpectedly found nil while unwrapping an Optional value) 342 | 343 | * 封包 344 | 简单来就是将一种确定的数据类型转换为可选类型(Optional)的过程,我们称之为封包。例如:将String类型转换为Optional String类型的过程,其实就是一种封包过程。 345 | 346 | 347 | pngData()和jpegData() 为nil 348 | 349 | 文档中指明:May return nil if image has no CGImageRef or invalid bitmap format 350 | 351 | 352 | [上一页 Swift4-类 属性 协议 范型 扩展](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift004.md) 353 | [下一页 Swift6-多线程](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift006.md) -------------------------------------------------------------------------------- /Swift/Swift006.md: -------------------------------------------------------------------------------- 1 | # Swift6-多线程 2 | 3 | > ## 相关概念 4 | 5 | * 进程 6 | 指在系统中正在运行的一个应用程序,进程拥有独立运行所需的全部资源(例如:正在运行的QQ就是一个进程)。 7 | 8 | * 线程 9 | 指程序中独立运行的代码段(例如:接收QQ消息的代码),一个进程是由一或多个线程组成。 10 | 11 | * 多线程 12 | 1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务,进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。 13 | 14 | * 单线程与多线程对比 15 | - 单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)。 16 | - 多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。 17 | 18 | * 线程相关 19 | - 同步线程:同步线程会阻塞当前线程去执行线程内的任务,执行完之后才会反回当前线程。 20 | - 异步线程:异步线程不会阻塞当前线程,会开启其他线程去执行线程内的任务。 21 | - 串行队列:线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)。 22 | - 并发队列:多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的。 23 | - 并发VS并行:并行是基于多核设备的,并行一定是并发,并发不一定是并行。 24 | 25 | * 死锁 26 | 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去 27 | 28 | 例如主线程串行队列同步执行任务引起死锁 29 | 30 | ``` 31 | DispatchQueue.main.sync { 32 | print("死锁了不执行") 33 | } 34 | ``` 35 | 36 | 自定义串行队列同步任务 嵌套 该自定义串行队列同步任务,产生死锁 37 | 38 | ``` 39 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 40 | serialQueue.sync { 41 | print("执行了1") 42 | serialQueue.sync { 43 | print("死锁了不执行") 44 | } 45 | } 46 | 47 | ``` 48 | 49 | 自定义串行队列异步任务 嵌套 该自定义串行队列同步任务, 产生死锁 50 | 51 | ``` 52 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 53 | serialQueue.async { 54 | print("执行了1") 55 | serialQueue.sync { 56 | print("死锁了不执行") 57 | } 58 | } 59 | print("执行了3") 60 | ``` 61 | 62 | 总结:遇到串行同步要小心 63 | 64 | * 死锁的四个必要条件 65 | 66 | - 互斥条件:一个资源每次只能被一个进程使用。 67 | - 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 68 | - 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 69 | - 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 70 | 71 | * 避免死锁的方法 72 | 1. 破坏“互斥”条件:就是在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他三个必要条件,而不去涉及破坏“互斥”条件。 73 | 2. 破坏“请求和保持”条件:在系统中不允许进程在已获得某种资源的情况下,申请其他资源。即要想出一个办法,阻止进程在持有资源的同时申请其他资源。 74 | 方法:要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源S时,须先把它先前占有的资源R释放掉,然后才能提出对S的申请,即使它可能很快又要用到资源R。 75 | 3. 破坏“不可抢占”条件:允许对资源实行抢夺。 76 | 方法一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。 77 | 方法二:如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。 78 | 4. 破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。 79 | 方法:利用银行家算法避免死锁。 80 | 81 | [死锁参考](https://blog.csdn.net/yanxiaolx/article/details/51944048) 82 | 83 | * 线程安全 84 | 85 | 一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。 86 | 87 | 互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源 88 | 原子属性(atomic)加锁 89 | 90 | atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。 91 | 92 | nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。 93 | 94 | > ## 几种多线程技术比较 95 | 96 | * pthread 97 | 优点: 跨平台,可移植性强 98 | 缺点: 程序员管理生命周期,使用难度大。一般不用 99 | 100 | * NSThread (抽象层次:低) 101 | 优点:轻量级,简单易用,可以直接操作线程对象 102 | 缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。 103 | 104 | * Cocoa NSOperation (抽象层次:中) 105 | 优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象 106 | 缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation. 107 | 108 | * GCD 全称Grand Center Dispatch (抽象层次:高) 109 | 优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。 110 | 缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。 111 | 112 | GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD 113 | 114 | * 为什么要用 GCD 呢? 115 | - GCD 可用于多核的并行运算 116 | - GCD 会自动利用更多的 CPU 内核(比如双核、四核) 117 | - GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 118 | - 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码 119 | 120 | 121 | > ## GCD 122 | 123 | 1.主线程串行队列(main queue):提交至Main queue的任务会在主线程中执行,Main queue 可以通过```DispatchQueue.main```来获取,主队列一定伴随主线程,但主线程不一定伴随主队列。 124 | 2.全局并发队列(Global queue):全局并发队列由整个进程共享,有Qos优先级别。Global queue 可以通过调用```DispatchQueue.global()```函数来获取(可以设置优先级) 125 | 3.自定义队列(Custom queue): 可以为串行,也可以为并发。Custom queue 可以通过```DispatchQueue(label: String)和DispatchQueue(label: String, attributes: DispatchQueue.Attributes.concurrent)```来获取; 126 | 4.队列组 (Group queue):将多线程进行分组,最大的好处是可获知所有线程的完成情况。 127 | Group queue 可以通过调用dispatch_group_create()来创建,通过dispatch_group_notify 可以直接监听组里所有线程完成情况。 128 | 129 | * 主线程串行队列(The main queue) 130 | 131 | 1.主线程串行队列同步执行任务,在主线程运行时,会产生死锁 132 | 133 | ``` 134 | DispatchQueue.main.sync { 135 | print("死锁了不执行") 136 | } 137 | ``` 138 | 139 | 2.主线程串行队列异步执行任务,在主线程运行,不会产生死锁 140 | 141 | ``` 142 | DispatchQueue.main.async { 143 | print("执行了") 144 | } 145 | ``` 146 | 147 | 3.安全异步主线程主队列 148 | 149 | ``` 150 | import Foundation 151 | 152 | extension DispatchQueue { 153 | fileprivate static var currentQueueLabel: String? { 154 | let cString = __dispatch_queue_get_label(nil) 155 | return String(cString: cString) 156 | } 157 | // "com.apple.main-thread" 158 | fileprivate static var isMainQueue: Bool { 159 | return currentQueueLabel == self.main.label 160 | } 161 | } 162 | 163 | func ddyMainAsyncSafe(_ execute: @escaping () -> Void) { 164 | DispatchQueue.isMainQueue ? execute() : DispatchQueue.main.async(execute: execute) 165 | } 166 | 167 | // 调用 168 | ddyMainAsyncSafe { 169 | print("主线程主队列刷新UI") 170 | } 171 | ``` 172 | 173 | 附:RxSwift中判断主线程主队列方式 174 | 175 | ``` 176 | extension DispatchQueue { 177 | private static var token: DispatchSpecificKey<()> = { 178 | let key = DispatchSpecificKey<()>() 179 | DispatchQueue.main.setSpecific(key: key, value: ()) 180 | return key 181 | }() 182 | 183 | static var isMain: Bool { 184 | return DispatchQueue.getSpecific(key: token) != nil 185 | } 186 | } 187 | ``` 188 | 189 | 注意:主线程串行队列由系统默认生成,无法调用conQueue.resume()和Queue.suspend()来控制执行继续或中断。 190 | 191 | * 全局并发队列(Global queue) 192 | 193 | 耗时操作(如网络请求,IO,数据库读写等)在子线程中处理,然后通知主线程更新界面 194 | 195 | 1.全局并发队列同步执行任务,在主线程执行会导致页面卡顿。 196 | 197 | ``` 198 | print("同步执行任务 1") 199 | DispatchQueue.global().sync { 200 | print("同步执行任务 2") 201 | } 202 | print("同步执行任务 3") 203 | ``` 204 | 2 全局并发队列异步执行任务,会开启新的子线程去执行任务,页面不会卡顿。 205 | 206 | ``` 207 | print("异步执行任务 1") 208 | DispatchQueue.global().async { 209 | print("异步执行任务 2") 210 | } 211 | print("异步执行任务 3") 212 | ``` 213 | 214 | 3 多个全局并发队列,异步执行任务。 215 | 216 | ``` 217 | print("全局并发队列 异步执行任务 1") 218 | DispatchQueue.global().async { 219 | print("全局并发队列 异步执行任务 2") 220 | } 221 | DispatchQueue.global().async { 222 | print("全局并发队列 异步执行任务 3") 223 | } 224 | print("全局并发队列 异步执行任务 4") 225 | ``` 226 | 227 | 异步线程的执行顺序是不确定的,几乎同步开始执行,2和3 顺序不确定 228 | 全局并发队列由系统默认生成,无法调用conQueue.resume()和Queue.suspend()来控制执行继续或中断。 229 | 230 | 附 231 | ``` 232 | // Swift3开始使用了DispatchWorkItem类将任务封装成为对象,由对象进行任务。 233 | let item = DispatchWorkItem { 234 | // do task 235 | } 236 | DispatchQueue.global().async(execute: item) 237 | 238 | // 也可以使用DispatchWorkItem实例对象的perform方法执行任务 239 | let workItem = DispatchWorkItem { 240 | // do task 241 | } 242 | DispatchQueue.global().async { 243 | workItem.perform() 244 | } 245 | ``` 246 | 247 | * 自定义队列 (Custom queue) 248 | 249 | 1 自定义串行队列同步执行任务(依次执行) 250 | 251 | ``` 252 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 253 | print("自定义串行队列同步执行任务 1") 254 | serialQueue.async { 255 | print("自定义串行队列同步执行任务 2") 256 | } 257 | serialQueue.async { 258 | print("自定义串行队列同步执行任务 3") 259 | } 260 | print("自定义串行队列同步执行任务 4") 261 | ``` 262 | 263 | 2 自定义串行队列同步任务 嵌套 该自定义串行队列同步任务,产生死锁 264 | 265 | ``` 266 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 267 | serialQueue.sync { 268 | print("执行了1") 269 | serialQueue.sync { 270 | print("死锁了不执行") 271 | } 272 | } 273 | print("没执行") 274 | ``` 275 | 276 | 3 自定义串行队列同步任务嵌套并发队列同步任务 不死锁 277 | 278 | ``` 279 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 280 | let globalQueue = DispatchQueue.global() 281 | let concurrentQ = DispatchQueue(label: "concurrentQueue1", attributes: .concurrent) 282 | serialQueue.sync { 283 | print("执行了1") 284 | globalQueue.sync { 285 | print("执行了2") 286 | } 287 | concurrentQ.sync { 288 | print("执行了3") 289 | } 290 | } 291 | print("执行4") 292 | ``` 293 | 294 | 4 自定义串行队列同步执行任务 嵌套 另一个自定义串行队列同步任务 不死锁 295 | 296 | ``` 297 | let serialQueue1 = DispatchQueue(label: "com.ddy.serialQueue1") 298 | let serialQueue2 = DispatchQueue(label: "com.ddy.serialQueue2") 299 | serialQueue1.sync { 300 | print("执行了1") 301 | serialQueue2.sync { 302 | print("执行了2") 303 | } 304 | } 305 | print("执行了3") 306 | ``` 307 | 308 | 5 自定义串行队列异步执行任务 嵌套 该自定义串行队列同步任务,产生死锁 309 | 310 | ``` 311 | let serialQueue = DispatchQueue(label: "com.ddy.serialQueue") 312 | serialQueue.async { 313 | print("执行了1") 314 | serialQueue.sync { 315 | print("死锁了不执行") 316 | } 317 | } 318 | print("执行了3") 319 | ``` 320 | 总结:遇到嵌套串行队列同步任务要小心 可能 会死锁 321 | 322 | 323 | 6 自定义并发队列执行同步任务(顺序执行) 324 | 325 | ``` 326 | let concurrentQ = DispatchQueue(label: "concurrentQueue1", attributes: .concurrent) 327 | print("执行了 1") 328 | concurrentQ.sync { 329 | print("执行了 2") 330 | } 331 | concurrentQ.sync { 332 | print("执行了 3") 333 | } 334 | print("执行了 4") 335 | ``` 336 | 337 | 7 自定义并发队列同步任务 嵌套 该自定义并发队列同步任务 (顺序执行 不会死锁) 338 | 339 | ``` 340 | let concurrentQ = DispatchQueue(label: "concurrentQueue1", attributes: .concurrent) 341 | print("执行了 1") 342 | concurrentQ.sync { 343 | print("执行了 2") 344 | concurrentQ.sync { 345 | print("执行了 3") 346 | } 347 | } 348 | print("执行了 4") 349 | ``` 350 | 351 | 8 自定义并发队列执行异步任务(2,3不确定顺序) 352 | 353 | ``` 354 | let concurrentQ = DispatchQueue(label: "concurrentQueue1", attributes: .concurrent) 355 | print("执行了 1") 356 | concurrentQ.async { 357 | print("执行了 2") 358 | } 359 | concurrentQ.async { 360 | print("执行了 3") 361 | } 362 | print("执行了 4") 363 | ``` 364 | 365 | 队列便利构造器 366 | 367 | - label: 队列的唯一标识符(用于调试) 368 | - qos: 队列的优先级(.userInteractive .userInitiated .default .utility .background .unspecified) 369 | - .userInteractive:用户交互相关,为了好的用户体验,任务需要立马执行。使用该优先级用于UI更新,事件处理和小工作量任务,在主线程执行。 370 | - .userInitiated:优先级等同于DISPATCH_QUEUE_PRIORITY_HIGH,需要立刻的结果 371 | - .default:默认优先级,优先级等同于DISPATCH_QUEUE_PRIORITY_DEFAULT,建议大多数情况下使用默认优先级 372 | - .utility:优先级等同于DISPATCH_QUEUE_PRIORITY_LOW,可以执行很长时间,再通知用户结果。比如:下载一个大文件,网络,计算 373 | - .background:最低优先级,等同于DISPATCH_QUEUE_PRIORITY_BACKGROUND. 用户不可见,比如:在后台存储大量数据 374 | - .unspecified:未定义 375 | - attributes: 队列属性(attributes是一个结构体并遵守OptionSet协议,所以传入的参数可以为[.option1, .option2]) 376 | - .concurrent并发队列, 377 | - .initiallyInactive表明队列需要手动开启。不填写时默认队列串行、自动执行 378 | - autoreleaseFrequency:自动释放频率,有些列队会在执行完任务之后自动释放,有些是不会自动释放的,需要手动释放。官方文档是说当设为.workItem时,所有异步任务提交的代码块会被封装成独立的任务,在同步执行的队列则不受影响。 379 | - target:目标队列 380 | 381 | ``` 382 | // 队列的便利构造函数(便利构造器) 383 | public convenience init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil) 384 | // 优先级顺序:userInteractive> userInitiated> default> utility> background> unspecified 385 | 386 | let label = "com.ddy.concurrentQueue" 387 | let qos = DispatchQoS.default 388 | let attributes = DispatchQueue.Attributes.concurrent 389 | let autoreleaseFrequnecy = DispatchQueue.AutoreleaseFrequency.never 390 | let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequnecy, target: nil) 391 | ``` 392 | 393 | 等待任务结束 394 | 395 | ``` 396 | let concurrentQ = DispatchQueue(label: "concurrentQueue1", attributes: .concurrent) 397 | // 可以在初始化的时候指定更多的参数 398 | let workItem = DispatchWorkItem(qos: .default, flags: .barrier) { 399 | sleep(5) 400 | print("done") 401 | } 402 | concurrentQ.async(execute: workItem) 403 | print("before waiting") 404 | workItem.wait() 405 | print("after waiting") 406 | // before waiting 407 | // done 408 | // after waiting 409 | ``` 410 | 411 | * 队列组(Group queue) 412 | 413 | 场景1: A、B、C 三个任务并发异步执行,都执行完才执行D任务(三个网络请求和都请求完毕才刷新UI) 414 | 415 | ``` 416 | // 并发队列 417 | let concurrentQ = DispatchQueue(label: "com.ddy.concurrentQueue", attributes: .concurrent) 418 | // 创建组 419 | let group = DispatchGroup() 420 | // 网络请求1 421 | group.enter() 422 | concurrentQ.async { 423 | DDYRequest.request(["test":"1"], { (success: Bool) in 424 | group.leave() 425 | }) 426 | } 427 | // 网络请求2 428 | group.enter() 429 | concurrentQ.async { 430 | DDYRequest.request(["test":"2"], { (success: Bool) in 431 | group.leave() 432 | }) 433 | } 434 | // 调度组里的任务都执行完毕执行 435 | group.notify(queue: concurrentQ) { 436 | DispatchQueue.global().async { 437 | // 处理数据 438 | DispatchQueue.main.async { 439 | // 刷新UI 440 | } 441 | } 442 | } 443 | ``` 444 | 445 | 场景2:A执行完才执行B(第一次请求网络拿到ID去再次请求网络拿具体数据,最后刷新UI) 446 | 447 | ``` 448 | // 并发队列 449 | let concurrentQ = DispatchQueue(label: "com.ddy.concurrentQueue", attributes: .concurrent) 450 | // 创建组 451 | let group = DispatchGroup() 452 | // 网络请求1 453 | group.enter() 454 | concurrentQ.async { 455 | DDYRequest.request(["test":"1"], { (success: Bool) in 456 | print("离开 1") 457 | group.leave() 458 | }) 459 | } 460 | group.wait() 461 | // 网络请求2 462 | group.enter() 463 | concurrentQ.async { 464 | DDYRequest.request(["test":"2"], { (success: Bool) in 465 | print("离开 2") 466 | group.leave() 467 | }) 468 | } 469 | // 调度组里的任务都执行完毕执行 470 | group.notify(queue: concurrentQ) { 471 | DispatchQueue.global().async { 472 | print("处理数据") 473 | DispatchQueue.main.async { 474 | print("刷新UI") 475 | } 476 | } 477 | } 478 | ``` 479 | 480 | group.wait(timeout: DispatchTime(uptimeNanoseconds: 10*NSEC_PER_SEC)) 来表示等待与超时 481 | 482 | > ## GCD一些常用函数 483 | 484 | * asyncAfter 延迟添加调用 485 | 486 | asyncAfter并不是在指定时间后执行任务处理,而是在指定时间后把任务追加到queue里面。因此会有少许延迟。 487 | 488 | ``` 489 | DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2.0) { 490 | print("2秒后执行的") 491 | } 492 | // let delay = DispatchTime.now() + Double(Int64(3 * 1000 * 1000000)) / Double(NSEC_PER_SEC) 493 | // let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10) 494 | let delay = DispatchTime.now() + 10 495 | DispatchQueue.main.asyncAfter(deadline: delay) { 496 | print("10秒后执行的") 497 | } 498 | // 注意:我们不能直接取消我们已经提交到 asyncAfter 里的任务代码。 499 | // 如需取消正在等待执行的Block操作,可先将这个Block封装到DispatchWorkItem对象中,然后对其发送cancle,来取消一个正在等待执行的block 500 | // 将要执行的操作封装到DispatchWorkItem中 501 | let task = DispatchWorkItem { print("3秒后执行被取消") } 502 | // 延时2秒执行 503 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0, execute: task) 504 | // 取消任务 505 | task.cancel() 506 | ``` 507 | * 循环执行concurrentPerform(OC快速迭代 apply) 508 | 509 | OC中GCD的dispatch_apply(),而Swift中用concurrentPerform() 510 | 511 | ``` 512 | let concurrentQ = DispatchQueue(label: "com.ddy.concurrentQueue", attributes:.concurrent) 513 | let array = ["1", "3", "5", "7", "9"]; 514 | concurrentQ.async { 515 | DispatchQueue.concurrentPerform(iterations: array.count) { (index) in 516 | print("\(array[index])") 517 | } 518 | } 519 | // 1 9 3 5 7 可以利用多核的优势,所以无序 520 | // 简化 迭代五次 521 | // DispatchQueue.concurrentPerform(iterations: 5) { 522 | // print("\($0)") 523 | // } 524 | ``` 525 | 526 | * 信号量 semaphore 527 | 528 | 创建信号量对象,调用signal方法发送信号,信号加1,调用wait方法等待,信号减1.用信号量实现刚刚的多个请求功能。 529 | 530 | ``` 531 | // DispatchSemaphore(value: ):用于创建信号量,可以指定初始化信号量计数值,这里我们默认1. 532 | // semaphore.wait():会判断信号量。如果是0,则等待,如果非0,则往下执行 533 | // semaphore.signal():代表运行结束,信号量加1,有等待的任务这个时候才会继续执行。 534 | let queue = DispatchQueue.global() 535 | let group = DispatchGroup() 536 | let semaphore = DispatchSemaphore(value: 0) 537 | 538 | queue.async(group: group) { 539 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: { 540 | semaphore.signal() 541 | print("Task one finished") 542 | }) 543 | semaphore.wait() 544 | } 545 | queue.async(group: group) { 546 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: { 547 | semaphore.signal() 548 | print("Task two finished") 549 | }) 550 | semaphore.wait() 551 | } 552 | queue.async(group: group) { 553 | print("Task three finished") 554 | } 555 | 556 | group.notify(queue: queue) { 557 | print("All task has finished") 558 | } 559 | ``` 560 | 561 | * 栅栏操作 barrier 562 | 563 | GCD里的Barrier和NSOperationQueue的dependency比较接近 564 | 只能用在自定义并行队列,且只保证任务中代码执行到,如果任务中存在异步则不保证执行完 565 | 566 | ``` 567 | let concurrentQ = DispatchQueue(label: "com.ddy.barrier", attributes: .concurrent) 568 | concurrentQ.async { 569 | print("task 0-0") 570 | DDYRequest.request(2, { (success: Bool) in 571 | print("task 0-1") 572 | }) 573 | print("task 0-3") 574 | } 575 | concurrentQ.async { 576 | print("task 1-0") 577 | DDYRequest.request(2, { (success: Bool) in 578 | print("task 1-1") 579 | }) 580 | print("task 1-3") 581 | } 582 | concurrentQ.async(flags: .barrier) { 583 | print("task 2-0") 584 | DDYRequest.request(2, { (success: Bool) in 585 | print("task 2-1") 586 | }) 587 | } 588 | concurrentQ.async { 589 | print("task 3-0") 590 | DDYRequest.request(2, { (success: Bool) in 591 | print("task 3-1") 592 | }) 593 | } 594 | ``` 595 | 596 | ``` 597 | let concurrentQ = DispatchQueue(label: "com.ddy.barrier", attributes: .concurrent) 598 | concurrentQ.async { 599 | print("task 0-0") 600 | DDYRequest.request(2, { (success: Bool) in 601 | print("task 0-1") 602 | }) 603 | print("task 0-3") 604 | } 605 | concurrentQ.async { 606 | print("task 1-0") 607 | DDYRequest.request(2, { (success: Bool) in 608 | print("task 1-1") 609 | }) 610 | print("task 1-3") 611 | } 612 | let writeTask = DispatchWorkItem(flags: .barrier) { 613 | print("task 2-0") 614 | DDYRequest.request(2, { (success: Bool) in 615 | print("task 2-1") 616 | }) 617 | } 618 | concurrentQ.async(execute: writeTask) 619 | concurrentQ.async { 620 | print("task 3-0") 621 | DDYRequest.request(2, { (success: Bool) in 622 | print("task 3-1") 623 | }) 624 | } 625 | ``` 626 | 627 | 执行顺序分析 628 | 先排除各个任务中异步延迟操作(该操作已经执行到,但不保证执行完回调) 629 | 0-0 630 | (0-3 1-0 1-3) or (1-0 1-3 0-3) or (1-0 0-3 1-3) 631 | 2-0 632 | 然后分析异步延迟的回调 633 | (0-1 1-1 3-1) or (0-1 3-1 1-1) or (1-1 0-1 3-1) or (1-1 3-1 0-1) or (3-1 0-1 1-1) or (3-1 1-1 0-1) 634 | 2-1 635 | 636 | * DispatchSource实现GCD定时器 637 | 638 | Timer的无奈: 639 | Timer的创建与撤销必须在同一个线程操作,在多线程环境下使用不便. 640 | 使用时必须保证有一个活跃的runloop,然而主线程的runloop是默认开启的,子线程的runloop却是默认不开启的,当在子线程中使用Timer的时候还需要先激活runloop,否则Timer是不会起效的. 641 | 内存泄漏问题. 在控制器中使用Timer的时候需要控制器对Timer进行强引用,然而Timer还会对控制器进行强引用,造成循环引用最终控制器无法释放导致内存泄漏. 642 | 643 | ``` 644 | private class func testGCDTimer() { 645 | // 倒计时总次数 646 | var timeCount = 20 647 | // 自定义并发队列 648 | let concurrentQ = DispatchQueue(label: "com.ddy.timer", attributes: .concurrent) 649 | // 在自定义队列的定时器 650 | let timer = DispatchSource.makeTimerSource(flags: [], queue: concurrentQ) 651 | // 设置立即开始 0.5秒循环一次 652 | timer.schedule(deadline: .now(), repeating: 0.5) 653 | // 触发回调事件 654 | timer.setEventHandler { 655 | timeCount = timeCount - 1 656 | if timeCount <= 0 { 657 | timer.cancel() 658 | } 659 | DispatchQueue.main.async { 660 | print("主线程更新UI \(timeCount)") 661 | } 662 | } 663 | // cancel事件回调 664 | timer.setCancelHandler { 665 | DispatchQueue.main.async { 666 | print("已结束I \(timeCount)") 667 | } 668 | } 669 | // 启动定时器 670 | timer.resume() 671 | } 672 | ``` 673 | 674 | 675 | [参考 Swift4 - GCD的使用](https://blog.csdn.net/longshihua/article/details/79756676) 676 | [参考 Swift4.0 - GCD](https://www.jianshu.com/p/96032a032c7c) 677 | [参考 多线程之GCD](https://blog.csdn.net/longshihua/article/details/50523051) 678 | [参考 从使用场景了解GCD新API](https://www.jianshu.com/p/fc78dab5736f) 679 | 680 | 681 | 682 | [上一页 Swift5-转型 可选链 其他知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift005.md) 683 | [下一页 Swift7-高阶函数 map flatMap compactMap filter reduce](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift007.md) 684 | 685 | -------------------------------------------------------------------------------- /Swift/Swift007.md: -------------------------------------------------------------------------------- 1 | # Swift7-高阶函数 map flatMap compactMap filter reduce 2 | 3 | > ## 相关概念 4 | 5 | Swift1中接触了高阶类型--元组(Tuple),现在再了解一个新概念--高阶函数。 6 | Swift作为一门多范式编程语言,其对函数式编程的支持成就了对高阶函数的应用。 7 | 高阶函数(Higher order functions),顾名思义,仍是函数,但这种函数能接受函数作为参数或者返回一个函数操作其他函数。 8 | 由此可见我们常用的闭包(closure)也算一种高阶函数 9 | 现在了解一下Swift中最典型的4个高阶函数:Map/FlatMap/Filter/Reduce 10 | 11 | > ## map 12 | 13 | 干嘛的? 对集合类型的数据进行循环,并对每个元素采取相同操作,然后返回同维集合 14 | 15 | 例如,要对一个一维整型数组内所有数字进行+1 16 | 17 | ``` 18 | private class func testMap() { 19 | let numbersArray = [1, 3, 5, 7, 9] 20 | var newNumArray1 = [Int]() 21 | // for循环方式 22 | for item in numbersArray { 23 | newNumArray1.append(item+1) 24 | } 25 | // 高阶函数Map接受闭包方式 26 | let newNumArray2 = numbersArray.map { $0+1 } 27 | print("加1后数组1:\(newNumArray1)") 28 | print("加1后数组2:\(newNumArray2)") 29 | // 加1后数组1:[2, 4, 6, 8, 10] 30 | // 加1后数组2:[2, 4, 6, 8, 10] 31 | } 32 | ``` 33 | 34 | 附加尾随闭包几种不同写法 35 | 36 | ``` 37 | let result1 = numbersArray.map { (item: Int) -> Int in return item + 1 } 38 | let result2 = numbersArray.map { (item: Int) in return item + 1 } 39 | let result3 = numbersArray.map { item in return item + 1 } 40 | let result4 = numbersArray.map { item in item + 1 } 41 | let result5 = numbersArray.map { $0+1 } 42 | print("\(result1) \(result2) \(result3) \(result4) \(result5)") 43 | ``` 44 | 45 | > ## flatMap & compactMap 46 | 47 | flatMap 对集合类型的数据进行循环,并对每个元素采取相同操作,然后返回一个一维集合 48 | compactMap 对集合类型的数据进行循环,并对每个元素采取相同操作,然后返回一个同维集合 49 | 50 | flatMap已经禁止 51 | 52 | 例如,要对一个二维整型数组内所有数字进行+1 53 | 54 | ``` 55 | private class func testFlatMap() { 56 | let numbersArray = [[1, 2, 3], [4, 5, 6]] 57 | let newNumArray1 = numbersArray.flatMap { $0.map { $0+1 }} 58 | let newNumArray2 = numbersArray.compactMap{ $0.map { $0+1 }} 59 | let newNumArray3 = numbersArray.map { $0.map { $0+1 }} 60 | print("加1后数组1:\(newNumArray1)") 61 | print("加1后数组2:\(newNumArray2)") 62 | print("加1后数组3:\(newNumArray3)") 63 | // 加1后数组1:[2, 3, 4, 5, 6, 7] 64 | // 加1后数组2:[[2, 3, 4], [5, 6, 7]] 65 | // 加1后数组3:[[2, 3, 4], [5, 6, 7]] 66 | } 67 | ``` 68 | 69 | > ## Filter 70 | 71 | 干嘛的?对集合类型的数据进行循环,并对每个元素进行筛选,然后返回一个一维集合 72 | 73 | ``` 74 | private class func testFilter() { 75 | let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9] 76 | let newNumArray1 = numbersArray.filter { $0 % 2 == 0} 77 | let newNumArray2 = numbersArray.filter { $0 % 2 == 1} 78 | print("删除奇数后的数组1:\(newNumArray1)") 79 | print("删除偶数后的数组2:\(newNumArray2)") 80 | // 删除奇数后的数组1:[2, 4, 6, 8] 81 | // 删除偶数后的数组2:[1, 3, 5, 7, 9] 82 | } 83 | ``` 84 | 85 | > ## Reduce 86 | 87 | 干嘛的?接受一个初始化值,并且接受一个闭包作为规则,自动遍历集合的每一个元素,使用闭包的规则去处理这些元素,合并处理结果 88 | 89 | ``` 90 | private class func testReduce() { 91 | let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9] 92 | let sum = numbersArray.reduce(0) { $0 + $1} 93 | print("数组中数字之和:\(sum)") 94 | // 数组中数字之和:45 95 | } 96 | ``` 97 | 98 | > ## 综合起来,链式调用 99 | 100 | ``` 101 | private class func testAllFunc() { 102 | let numbersArray = [1, 2, 3, 4, 5, 6, 7, 8, 9] 103 | let result = numbersArray.filter { $0 % 2 == 0 }.map { $0+1}.reduce(0) { $0 + $1 } 104 | print("数组数据去除奇数得到的数组中所有元素加1后的求和结果:\(result)") 105 | // 数组数据去除奇数得到的数组中所有元素加1后的求和结果:24 106 | } 107 | ``` 108 | 109 | * flatMap 与 map 不同之处是 110 | 111 | - flatMap 去nil,返回后的数组中不存在 nil 同时它会把Optional解包; 112 | - flatMap 降纬,把数组中存有数组的数组一同打开变成一个新的数组; 113 | - flatMap 也能把两个不同的数组合并成一个数组 这个合并的数组元素个数是前面两个数组元素个数的乘积 114 | 115 | ``` 116 | let array0: [Int?] = [1, 2, 3, nil, 4, 5] 117 | 118 | let array1: [Int?] = array0.map { $0 } 119 | // let array2: [Int] = array0.map { $0 } // Cannot convert value of type 'Int?' to closure result type 'Int' 120 | let array3 = array0.map { $0 } 121 | let array4: [Int?] = array0.compactMap { $0 } 122 | let array5: [Int] = array0.compactMap { $0 } 123 | let array6 = array0.compactMap { $0 } 124 | 125 | print("1:\(array1)") 126 | print("2:\(array3)") 127 | print("4:\(array4)") 128 | print("5:\(array5)") 129 | print("6:\(array6)") 130 | // 1:[Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5)] 131 | // 2:[Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5)] 132 | // 4:[Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5)] 133 | // 5:[1, 2, 3, 4, 5] 134 | // 6:[1, 2, 3, 4, 5] 135 | ``` 136 | 137 | * 学习这些高阶函数有什么意义? 138 | 139 | - 阅读和理解复杂的函数式编程 140 | - 编写更优美、更易于维护的代码,具有更好的可读性 141 | - 提高我们的Swift语言能力 142 | 143 | 144 | [函数式思想--Swift中Map、Filter、Reduce函数实现原理及仿写](www.cocoachina.com/articles/20075) 145 | [Swift 四种高阶函数简介](https://www.jianshu.com/p/7f4472c1b039) 146 | [Swift之Map与CompactMap区别](https://www.jianshu.com/p/07b59f4f0071) 147 | [swift中高阶函数map、flatMap、filter、reduce](https://www.cnblogs.com/muzijie/p/6542650.html) 148 | [Swift 4.1迁移 flatMap to compactMap](https://www.jianshu.com/p/665d2c3e84b7) 149 | 150 | 151 | [上一页 Swift6-多线程](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift006.md) 152 | [下一页 Swift8-琐碎知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift008.md) -------------------------------------------------------------------------------- /Swift/Swift008.md: -------------------------------------------------------------------------------- 1 | # Swift8-琐碎知识点 2 | 3 | * ### 三方库一处引入,处处使用 4 | 5 | ``` 6 | // 可以建立一个文件放进去,如 Const.swift 7 | @_exported import IQKeyboardManagerSwift 8 | @_exported import Alamofire 9 | @_exported import Moya 10 | @_exported import Kingfisher 11 | @_exported import SnapKit 12 | @_exported import SwiftyJSON 13 | @_exported import ObjectMapper 14 | ``` 15 | 16 | * ### 几种定时器 17 | 18 | 1. Timer 19 | 20 | ``` 21 | // 创建方式1 自动加入runloop 22 | timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerRun), userInfo: nil, repeats: true) 23 | 24 | // 创建方式2 手动加入runloop 25 | timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerRun), userInfo: nil, repeats: true) 26 | RunLoop.current.add(timer!, forMode: .default) 27 | 28 | // 创建方式3 自动加入runloop iOS10后block回调 29 | timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer: Timer) in 30 | weakSelf?.timerRun() 31 | }) 32 | 33 | // 创建方式4 手动加入runloop iOS10后block回调 34 | timer = Timer.init(timeInterval: 1, repeats: true, block: { (timer: Timer) in 35 | weakSelf?.timerRun() 36 | }) 37 | RunLoop.current.add(timer!, forMode: .default) 38 | // 还有其他API,暂时用不到,不做太多说明 39 | let timerFireDate = Date.init(timeIntervalSinceNow: 79) 40 | let timer = Timer.init(fireAt: timerFireDate, interval: 1, target: self, selector: #selector (timerRun), userInfo: nil, repeats: true) 41 | RunLoop.current.add(timer, forMode: .defaultRunLoopMode) 42 | timer.fire() 43 | 44 | @objc func timerRun() { 45 | print("timerRun \(Date())") 46 | } 47 | ``` 48 | 49 | 方式1和方式2带来一个严重的问题--循环引用导致内存泄漏[参考苹果文档](https://developer.apple.com/documentation/foundation/timer) 50 | 由于内部获取传入的target对象的指针并强引用该对象,再加上runloop对timer的强引用,必然导致循环引用。 51 | 即使声明局部变量(runloop--target--timer三者关系并没有破坏),即使再加上用weak形式(weak对象和原对象地址相同),也改变不了循环引用问题的存在 52 | 方式3和方式4由于采用了block回调的方式,变相的将target编程timer自己,从而阻断了闭环,解决了循环引用问题,但只适用于iOS10及以后系统 53 | 要想兼容性的(主要iOS8-iOS9)解决就要另选方案 54 | 55 | 解决方案 56 | 57 | 写Timer的扩展,实现类似iOS10以后的block回调形式(内部判断,版本满足iOS10则直接调用系统block形式,不满足则自己转换成block形式) 58 | 59 | ``` 60 | extension Timer { 61 | 62 | public static func ddyScheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { 63 | if #available(iOS 10, *) { 64 | return scheduledTimer(withTimeInterval: interval, repeats: repeats, block: { block($0) }) 65 | } else { 66 | return scheduledTimer(timeInterval: interval, target: self, selector: #selector(ddyTimerInvoke(_:)), userInfo: block, repeats: repeats) 67 | } 68 | } 69 | 70 | public class func ddyInit(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { 71 | if #available(iOS 10, *) { 72 | return self.init(timeInterval: interval, repeats: repeats, block: { block($0) }) 73 | } else { 74 | return self.init(timeInterval: interval, target: self, selector: #selector(ddyTimerInvoke(_:)), userInfo: block, repeats: repeats) 75 | } 76 | } 77 | 78 | @objc static func ddyTimerInvoke(_ timer: Timer) { 79 | if let block = timer.userInfo as? (Timer) -> Void { 80 | block(timer) 81 | } 82 | } 83 | } 84 | 85 | ``` 86 | 87 | 也可以仿照OC中NSProxy方案,引入中间人,进行转发 88 | 89 | 异步子线程中使用Timer 90 | 91 | ``` 92 | DispatchQueue.global().async { 93 | // 如果timer为全局变量,需要用weak形式 94 | let timer = Timer.ddyInit(timeInterval: 1, repeats: true, block: { (timer: Timer) in 95 | weakSelf?.timerRun() 96 | }) 97 | RunLoop.current.add(timer, forMode: .default) 98 | // 子线程(异步情况)默认无执行的runloop 99 | RunLoop.current.run() 100 | } 101 | ``` 102 | 103 | 切后台定时器停止解决方案 104 | 105 | ``` 106 | // 后台任务标识 107 | var backgroundTask:UIBackgroundTaskIdentifier! = nil 108 | 109 | func applicationDidEnterBackground(_ application: UIApplication) { 110 | // 延迟程序静止的时间 111 | DispatchQueue.global().async() { 112 | //如果已存在后台任务,先将其设为完成 113 | if self.backgroundTask != nil { 114 | application.endBackgroundTask(self.backgroundTask) 115 | self.backgroundTask = UIBackgroundTaskInvalid 116 | } 117 | } 118 | 119 | //如果要后台运行 120 | self.backgroundTask = application.beginBackgroundTask(expirationHandler: { () -> Void in 121 | //如果没有调用endBackgroundTask,时间耗尽时应用程序将被终止 122 | application.endBackgroundTask(self.backgroundTask) 123 | self.backgroundTask = UIBackgroundTaskInvalid 124 | }) 125 | } 126 | ``` 127 | 128 | 129 | 2. dispatchSourceTimer(GCD定时器) 130 | 131 | ``` 132 | private class func testGCDTimer() { 133 | // 倒计时总次数 134 | var timeCount = 20 135 | // 自定义并发队列 136 | let concurrentQ = DispatchQueue(label: "com.ddy.timer", attributes: .concurrent) 137 | // 在自定义队列的定时器 138 | let timer = DispatchSource.makeTimerSource(flags: [], queue: concurrentQ) 139 | // 设置立即开始 0.5秒循环一次 140 | timer.schedule(deadline: .now(), repeating: 0.5) 141 | // 触发回调事件 142 | timer.setEventHandler { 143 | timeCount = timeCount - 1 144 | if timeCount <= 0 { 145 | timer.cancel() 146 | } 147 | DispatchQueue.main.async { 148 | print("主线程更新UI \(timeCount)") 149 | } 150 | } 151 | // cancel事件回调 152 | timer.setCancelHandler { 153 | DispatchQueue.main.async { 154 | print("已结束I \(timeCount)") 155 | } 156 | } 157 | // 启动定时器 158 | timer.resume() 159 | } 160 | ``` 161 | 162 | 3. CADisplayLink 163 | 164 | CADisplayLink 是一种触发频率和屏幕刷新频率相同的高精度定时器。 165 | 166 | ``` 167 | private func testCADisplayLink() { 168 | 169 | let displayLink = CADisplayLink.init(target: self, selector: #selector(handleDisplayLink(_:))) 170 | // 设置触发频率 171 | if #available(iOS 10, *) { 172 | // 每秒多少帧,设置0则默认60,即是一秒内有60帧执行刷新调用。 173 | displayLink.preferredFramesPerSecond = 30 174 | } else { 175 | // 每多少帧调用一次 176 | displayLink.frameInterval = 2 177 | } 178 | displayLink.add(to: RunLoop.main, forMode: .default) 179 | } 180 | 181 | @objc func handleDisplayLink(_ displayLink:CADisplayLink) -> Void { 182 | // 当前帧开始刷新的时间 183 | print(displayLink.timestamp) 184 | // 一帧刷新使用的时间 185 | print(displayLink.duration) 186 | // 下一帧开始刷新的时间 187 | print(displayLink.targetTimestamp) 188 | // duration = targetTimestamp - timestamp 189 | // 暂停帧的刷新 true:停 ; false:开始 190 | displayLink.isPaused = true 191 | // 将定时器移除主循环 192 | displayLink.remove(from: RunLoop.main, forMode: .default) 193 | // 停止定时器 194 | displayLink.invalidate() 195 | } 196 | ``` 197 | 198 | * ### 结构体(struct) 和 类(class) 的区别 199 | 200 | swift中 201 | 202 | - class是引用类型,struct是值类型。 203 | 值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。 204 | - class 可以继承,这样子类可以使用父类的特性和方法 205 | - class 可以被多次引用 206 | - struct 更轻量级,适用于复制操作,相比于一个class的实例被多次引用更加安全。 207 | 无须担心内存memory leak或者多线程冲突问题 208 | 209 | 210 | C语言中 211 | 212 | - struct只是作为一种复杂数据类型定义,不能用于面向对象编程。 213 | 214 | C++中 215 | 216 | - 对于成员访问权限以及继承方式,class默认private的,而struct是public的。 217 | - class还可以用于表示模板类型,struct则不行 218 | 219 | * ### 2B程序员 普通程序员 大神程序员 220 | 221 | 交换数组中索引下标m和n处两个数的位置(m,n均不会越界) 222 | 223 | ``` 224 | // 2B程序员 225 | func swap1(_ numberArray: inout Array, _ m: Int, _ n: Int) { 226 | let temp = numberArray[m] 227 | numberArray[m] = numberArray[n] 228 | numberArray[n] = temp 229 | } 230 | // 普通程序员 231 | func swap2(_ array: inout [T], _ m: Int, _ n: Int) { 232 | let temp = array[m] 233 | array[m] = array[n] 234 | array[n] = temp 235 | } 236 | // 大神程序员 237 | func swap3(_ nums: inout [T], _ m: Int, _ n: Int) { 238 | (nums[m], nums[n]) = (nums[n], nums[m]) 239 | } 240 | 241 | // 调用 242 | var numberArray = [1, 2, 3, 4, 5] 243 | swap1(&numberArray, 1, 2) 244 | // swap2(&numberArray, 1, 2) 245 | // swap3(&numberArray, 1, 2) 246 | ``` 247 | 248 | 输入一个整数,输出其加10后的数字 249 | 250 | ``` 251 | // 2B程序员 252 | func add1(_ number: Int) -> Int { 253 | return number + 10 254 | } 255 | // 普通程序员 256 | func add2(_ number: Int, add number2: Int) -> Int { 257 | return number + number2 258 | } 259 | // 大神程序员 260 | func add3(_ number: Int) ->(Int) -> Int { 261 | return { number2 in 262 | return number + number2 263 | } 264 | } 265 | 266 | // 调用 267 | let num1 = add1(2) 268 | let num2 = add2(2, add: 10) 269 | let num3 = add3(2)(10) 270 | print("\(num1) \(num2) \(num3)") 271 | ``` 272 | 273 | 敲黑板,划重点 274 | 275 | 1. inout关键字 276 | swift有两种参数传递方式 277 | 值传递: 传递的参数是副本,调用参数过程不影响原始数据 278 | 指针传递: 把参数内存地址传递过去,调用过程会影响原始数据 279 | 280 | class默认指针传递,Int,Float,Bool,Character,Array,Set,Dictionary,enum,struct默认值传递; 281 | 想将值传递参数变成引用方式指针传递,用inout关键字实现。 282 | 283 | 注 284 | 函数声明中参数用inout修饰以达到函数内部改变外部传入的参数,在调用时变量前加 & 符号; 285 | 参数被inout修饰,就不用var和let修饰了。 286 | 2. 范型 287 | 当函数只是传入参数类型不同,其他都相同,此时不用具体类型,而用范型 288 | 3. 元组 289 | 元组能简洁的交换两个变量值 290 | 4. Currying(柯里化) 291 | 通过局部套用(部分求值)实现多参变单参的函数式思想 292 | 其特点为:只用表达式(单纯运算过程,总是有返回值),不用语句(执行某种操作,没有返回值),不修改值,只返回新值。 293 | 294 | 优点: 295 | - 代码简洁 296 | - 提高代码复用性 297 | - 代码管理方便,相互之间不依赖,每个函数都 是一个独立的模块,很容易进行单元测试。 298 | - 易于“并发编程”,因为不修改变量的值,都是返回新值。 299 | - 最大的好处就是能把函数当参数用!! 300 | [参考](https://www.jianshu.com/p/fc8c13ce7157) 301 | 302 | * ### 一些关键字 303 | 304 | let: 常量声明关键字 305 | 306 | ``` 307 | class ClassA { 308 | let number: Int 309 | init(num: Int) { 310 | // 在 init 里对 let 声明的常量进行一次赋值(number此时还没有实例化) 311 | number = num 312 | } 313 | } 314 | ``` 315 | 316 | var: 变量声明关键字 317 | 318 | ``` 319 | var name: String? 320 | name = "LiLei" 321 | ``` 322 | 323 | class: 类声明关键字 324 | 325 | ``` 326 | class ClassB { 327 | 328 | } 329 | ``` 330 | struct:结构体声明关键字 331 | 332 | ``` 333 | struct StructC { 334 | 335 | } 336 | ``` 337 | 338 | enum: 枚举声明关键字 339 | 340 | ``` 341 | enum Alignment { 342 | case left 343 | case center 344 | case right 345 | } 346 | ``` 347 | 348 | override:重写关键字(方法和属性) 349 | 350 | ``` 351 | class student: Person { 352 | override var name: String { 353 | return "LiLei" 354 | } 355 | override func age() -> Int { 356 | return 18 357 | } 358 | } 359 | ``` 360 | 361 | final: 防止重写(方法/属性)或继承(类)关键字 362 | 363 | ``` 364 | final class student { 365 | print("如果继承,报错Inheritance from a final class 'student'") 366 | } 367 | class Name { 368 | final var name :String { 369 | return "LiLei" 370 | } 371 | final func age() -> Int { 372 | return 18 373 | } 374 | } 375 | ``` 376 | 377 | super: 子类调用父类中方法或属性关键字 378 | 379 | ``` 380 | override func viewDidLoad() { 381 | super.viewDidLoad() 382 | print("super是编译器符号,代表该子类而不是父类") 383 | } 384 | ``` 385 | 386 | lazy:懒加载关键字 387 | 388 | ``` 389 | lazy var scanLineView: UIImageView = { 390 | let scanLine = UIImageView() 391 | return scanLine 392 | }() 393 | 394 | var name = "ss" 395 | lazy var address = name 396 | ``` 397 | 398 | init:初始化关键字(构造器) 399 | 400 | ``` 401 | var name:String? 402 | init?(value:Any) { // ? 表示可以初始化失败 403 | name = value as? String 404 | } 405 | ``` 406 | 407 | deinit:反初始化关键字(析构) 408 | 409 | ``` 410 | deinit { 411 | print("反初始化") 412 | } 413 | ``` 414 | 415 | is: 对比关键字 416 | 417 | ``` 418 | if value is String { 419 | print("我是字符串") 420 | } 421 | ``` 422 | 423 | convenience: 便利构造器关键字 424 | 425 | - 指定构造方法(Designated): 没有convenience单词,必须对所有属性进行初始化 426 | - 便利构造方法(Convenience): 有convenience单词,不用对所有属性进行初始化,因为便利构造方法依赖于指定构造方法。 427 | 428 | 便利构造函数的特点: 429 | 1、便利构造函数通常都是写在extension里面 430 | 2、便利函数init前面需要加载convenience 431 | 3、在便利构造函数中需要明确的调用self.init() 432 | 433 | ``` 434 | extension UILabel { 435 | /// 便利构造器 436 | convenience init(text: String, font: UIFont = UIFont.systemFont(ofSize: 12), textAlignment: NSTextAlignment = .center, numberOfLines: Int = 0) { 437 | self.init() 438 | self.text = text 439 | self.font = font 440 | self.textAlignment = textAlignment 441 | self.numberOfLines = numberOfLines 442 | } 443 | } 444 | ``` 445 | 446 | required: 必须实现关键字 447 | 448 | ``` 449 | class requiredClass{ 450 | required init(ss:String) { 451 | print("常用于修饰init,代表必须实现该init") 452 | } 453 | } 454 | ``` 455 | 456 | extension:扩展关键字 457 | 458 | ``` 459 | extension String { 460 | 461 | } 462 | ``` 463 | 464 | typealias:起别名关键字(命名空间) 465 | 466 | ``` 467 | typealias MyInt = Int 468 | 469 | typealias DDYClosure = (_ str: String?,_ errorCode: Int) -> Void 470 | ``` 471 | 472 | fallthrough:继续执行关键字(swift 中switch自带break) 473 | 474 | ``` 475 | switch age { 476 | case 17: 477 | print("17") 478 | fallthrough 479 | case 18: 480 | print("18") 481 | fallthrough 482 | default: 483 | print("1000") 484 | } 485 | ``` 486 | 487 | 与声明有关的关键字: 488 | 489 | ``` 490 | import,class,enum,struct,extension,protocol, 491 | init,deinit,func,var,let, typealias, 492 | fileprivate,private,internal,public,open, 493 | static,operator,subscript, 494 | ``` 495 | 496 | 与语句有关的关键字: 497 | 498 | ``` 499 | if,else,for,while,do,switch, 500 | where,in,case, 501 | break,return,fallthrough, 502 | ``` 503 | 504 | 表达式和类型关键字: 505 | 506 | ``` 507 | as,dynamicType,true,false,is,nil, 508 | self,Self,super, 509 | _COLUMN_, _FILE_, _FUNCTION_, _LINE_ 510 | ``` 511 | 512 | 在特定上下文中使用的关键字 513 | 514 | ``` 515 | associativity,convenience,dynamic didSet,final,get, 516 | infix,inout,lazy,left,mutating,none,nonmutating, 517 | optional,override,postfix,precedence,prefix,weak 518 | Protocol,required,right,set,Type,unowned,willSet 519 | ``` 520 | 521 | dynamic关键字 522 | 523 | 如果有过OC的开发经验,那一定会对OC中@dynamic关键字比较熟悉,它告诉编译器不要为属性合成getter和setter方法。 524 | Swift中也有dynamic关键字,它可以用于修饰变量或函数,它的意思也与OC完全不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。 525 | 因此,标记为dynamic的变量/函数会隐式的加上@objc关键字,它会使用OC的runtime机制。 526 | 虽然静态分发在效率上可能更好,不过一些app分析统计的库需要依赖动态分发的特性,动态的添加一些统计代码,这一点在Swift的静态分发机制下很难完成。这种情况下,虽然使用dynamic关键字会牺牲因为使用静态分发而获得的一些性能优化,但也依然是值得的。 527 | 528 | * ### 单例 529 | 530 | ``` 531 | class SharedInstanceTest: NSObject { 532 | // swift显式去掉了dispatch_one函数,但是内部lazy却隐式用了 533 | // swift仿照OC单例写法 534 | static let instance = SharedInstanceTest() 535 | class func sharedInstance() -> SharedInstanceTest { 536 | return instance 537 | } 538 | 539 | // 内部结构体写法 540 | static var defaultInstance: SharedInstanceTest { 541 | struct Static { 542 | static let sharedInstance = SharedInstanceTest() 543 | } 544 | return Static.sharedInstance; 545 | } 546 | 547 | // 简便写法(推荐) 548 | static let `default` = SharedInstanceTest() 549 | 550 | // 防止外部调用init初始化 551 | private override init() { } 552 | // https://www.jianshu.com/p/10d6cc302366 553 | } 554 | ``` 555 | 556 | 557 | 558 | 559 | * ### debugPrint 560 | 561 | 如果想只在debug模式打印,而不去配置build configuration可以这么用 562 | 563 | 564 | ``` 565 | func DDYPrint(_ message: M) { 566 | if _isDebugAssertConfiguration() { 567 | print("\(message)") 568 | } 569 | } 570 | ``` 571 | 572 | * ### 去除UIView(特别UILabel)上莫名其妙多出的横线 573 | 574 | ``` 575 | // OC 576 | // CGRectIntegral(this_frame) 577 | // Swift 578 | this_frame.integral 579 | ``` 580 | 581 | * ### 去除滚动可能出现的诡异动画 582 | 583 | ``` 584 | CATransaction.begin() 585 | CATransaction.setDisableActions(true) 586 | // 滚动到最后一行 587 | scrollToRow(at: IndexPath(item: letterDetailVMArr.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: false) 588 | CATransaction.commit() 589 | ``` 590 | [参考](https://blog.csdn.net/Felicity294250051/article/details/84069002) 591 | 592 | 593 | 594 | [上一页 Swift7-高阶函数 map flatMap compactMap filter reduce](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift007.md) 595 | [下一页 Swift9-加密](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift009.md) -------------------------------------------------------------------------------- /Swift/Swift009.md: -------------------------------------------------------------------------------- 1 | # Swift9-加密 2 | 3 | * ### MD5 4 | 5 | MD5(Message-Digest Algorithm 5)以512位分组来处理输入文本,每组又划分为16个32位子分组。算法的输出由四个32位分组组成,将它们级联形成一个128位散列值。 6 | 7 | ``` 8 | /// MD5 编码 9 | /// 10 | /// - Parameters: 11 | /// - string: 需要编码的字符串 12 | /// - lower: true:字母小写 false:字母大写 13 | /// - Returns: 编码结果 14 | public static func ddyMD5String(_ string: String?, lower: Bool) -> String? { 15 | guard let cStr = string?.cString(using: .utf8) else { 16 | return nil 17 | } 18 | let buffer = UnsafeMutablePointer.allocate(capacity: 16) 19 | CC_MD5(cStr,(CC_LONG)(strlen(cStr)), buffer) 20 | let md5String = NSMutableString(); 21 | for i in 0 ..< 16 { 22 | if lower { 23 | md5String.appendFormat("%02x", buffer[i]) 24 | } else { 25 | md5String.appendFormat("%02X", buffer[i]) 26 | } 27 | } 28 | free(buffer) 29 | return md5String as String 30 | } 31 | ``` 32 | 33 | * ### Base64 34 | 35 | Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。作用是Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。 36 | 37 | ``` 38 | /// Base64 编解码 39 | /// 40 | /// - Parameters: 41 | /// - string: 需要编解码的字符串 42 | /// - encode: true:编码 false:解码 43 | /// - Returns: 编码结果 44 | public static func ddyBase64String(_ string: String?, encode: Bool) -> String? { 45 | if encode { // 编码 46 | guard let codingData = string?.data(using: .utf8) else { 47 | return nil 48 | } 49 | return codingData.base64EncodedString() 50 | } else { // 解码 51 | guard let newStr = string else { 52 | return nil 53 | } 54 | guard let decryptionData = Data(base64Encoded: newStr, options: .ignoreUnknownCharacters) else { 55 | return nil 56 | } 57 | return String.init(data: decryptionData, encoding: .utf8) 58 | } 59 | } 60 | ``` 61 | 62 | * ### Sha1 63 | 64 | SHA1和MD5本质都是摘要函数,长度不同(MD5是128位,SHA1是160位,SHA256是256位) 65 | 66 | ``` 67 | public static func ddySha1String(_ string: String?) -> String? { 68 | guard let codingData = string?.data(using: .utf8) else { 69 | return nil 70 | } 71 | var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH)) 72 | let newData = NSData.init(data: codingData) 73 | CC_SHA1(newData.bytes, CC_LONG(codingData.count), &digest) 74 | let output = NSMutableString(capacity: Int(CC_SHA1_DIGEST_LENGTH)) 75 | for byte in digest { 76 | output.appendFormat("%02x", byte) 77 | } 78 | return output as String 79 | } 80 | ``` 81 | 82 | * ### 综合封装 83 | 84 | MD5、SHA1、SHA224、SHA256、SHA384、SHA512 85 | 86 | 87 | ``` 88 | // Secure Hash Algorithm 89 | enum DDYSHAType { 90 | case MD5, SHA1, SHA224, SHA256, SHA384, SHA512 91 | var infoTuple: (algorithm: CCHmacAlgorithm, length: Int) { 92 | switch self { 93 | case .MD5: return (algorithm: CCHmacAlgorithm(kCCHmacAlgMD5), length: Int(CC_MD5_DIGEST_LENGTH)) 94 | case .SHA1: return (algorithm: CCHmacAlgorithm(kCCHmacAlgSHA1), length: Int(CC_SHA1_DIGEST_LENGTH)) 95 | case .SHA224: return (algorithm: CCHmacAlgorithm(kCCHmacAlgSHA224), length: Int(CC_SHA224_DIGEST_LENGTH)) 96 | case .SHA256: return (algorithm: CCHmacAlgorithm(kCCHmacAlgSHA256), length: Int(CC_SHA256_DIGEST_LENGTH)) 97 | case .SHA384: return (algorithm: CCHmacAlgorithm(kCCHmacAlgSHA384), length: Int(CC_SHA384_DIGEST_LENGTH)) 98 | case .SHA512: return (algorithm: CCHmacAlgorithm(kCCHmacAlgSHA512), length: Int(CC_SHA512_DIGEST_LENGTH)) 99 | } 100 | } 101 | } 102 | 103 | // MD5 SHA1 SHA256 SHA512 这4种本质都是摘要函数,不通在于长度 MD5 是 128 位,SHA1 是 160 位 ,SHA256 是 256 位 104 | public static func shaCrypt(string: String?, cryptType: DDYSHAType, key: String?, lower: Bool) -> String? { 105 | guard let cStr = string?.cString(using: String.Encoding.utf8) else { 106 | return nil 107 | } 108 | // let strLen = Int(string!.lengthOfBytes(using: String.Encoding.utf8)) 109 | let strLen = strlen(cStr) 110 | let digLen = cryptType.infoTuple.length 111 | let buffer = UnsafeMutablePointer.allocate(capacity: digLen) 112 | let hash = NSMutableString() 113 | 114 | if let cKey = key?.cString(using: String.Encoding.utf8), key != "" { 115 | let keyLen = Int(key!.lengthOfBytes(using: String.Encoding.utf8)) 116 | CCHmac(cryptType.infoTuple.algorithm, cKey, keyLen, cStr, strLen, buffer) 117 | } else { 118 | switch cryptType { 119 | case .MD5: CC_MD5(cStr, (CC_LONG)(strlen(cStr)), buffer) 120 | case .SHA1: CC_SHA1(cStr, (CC_LONG)(strlen(cStr)), buffer) 121 | case .SHA224: CC_SHA224(cStr, (CC_LONG)(strlen(cStr)), buffer) 122 | case .SHA256: CC_SHA256(cStr, (CC_LONG)(strlen(cStr)), buffer) 123 | case .SHA384: CC_SHA384(cStr, (CC_LONG)(strlen(cStr)), buffer) 124 | case .SHA512: CC_SHA512(cStr, (CC_LONG)(strlen(cStr)), buffer) 125 | } 126 | } 127 | for i in 0.. String? { 162 | 163 | if string == nil { 164 | return nil 165 | } 166 | let strData = encode ? string!.data(using: .utf8) : Data(base64Encoded: string!) 167 | // 创建数据编码后的指针 168 | let dataPointer = UnsafeRawPointer((strData! as NSData).bytes) 169 | // 获取转码后数据的长度 170 | let dataLength = size_t(strData!.count) 171 | // 将加密或解密的密钥转化为Data数据 172 | guard let keyData = key?.data(using: .utf8) else { 173 | return nil 174 | } 175 | // 创建密钥的指针 176 | let keyPointer = UnsafeRawPointer((keyData as NSData).bytes) 177 | // 设置密钥的长度 178 | let keyLength = cryptType.infoTuple.keyLength 179 | 180 | // 创建加密或解密后的数据对象 181 | let cryptData = NSMutableData(length: Int(dataLength) + cryptType.infoTuple.digLength) 182 | // 获取返回数据(cryptData)的指针 183 | let cryptPointer = UnsafeMutableRawPointer(mutating: cryptData!.mutableBytes) 184 | // 获取接收数据的长度 185 | let cryptDataLength = size_t(cryptData!.length) 186 | // 加密或则解密后的数据长度 187 | var cryptBytesLength:size_t = 0 188 | // 是解密或者加密操作(CCOperation 是32位的) 189 | let operation = encode ? CCOperation(kCCEncrypt) : CCOperation(kCCDecrypt) 190 | // 算法类型 191 | let algoritm: CCAlgorithm = CCAlgorithm(cryptType.infoTuple.algorithm) 192 | // 设置密码的填充规则( PKCS7 & ECB 两种填充规则) 193 | let options:CCOptions = UInt32(kCCOptionPKCS7Padding) | UInt32(kCCOptionECBMode) 194 | // 执行算法处理 195 | let cryptStatus = CCCrypt(operation, algoritm, options, keyPointer, keyLength, nil, dataPointer, dataLength, cryptPointer, cryptDataLength, &cryptBytesLength) 196 | // 结果字符串初始化 197 | var resultString: String? 198 | // 通过返回状态判断加密或者解密是否成功 199 | if CCStatus(cryptStatus) == CCStatus(kCCSuccess) { 200 | cryptData!.length = cryptBytesLength 201 | if encode { 202 | resultString = cryptData!.base64EncodedString(options: .lineLength64Characters) 203 | } else { 204 | resultString = NSString(data:cryptData! as Data ,encoding:String.Encoding.utf8.rawValue) as String? 205 | } 206 | } 207 | return resultString 208 | } 209 | ``` 210 | 211 | 212 | CCCrypt 函数的介绍 213 | 214 | 1、参数1: 是指定加密还是解密的枚举类型(kCCEncrypt 、kCCDecrypt) 215 | 2、参数2: 是指加密算法的类型。在CommonCryptor.h中提供了kCCAlgorithmAES128、kCCAlgorithmAES、kCCAlgorithmDES、kCCAlgorithm3DES、kCCAlgorithmCAST、kCCAlgorithmRC4、kCCAlgorithmRC2、kCCAlgorithmBlowfish等多种类型的加密算法 216 | 3、 参数3:用来设置密码的填充规则(表示在使用密钥和算法对文本进行加密时的方法)的选项,该选项可以是kCCOptionPKCS7Padding或kCCOptionECBMode两者中的任一个 217 | 4、参数4:密钥的数据指针 218 | 5、参数5: 是密钥的长度 ,必须是 24 位 219 | 6、参数6: 加密或者解密的偏移对象 220 | 7、参数7: 要解密或者解密的数据指针对象 221 | 8、参数8: 要解密或者解密的数据字符长度 222 | 9、参数9: 加密或者解密的数据指针 223 | 10、参数10: 接受加密或者解密的数据长度 224 | 11、参数11: 这是加密或者解密的数据长度 225 | 226 | 227 | 注意 228 | 在加密或者解密的结果输出的时候,要重新设置接受解密或者加密的数据对象的长度为真实长度。 229 | 230 | CCCrypt 的返回结果 231 | 1、kCCSuccess 加解密操作正常结束 232 | 2、kCCParamError 非法的参数值 233 | 3、kCCBufferTooSmall 选项设置的缓存不够大 234 | 4、kCCMemoryFailure 内存分配失败 235 | 5、kCCAlignmentError 输入大小匹配不正确 236 | 6、kCCDecodeError 输入数据没有正确解码或解密 237 | 7、kCCUnimplemented 函数没有正确执行当前的算法 238 | 239 | 240 | 241 | [验证](http://tool.chacuo.net/cryptdes) 242 | [参考](https://www.jianshu.com/p/cab8f6ffb082) 243 | [参考](https://www.jianshu.com/p/940aaca1e3f6) 244 | [RSA加密](https://www.jianshu.com/p/d2cb314d30ec) 245 | 246 | 247 | [上一页 Swift8-琐碎知识点](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift008.md) 248 | [下一页 Swift10-UIview UILabel UIButton](https://github.com/DDYSwift/LearnSwift/blob/master/Swift/Swift010.md) -------------------------------------------------------------------------------- /Swift/Swift010.md: -------------------------------------------------------------------------------- 1 | # Swift10-UIView UILabel UIImageView UIButton 2 | 3 | 4 | * ### UIView 5 | 6 | UIView常用属性 7 | 8 | - frame:相对父视图的坐标和大小(x,y,w,h) 9 | - bounds:相对自身的坐标和大小,所以bounds的x和y永远为0(0,0,w,h) 10 | - center:相对父视图的中点坐标 11 | - transform:控制视图的放大缩小和旋转 12 | - superview:获取父视图 13 | - subviews:获取所有子视图 14 | - alpha:视图的透明度(0.0-1.0) 15 | - tag:视图标志(Int,默认0,最好不要设置1),设置后可通过viewWithTag取到该视图 16 | 17 | 常用方法 18 | 19 | 20 | - func removeFromSuperview():将视图从父视图中移除 21 | - func insertSubview(view:UIView, atIndex index:Int):指定一个位置插入一个视图,index越小,视图越往下 22 | - func exchangeSubviewAtIndex(index1:Int, withSubviewAtIndex index2:Int):将index1和index2位置的两个视图互换位置 23 | - func addSubview(view:UIView):添加视图到父视图 24 | - func insertSubview(view:UIView,belowSubview siblingSubview:UIView):在指定视图的下面插入视图 25 | - func insertSubview(view:UIVIew,aboveSubview siblingSubview:UIView):在指定视图上面插入视图 26 | - func bringSubviewToFront(view:UIView):把视图移到最顶层 27 | - func sendSubviewToBack(view:UIView):把视图移到最底层 28 | - func viewWithTag(tag:Int)->UIView?:根据tag值获取视图 29 | 30 | 31 | 可以用Extension UIView实现 32 | 33 | ``` 34 | // MARK:- 手势 35 | /// 点击手势(默认代理和target相同) 36 | public func tapGesture(_ target: Any?,_ action: Selector,_ numberOfTapsRequired: Int = 1) { 37 | let tapGesture = UITapGestureRecognizer(target: target, action: action) 38 | tapGesture.numberOfTapsRequired = numberOfTapsRequired 39 | tapGesture.delegate = target as? UIGestureRecognizerDelegate 40 | ddyValue.isUserInteractionEnabled = true 41 | ddyValue.addGestureRecognizer(tapGesture) 42 | } 43 | 44 | /// 长按手势(默认代理和target相同) 45 | public func longGesture(_ target: Any?,_ action: Selector,_ minDuration: TimeInterval = 0.5) { 46 | let longGesture = UILongPressGestureRecognizer(target: target, action: action) 47 | longGesture.minimumPressDuration = minDuration 48 | longGesture.delegate = target as? UIGestureRecognizerDelegate 49 | ddyValue.isUserInteractionEnabled = true 50 | ddyValue.addGestureRecognizer(longGesture) 51 | } 52 | 53 | /// 圆角与边线 54 | public func borderRadius(_ radius: CGFloat,_ masksToBounds: Bool,_ borderWidth: CGFloat = 0,_ borderColor: UIColor = UIColor.clear) { 55 | ddyValue.layer.borderWidth = borderWidth 56 | ddyValue.layer.borderColor = borderColor.cgColor 57 | ddyValue.layer.cornerRadius = radius 58 | ddyValue.layer.masksToBounds = masksToBounds 59 | } 60 | /// 部分圆角 61 | public func partRadius(_ corners: UIRectCorner,_ radius: CGFloat) { 62 | let shapeLayer = CAShapeLayer() 63 | shapeLayer.frame = ddyValue.bounds 64 | shapeLayer.path = UIBezierPath(roundedRect: ddyValue.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)).cgPath 65 | ddyValue.layer.mask = shapeLayer 66 | } 67 | 68 | /// 移除所有子视图 69 | public func removeAllChildView() { 70 | if ddyValue.subviews.isEmpty == false { 71 | _ = ddyValue.subviews.map { $0.removeFromSuperview() } 72 | } 73 | } 74 | ``` 75 | 76 | * ### UILabel 77 | 78 | 常用属性和方法 79 | 80 | ``` 81 | let label = UILabel(frame:CGRect(x:10, y:20, width:300, height:100)) 82 | label.textColor = UIColor.white 83 | label.text = "DDY" 84 | label.backgroundColor = UIColor.black 85 | label.preferredMaxLayoutWidth = 100 86 | label.lineBreakMode = .byTruncatingTail // 隐藏尾部并显示省略号 87 | // .byTruncatingMiddle // 隐藏中间部分并显示省略号 88 | // .byTruncatingHead // 隐藏头部并显示省略号 89 | // .byClipping //截去多余部分也不显示省略号 90 | label.adjustsFontSizeToFitWidth = true // 当文字超出标签宽度时,自动调整文字大小,使其不被截断 91 | // 设置文本高亮 92 | label.isHighlighted = true 93 | // 设置文本高亮颜色 94 | label.highlightedTextColor = UIColor.green 95 | // 通过富文本来设置行间距 96 | let paraph = NSMutableParagraphStyle() 97 | //将行间距设置为28 98 | paraph.lineSpacing = 20 99 | //样式属性集合 100 | let attributes = [NSFontAttributeName:UIFont.systemFont(ofSize: 15), 101 | NSParagraphStyleAttributeName: paraph] 102 | label.attributedText = NSAttributedString(string: str, attributes: attributes) 103 | view.addSubview(label) 104 | ``` 105 | 106 | 107 | 便利构造器扩展 108 | 109 | ``` 110 | extension UILabel { 111 | /// 便利构造器 112 | convenience init(text: String, font: UIFont = UIFont.systemFont(ofSize: 12), textAlignment: NSTextAlignment = .center, numberOfLines: Int = 0) { 113 | self.init() 114 | self.text = text 115 | self.font = font 116 | self.textAlignment = textAlignment 117 | self.numberOfLines = numberOfLines 118 | } 119 | } 120 | ``` 121 | 122 | 加个内边距调节 123 | 124 | ``` 125 | private var contentEdgeInsetsKey: Void? 126 | 127 | extension DDYWrapperProtocol where DDYT : UILabel { 128 | 129 | var contentEdgeInsets: UIEdgeInsets { 130 | get { 131 | guard let contentEdgeInsets = objc_getAssociatedObject(ddyValue, &contentEdgeInsetsKey) as? UIEdgeInsets else { 132 | return UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) 133 | } 134 | return contentEdgeInsets 135 | } 136 | set { 137 | objc_setAssociatedObject(ddyValue, &contentEdgeInsetsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC) 138 | } 139 | } 140 | } 141 | 142 | extension UILabel { 143 | public static func ddySwizzleMethod() { 144 | ddySwizzle(#selector(UILabel.textRect(forBounds:limitedToNumberOfLines:)), #selector(ddyTextRect(_:_:)), swizzleClass: self) 145 | ddySwizzle(#selector(UILabel.drawText(in:)), #selector(ddyDrawText(in:)), swizzleClass: self) 146 | } 147 | 148 | @objc private func ddyTextRect(_ bounds: CGRect,_ numberOfLines: Int) -> CGRect { 149 | var rect = self.ddyTextRect(bounds.inset(by: self.ddy.contentEdgeInsets), numberOfLines) 150 | rect.origin.x -= self.ddy.contentEdgeInsets.left; 151 | rect.origin.y -= self.ddy.contentEdgeInsets.top; 152 | rect.size.width += self.ddy.contentEdgeInsets.left + self.ddy.contentEdgeInsets.right; 153 | rect.size.height += self.ddy.contentEdgeInsets.top + self.ddy.contentEdgeInsets.bottom; 154 | return rect 155 | } 156 | 157 | @objc private func ddyDrawText(in rect: CGRect) { 158 | self.ddyDrawText(in: rect.inset(by: self.ddy.contentEdgeInsets)) 159 | } 160 | } 161 | ``` 162 | 163 | * ### UIImageView 164 | 165 | 常用属性和方法 166 | 167 | ``` 168 | let imageView = UIImageView() 169 | imageView.animationImages = imagesArray 170 | imageView.animationRepeatCount = 0 171 | imageView.animationDuration = 5 * 0.5 172 | imageView.startAnimating() 173 | imageView.contentMode = .scaleAspectFit 174 | view.addSubview(imageView) 175 | ``` 176 | 177 | 如果使用kingfisher加载网络图片 178 | 179 | ``` 180 | imageView.kf.setImage(with: url, placeholder: image) 181 | 182 | // 如果需要圆角 183 | let processor = RoundCornerImageProcessor(cornerRadius: 20) 184 | imageView.kf.setImage(with: url, placeholder: nil, options: [.processor(processor)]) 185 | ``` 186 | 187 | * ### UIButton 188 | 189 | - UIButtonType 190 | .system:前面不带图标,默认文字颜色为蓝色,有触摸时的高亮效果 191 | .custom:定制按钮,前面不带图标,默认文字颜色为白色,无触摸时的高亮效果 192 | .contactAdd:前面带“+”图标按钮,默认文字颜色为蓝色,有触摸时的高亮效果 193 | .detailDisclosure:前面带“!”图标按钮,默认文字颜色为蓝色,有触摸时的高亮效果 194 | .infoDark:为感叹号“!”圆形按钮(iOS7后同detailDisclosure) 195 | .infoLight:为感叹号“!”圆形按钮(iOS7后同detailDisclosure) 196 | 197 | - UIControl.State 198 | highlighted: 高亮 199 | disabled: 禁用 200 | selected: 选中 201 | 202 | - UIControl.Event 203 | touchDown:单点触摸按下事件,点触屏幕 204 | touchDownRepeat:多点触摸按下事件,点触计数大于1,按下第2、3或第4根手指的时候 205 | touchDragInside:触摸在控件内拖动时 206 | touchDragOutside:触摸在控件外拖动时 207 | touchDragEnter:触摸从控件之外拖动到内部时 208 | touchDragExit:触摸从控件内部拖动到外部时 209 | touchUpInside:在控件之内触摸并抬起事件 210 | touchUpOutside:在控件之外触摸抬起事件 211 | touchCancel:触摸取消事件,即一次触摸因为放上太多手指而被取消,或者电话打断 212 | 213 | 常用属性和方法 214 | 215 | ``` 216 | let button = UIButton(type: .custom) // 自定义按钮 217 | button.setTitle("普通状态", for:.normal) // 文字 218 | button.setTitleColor(UIColor.black, for: .normal) // 文本颜色 219 | button.setTitleShadowColor(UIColor.green, for:.normal) // 文本阴影颜色 220 | button.titleLabel?.shadowOffset = CGSize(width: -1.5, height: -1.5) // 文本阴影偏移 221 | button.titleLabel?.font = UIFont(name: "Zapfino", size: 13) // 字体字号 222 | button.adjustsImageWhenHighlighted=false // 使触摸模式下按钮也不会变暗(半透明) 223 | button.adjustsImageWhenDisabled=false // 使禁用模式下按钮也不会变暗(半透明) 224 | button.setImage(UIImage(named:"icon")?.withRenderingMode(.alwaysOriginal), for:.normal) //无渲染图片 225 | button.setBackgroundImage(UIImage(named:"bg1"), for:.normal) // 背景图片 226 | //传递触摸对象(即点击的按钮),需要在定义action参数时,方法名称后面带上冒号 227 | button.addTarget(self, action:#selector(tapped(_:)), for:.touchUpInside) 228 | 229 | // 事件 230 | @objc func tapped(_ button:UIButton){ 231 | print(button.title(for: .normal)) 232 | } 233 | ``` 234 | 235 | UIButton扩展 236 | 237 | ``` 238 | public enum DDYButtonStyle: Int { 239 | case defaultStyle = 0 // 默认效果(为了处理无调用状态,不可赋值) 240 | case imageLeft = 1 // 左图右文 241 | case imageRight = 2 // 右图左文 242 | case imageTop = 3 // 上图下文 243 | case imageBottom = 4 // 下图上文 244 | } 245 | 246 | private var styleKey: Void? 247 | private var paddingKey: Void? 248 | 249 | extension DDYWrapperProtocol where DDYT : UIButton { 250 | /// 设置图文样式(不可逆,一旦设置不能百分百恢复系统原来样式) 251 | var style: DDYButtonStyle { 252 | get { 253 | guard let style = objc_getAssociatedObject(ddyValue, &styleKey) as? DDYButtonStyle else { 254 | return .defaultStyle 255 | } 256 | return style 257 | } 258 | set { 259 | objc_setAssociatedObject(ddyValue, &styleKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 260 | ddyValue.layoutIfNeeded() 261 | } 262 | } 263 | 264 | var padding: CGFloat { 265 | get { 266 | guard let padding = objc_getAssociatedObject(ddyValue, &paddingKey) as? CGFloat else { 267 | return 0.5 268 | } 269 | return padding 270 | } 271 | set { 272 | objc_setAssociatedObject(ddyValue, &paddingKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 273 | ddyValue.layoutIfNeeded() 274 | } 275 | } 276 | 277 | public func setBackgroundColor(_ color: UIColor?, for state: UIControl.State) { 278 | guard let color = color else { 279 | return 280 | } 281 | func colorImage() -> UIImage? { 282 | let rect = CGRect(x: 0.0, y: 0.0, width: 1, height: 1) 283 | UIGraphicsBeginImageContext(rect.size) 284 | let context = UIGraphicsGetCurrentContext() 285 | context?.setFillColor(color.cgColor) 286 | context?.fill(rect) 287 | let image = UIGraphicsGetImageFromCurrentImageContext() 288 | UIGraphicsEndImageContext() 289 | return image ?? nil 290 | } 291 | ddyValue.setImage(colorImage(), for: state) 292 | } 293 | } 294 | 295 | extension UIButton { 296 | public static func ddySwizzleMethod() { 297 | ddySwizzle(#selector(layoutSubviews), #selector(ddyLayoutSubviews), swizzleClass: self) 298 | } 299 | 300 | @objc private func ddyLayoutSubviews() { 301 | self.ddyLayoutSubviews() 302 | adjustRect(margin: (contentEdgeInsets.top, contentEdgeInsets.left, contentEdgeInsets.bottom, contentEdgeInsets.right)) 303 | 304 | } 305 | 306 | private func adjustRect(margin:(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat)) { 307 | guard let imageSize = self.imageView?.frame.size, let titleSize = self.titleLabel?.frame.size else { 308 | return 309 | } 310 | guard imageSize != CGSize.zero && titleSize != CGSize.zero else { 311 | return 312 | } 313 | func horizontal(_ leftView: UIView,_ rightView: UIView) { 314 | let contentW = leftView.frame.width + self.ddy.padding + rightView.frame.width 315 | let contentH = max(leftView.frame.height, rightView.frame.height) 316 | let leftOrigin = CGPoint(x: margin.left, y: (contentH-leftView.frame.height)/2.0 + margin.top) 317 | let rightOrigin = CGPoint(x: margin.left + leftView.frame.width + self.ddy.padding, y: (contentH-rightView.frame.height)/2.0 + margin.top) 318 | 319 | self.bounds = CGRect(x: 0, y: 0, width: contentW + margin.left + margin.right, height: contentH + margin.top + margin.bottom) 320 | leftView.frame = CGRect(origin: leftOrigin, size: leftView.frame.size) 321 | rightView.frame = CGRect(origin: rightOrigin, size: rightView.frame.size) 322 | } 323 | func vertical(_ topView: UIView,_ bottomView: UIView,_ backSize: CGSize) { 324 | let contentW = max(max(topView.frame.width, bottomView.frame.width), backSize.width-margin.left-margin.right) 325 | let contentH = max(topView.frame.height + self.ddy.padding + bottomView.frame.height, backSize.height-margin.top-margin.bottom) 326 | let topOrigin = CGPoint(x: (contentW-topView.frame.width)/2.0 + margin.left, y: margin.top) 327 | let bottomOrigin = CGPoint(x: (contentW-bottomView.frame.width)/2.0 + margin.left, y: margin.top + topView.frame.height + self.ddy.padding) 328 | 329 | self.bounds = CGRect(x: 0, y: 0, width: contentW + margin.left + margin.right, height: contentH + margin.top + margin.bottom) 330 | topView.frame = CGRect(origin: topOrigin, size: topView.frame.size) 331 | bottomView.frame = CGRect(origin: bottomOrigin, size: bottomView.frame.size) 332 | print("layout: \(self.bounds) \(topView.frame) \(bottomView.frame)") 333 | } 334 | 335 | print("0000: \(self.bounds) \(self.imageView!.frame) \(self.titleLabel!.frame)") 336 | titleLabel?.sizeToFit() 337 | switch self.ddy.style { 338 | case .imageLeft: horizontal(self.imageView!, self.titleLabel!) 339 | case .imageRight: horizontal(self.titleLabel!, self.imageView!) 340 | case .imageTop: vertical(self.imageView!, self.titleLabel!, self.frame.size) 341 | case .imageBottom: vertical(self.titleLabel!, self.imageView!, self.frame.size) 342 | default: return 343 | } 344 | } 345 | } 346 | ``` -------------------------------------------------------------------------------- /Swift/SwiftTips.md: -------------------------------------------------------------------------------- 1 | 1. 打印代码执行时间 2 | 3 | ``` 4 | let startTime = CFAbsoluteTimeGetCurrent() 5 | // code              6 | let endTime = CFAbsoluteTimeGetCurrent() 7 | debugPrint("\((endTime - startTime) * 1000) 毫秒") 8 | ``` 9 | 10 | 2. UIButton Selected 按住时禁止显示normal 文字 11 | 12 | ``` 13 | let button = UIButton(type: .custom) 14 | button.setTitle("下一步".localized, for: .normal) 15 | button.setTitle("喂养".localized, for: .selected) 16 | button.setTitle("喂养".localized, for: [.selected, .highlighted]) 17 | button.adjustsImageWhenHighlighted = false 18 | ``` 19 | 20 | 3. 字符串转字符数组 21 | 22 | ``` 23 | let charArray = Array("String") 24 | ``` 25 | 26 | 4. 更改图片缩放和方向 27 | 28 | ``` 29 | // 可用在绘制的图片或网络图片设置@2x @3x 30 | let img = UIImage() 31 | let a = UIImage(cgImage: img.cgImage, scale: img.scale, orientation: .up) 32 | ``` 33 | 34 | 5. 找视图 35 | 36 | ``` 37 | // viewWithTag 38 | 39 | 设置多个视图相同tag,层级为下面关系 40 | 41 | .view1 42 | ..view11 43 | ...view111 44 | 45 | .view2 46 | ..view21 47 | 48 | https://blog.csdn.net/lingduhuoyan245/article/details/46849943 49 | https://blog.csdn.net/qq_19411159/article/details/70141913 50 | ``` 51 | 52 | 6. DispatchTime 53 | 54 | ``` 55 | DispatchTime.now() + .seconds(value) 56 | ``` 57 | 58 | 7. 等执行完执行下一步 59 | 60 | ``` 61 | @objc func prepareForShare() { 62 | 63 | let allLooker = [firstLooker, secondLooker, thirdLooker, forthLooker, fifthLooker, sexthLooker, seventhLooker, eighthLooker] 64 | // 截图隐藏空位 隐藏戒指 65 | let hideOperation = BlockOperation { 66 | DispatchQueue.main.async { [self] in 67 | allLooker.forEach { $0.isHidden = ($0.json == nil) ? true : false } 68 | boyRingAnimateImgView.isHidden = true 69 | girlRingAnimateImgView.isHidden = true 70 | } 71 | } 72 | // 截图分享 73 | let shareOperation = BlockOperation { [self] in 74 | DispatchQueue.main.async { 75 | self.shareAction() 76 | } 77 | } 78 | // 恢复显示空位 显示戒指 79 | let showOperation = BlockOperation { 80 | DispatchQueue.main.async { [self] in 81 | allLooker.forEach { $0.isHidden = false } 82 | boyRingAnimateImgView.isHidden = false 83 | girlRingAnimateImgView.isHidden = false 84 | } 85 | } 86 | shareOperation.addDependency(hideOperation) 87 | showOperation.addDependency(shareOperation) 88 | 89 | let operationQueue = OperationQueue() 90 | operationQueue.maxConcurrentOperationCount = 1 91 | operationQueue.addOperations([hideOperation, shareOperation, showOperation], waitUntilFinished: true) 92 | } 93 | ``` 94 | 95 | 8. 自定义比较相等 96 | 97 | ``` 98 | extension Animal: Equatable { 99 | static func ==(left: Animal, right: Animal) -> Bool { 100 | return (left.height == right.height && left.weight == right.weight) 101 | } 102 | } 103 | ``` 104 | 105 | 9. 循环 106 | 107 | ``` 108 | // for..in.. 109 | for item in items { } 110 | 111 | // 112 | 113 | ``` 114 | 115 | ``` 116 | /// 通过Cell类自动获取类名注册cell 117 | @discardableResult 118 | func tc_register(cellClass: T.Type) -> UITableView { 119 | register(cellClass, forCellReuseIdentifier: String(describing: T.self)) 120 | return self 121 | } 122 | 123 | /// 批量通过Cell类自动获取类名注册cell 124 | @discardableResult 125 | func tc_register(cellClasses: UITableViewCell.Type...) -> UITableView { 126 | for cellCls in cellClasses { 127 | register(cellCls, forCellReuseIdentifier: String(describing: cellCls.self)) 128 | } 129 | return self 130 | } 131 | 132 | /// 通过Cell类获取复用cell 133 | func tc_dequeueReusableCell(_ cellClass: T.Type, for indexPath: IndexPath) -> T { 134 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T else { 135 | fatalError("未能通过 \(String(describing: T.self)) 取出 \(String(describing: cellClass)),请检查注册的实际情况") 136 | } 137 | return cell 138 | } 139 | ``` 140 | 141 | 10. 使用ISO 8601和RFC 3339的格式标准生成日期时间戳 142 | 143 | ``` 144 | // "2015-01-01T00:00:00.000Z" 145 | var now = NSDate() 146 | var formatter = NSDateFormatter() 147 | formatter.dateFormat ="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 148 | formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) 149 | println(formatter.stringFromDate(now)) 150 | // https://www.codenong.com/28016578/ 151 | ``` 152 | 153 | 11. build setting -> User-defined 154 | 155 | ``` 156 | // 不每次重复全量编译 157 | user-defined HEADERMAP_USES_VFS YES 158 | 159 | // Command PhaseScriptExecution failed with a nonzero exit code 160 | SWIFT_ENABLE_BATCH_MODE NO 161 | ``` 162 | 163 | 12. webp 164 | 165 | ``` 166 | https://github.com/tattn/AnimatedWebP 167 | ``` 168 | 169 | 13. 日历 170 | 171 | ``` 172 | https://github.com/tattn/TTEventKit 173 | ``` 174 | 175 | 14. swift版本 176 | 177 | ``` 178 | #if swift(>=5.3) 179 | if #available(iOS 14.0, *) { 180 | return PHPhotoLibrary.authorizationStatus(for: accessLevel) 181 | } else { 182 | return PHPhotoLibrary.authorizationStatus() 183 | } 184 | #else 185 | return PHPhotoLibrary.authorizationStatus() 186 | #endif 187 | ``` 188 | 189 | 15. 扬声器播放 190 | 191 | ``` 192 | do { 193 | try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) 194 | } catch { 195 | DDLogError("666666 RocketFox audio overrideOutputAudioPort \(error)") 196 | } 197 | ``` 198 | 199 | ``` 200 | post_install do |installer_representation| 201 | installer_representation.pods_project.targets.each do |target| 202 | target.build_configurations.each do |config| 203 | config.build_settings['ENABLE_BITCODE'] = 'NO' 204 | config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" 205 | config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" 206 | config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" 207 | 208 | _plistFile = '$(SRCROOT)/Target Support Files/Pods-CommonPods-game_werewolf/Pods-CommonPods-game_werewolf-Info.plist' 209 | _frameworkArray = ["Adjust", "FirebaseInstallations", "Alamofire", "AppAuth", "Base64", "CocoaLumberjack", "CodePush", "CYLTabBarController", "DateToolsSwift", "DeviceKit", "Differentiator", "DoubleConversion", "EasyTipView", "EmptyDataSet-Swift", "FBReactNativeSpec", "FBSDKCoreKit", "FBSDKLoginKit", "FBSDKShareKit", "FirebaseCore", "FirebaseCoreDiagnostics", "FirebaseInstanceID", "FirebaseMessaging", "FLAnimatedImage", "FMDB", "Folly", "FTPopOverMenu", "FXForms", "GCDWebServer", "glog", "GoogleDataTransport", "GoogleDataTransportCCTSupport", "GoogleUtilities", "GPUImage", "GTMAppAuth", "GTMSessionFetcher", "HandyJSON", "HXPhotoPicker", "InputBarAccessoryView", "JWT", "JXCategoryView", "KeychainSwift", "KMCGeigerCounter", "LineSDKSwift", "LogUploader", "LTMorphingLabel", "MessageKit", "MJRefresh", "MLPAutoCompleteTextField", "MMKV", "MMKVCore", "nanopb", "orangeLab_iOS", "OrangelabIM", "OrgPhotoPreviewer", "PromisesObjC", "Protobuf", "QGVAPlayer", "Qiniu", "RCTTypeSafety", "React-Core", "React-CoreModules", "React-cxxreact", "React-jsi", "React-jsiexecutor", "React-jsinspector", "react-native-blur", "react-native-cameraroll", "react-native-geolocation", "react-native-image-resizer", "react-native-netinfo", "react-native-safe-area-context", "react-native-splash-screen", "react-native-view-shot", "react-native-viewpager", "react-native-webview", "React-RCTAnimation", "React-RCTBlob", "React-RCTImage", "React-RCTLinking", "React-RCTNetwork", "React-RCTSettings", "React-RCTText", "React-RCTVibration", "ReactCommon", "RealmJS", "rn-fetch-blob", "RNCAsyncStorage", "RNCClipboard", "RNCMaskedView", "RNDateTimePicker", "RNGestureHandler", "RNImageCropPicker", "RNLocalize", "RNReanimated", "RNScreens", "RNSound", "RNSVG", "RxCocoa", "RxDataSources", "RxRelay", "RxSwift", "SDWebImage", "SnapKit", "Socket.IO-Client-Swift", "Spring", "SSZipArchive", "Starscream", "SVGAPlayer", "SVProgressHUD", "SwiftyJSON", "SwiftyStoreKit", "TCAssist", "TCKit", "TextFieldEffects", "TOCropViewController", "TTGTagCollectionView", "TWMessageBarManager", "Yoga", "YTKKeyValueStore", "YYCache", "YYImage", "YYText", "YYWebImage", "ZLPhotoBrowser"] 210 | if _frameworkArray.include?(target.name) 211 | config.build_settings['INFOPLIST_FILE'] = "#{_plistFile}" 212 | end 213 | 214 | end 215 | end 216 | end 217 | ``` 218 | 219 | 220 | 221 | 222 | 223 | ``` 224 | post_install do |installer_representation| 225 | installer_representation.pods_project.targets.each do |target| 226 | target.build_configurations.each do |config| 227 | config.build_settings['ENABLE_BITCODE'] = 'NO' 228 | config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" 229 | config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" 230 | config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" 231 | 232 | _plistFile = '$(SRCROOT)/Target Support Files/Pods-en_dev/Pods-en_dev-Info.plist' 233 | _frameworkArray = ["Adjust", "FirebaseInstallations", "Alamofire", "AppAuth", "Base64", "CocoaLumberjack", "CodePush", "CYLTabBarController", "DateToolsSwift", "DeviceKit", "Differentiator", "DoubleConversion", "EasyTipView", "EmptyDataSet-Swift", "FBReactNativeSpec", "FBSDKCoreKit", "FBSDKLoginKit", "FBSDKShareKit", "FirebaseCore", "FirebaseCoreDiagnostics", "FirebaseInstanceID", "FirebaseMessaging", "FLAnimatedImage", "FMDB", "Folly", "FTPopOverMenu", "FXForms", "FSPagerView", "GCDWebServer", "glog", "GoogleDataTransport", "GoogleDataTransportCCTSupport", "GoogleUtilities", "GPUImage", "GTMAppAuth", "GTMSessionFetcher", "HandyJSON", "HXPhotoPicker", "InputBarAccessoryView", "IQKeyboardManagerSwift", "JWT", "JXCategoryView", "KeychainSwift", "KMCGeigerCounter", "LineSDKSwift", "LogUploader", "LTMorphingLabel", "MessageKit", "MJRefresh", "MLPAutoCompleteTextField", "MMKV", "MMKVCore", "nanopb", "orangeLab_iOS", "OrangelabIM", "OrgPhotoPreviewer", "PromisesObjC", "Protobuf", "QGVAPlayer", "Qiniu", "RCTTypeSafety", "React-Core", "React-CoreModules", "React-cxxreact", "React-jsi", "React-jsiexecutor", "React-jsinspector", "react-native-blur", "react-native-cameraroll", "react-native-geolocation", "react-native-image-resizer", "react-native-netinfo", "react-native-safe-area-context", "react-native-splash-screen", "react-native-view-shot", "react-native-viewpager", "react-native-webview", "React-RCTAnimation", "React-RCTBlob", "React-RCTImage", "React-RCTLinking", "React-RCTNetwork", "React-RCTSettings", "React-RCTText", "React-RCTVibration", "ReactCommon", "RealmJS", "rn-fetch-blob", "RNCAsyncStorage", "RNCClipboard", "RNCMaskedView", "RNDateTimePicker", "RNGestureHandler", "RNImageCropPicker", "RNLocalize", "RNReanimated", "RNScreens", "RNSound", "RNSVG", "RxCocoa", "RxDataSources", "RxRelay", "RxSwift", "SDWebImage", "SnapKit", "Socket.IO-Client-Swift", "Spring", "SSZipArchive", "Starscream", "SVGAPlayer", "SVProgressHUD", "SwiftyJSON", "SwiftyStoreKit", "TCAssist", "TCKit", "TextFieldEffects", "TOCropViewController", "TTGTagCollectionView", "TWMessageBarManager", "Yoga", "YTKKeyValueStore", "YYCache", "YYImage", "YYText", "YYWebImage", "ZLPhotoBrowser"] 234 | if _frameworkArray.include?(target.name) 235 | config.build_settings['INFOPLIST_FILE'] = "#{_plistFile}" 236 | end 237 | 238 | end 239 | end 240 | end 241 | ``` 242 | 243 | 244 | ``` 245 | ["eu", "hr_BA", "en_CM", "en_BI", "en_AE", "rw_RW", "ast", "en_SZ", "he_IL", "ar", "uz_Arab", "en_PN", "as", "en_NF", "ks_IN", "es_KY", "rwk_TZ", "zh_Hant_TW", "en_CN", "gsw_LI", "ta_IN", "th_TH", "es_EA", "fr_GF", "nso", "ar_001", "en_RW", "tr_TR", "de_CH", "ee_TG", "en_NG", "byn", "fr_TG", "fr_SC", "az", "es_HN", "en_CO", "pa_Aran_PK", "en_AG", "ccp_IN", "gsw", "ru_KZ", "ks_Aran", "dyo", "so_ET", "ff_Latn", "zh_Hant_MO", "de_BE", "km_KH", "nus_SS", "my_MM", "mgh_MZ", "ee_GH", "es_EC", "kw_GB", "rm_CH", "en_ME", "nyn", "mk_MK", "bs_Cyrl_BA", "ar_MR", "es_GL", "en_BM", "ms_Arab", "en_AI", "gl_ES", "en_PR", "trv_TW", "ne_IN", "or_IN", "byn_ER", "khq_ML", "ia_001", "en_MG", "iu_CA", "en_LC", "pt_TL", "ta_SG", "tn_ZA", "myv", "syr", "jmc_TZ", "ceb_PH", "om_ET", "lv_LV", "ps_PK", "es_US", "ceb", "en_PT", "vai_Latn_LR", "en_NL", "to_TO", "cgg_UG", "en_MH", "ta", "ur_Arab_PK", "xh", "zu_ZA", "shi_Latn_MA", "es_FK", "ar_KM", "en_AL", "brx_IN", "te", "chr_US", "yo_BJ", "fr_VU", "pa", "ks_Arab", "sat_Olck", "kea", "ksh_DE", "sw_CD", "te_IN", "fr_RE", "tg", "th", "ur_IN", "ti", "yo_NG", "es_HT", "es_GP", "nqo_GN", "guz_KE", "tk", "kl_GL", "ksf_CM", "mua_CM", "lag_TZ", "lb", "fr_TN", "tn", "es_PA", "pl_PL", "to", "hi_IN", "dje_NE", "es_GQ", "en_BR", "kok_IN", "ss_ZA", "fr_GN", "pl", "bem", "ha", "ckb", "es_CA", "lg", "tr", "en_PW", "ts", "tt", "en_NO", "nyn_UG", "nr_ZA", "oc_FR", "sr_Latn_RS", "jbo", "gsw_FR", "he", "pa_Guru", "ps_AF", "lu_CD", "mgo_CM", "qu_BO", "en_BS", "sn_ZW", "da", "ps", "ss_SZ", "ln", "pt", "hi", "lo", "ebu", "de", "gu_IN", "wo_SN", "seh", "en_CX", "en_ZM", "mni_Mtei", "fr_HT", "fr_GP", "pt_GQ", "lt", "lu", "es_TT", "ln_CD", "vai_Latn", "el_GR", "lv", "en_MM", "io_001", "en_KE", "sbp", "ff_Latn_GW", "hr", "ur_Aran_PK", "en_CY", "es_GT", "twq_NE", "zh_Hant_HK", "kln_KE", "fr_GQ", "chr", "hu", "es_UY", "fr_CA", "ms_BN", "en_NR", "mer", "fr_SN", "es_PE", "shi", "bez", "sw_TZ", "wae_CH", "kkj", "hy", "dz_BT", "en_CZ", "teo_KE", "teo", "en_AR", "ar_JO", "yue_Hans_CN", "mer_KE", "dv", "khq", "ln_CF", "nn_NO", "es_SR", "en_MO", "ve_ZA", "gez", "ar_TD", "dz", "ses", "en_BW", "en_AS", "ar_IL", "es_BB", "bo_CN", "nnh", "mni", "ff_Latn_GM", "hy_AM", "ln_CG", "sr_Latn_BA", "teo_UG", "en_MP", "ksb_TZ", "ar_SA", "smn_FI", "ar_LY", "en_AT", "so_KE", "fr_CD", "af_NA", "en_NU", "es_PH", "en_KI", "ba_RU", "en_JE", "ff_Latn_GH", "lkt", "dv_MV", "en_AU", "fa_IR", "pt_FR", "uz_Latn_UZ", "zh_Hans_CN", "ewo_CM", "jv_ID", "fr_PF", "ca_IT", "es_GY", "en_BZ", "ar_KW", "am_ET", "fr_FR", "ff_Latn_SL", "en_VC", "es_DM", "fr_DJ", "pt_GW", "fr_CF", "es_SV", "en_MS", "nqo", "pt_ST", "ar_SD", "luy_KE", "gd_GB", "de_LI", "it_VA", "fr_CG", "pt_CH", "ckb_IQ", "zh_Hans_SG", "en_MT", "sc_IT", "ha_NE", "en_ID", "ewo", "af_ZA", "om_KE", "os_GE", "wa", "nl_SR", "es_ES", "es_DO", "ar_IQ", "sat_Olck_IN", "en_UA", "tig_ER", "fr_CH", "nnh_CM", "es_SX", "es_419", "en_MU", "en_US_POSIX", "yav_CM", "luo_KE", "dua_CM", "et_EE", "en_IE", "ak_GH", "sa", "rwk", "sc", "es_CL", "kea_CV", "sd", "fr_CI", "ckb_IR", "fr_BE", "se", "en_NZ", "syr_IQ", "en_MV", "en_LR", "es_PM", "en_KN", "nb_SJ", "ha_NG", "sg", "tn_BW", "sr_Cyrl_RS", "ru_RU", "en_ZW", "oc", "ga_IE", "si", "sv_AX", "wo", "en_VG", "ky_KG", "agq_CM", "mzn", "fr_BF", "naq_NA", "mr_IN", "en_MW", "de_AT", "az_Latn", "en_LS", "ka", "sk", "sl", "sat_Deva_IN", "sn", "sr_Latn_ME", "wa_BE", "fr_NC", "so", "is_IS", "kpe_LR", "twq", "ig_NG", "sq", "fo_FO", "sd_Deva", "sr", "ga", "eo_001", "en_MX", "om", "en_LT", "bas_CM", "se_NO", "ss", "st", "tzm", "ki", "nl_BE", "ar_QA", "gd", "sv", "kk", "pa_Aran", "rn_BI", "es_CO", "az_Latn_AZ", "kl", "en_VI", "es_AG", "ca", "or", "km", "os", "sw", "en_MY", "kn", "en_LU", "fr_SY", "ar_TN", "en_JM", "fr_PM", "ko", "st_ZA", "fr_NE", "ce", "fr_MA", "co_FR", "nso_ZA", "gl", "ru_MD", "kaj_NG", "es_BL", "ks", "fr_CM", "lb_LU", "gv_IM", "fr_BI", "gn", "saq_KE", "en_LV", "ku", "en_KR", "ks_Arab_IN", "es_NI", "en_GB", "kw", "nl_SX", "dav_KE", "tr_CY", "ky", "en_UG", "es_BM", "en_TC", "es_AI", "ar_EG", "fr_BJ", "co", "gu", "es_PR", "fr_RW", "gv", "lrc_IQ", "kcg", "sr_Cyrl_BA", "es_MF", "fr_MC", "cs", "bez_TZ", "es_CR", "asa_TZ", "ar_EH", "fo_DK", "ms_Arab_BN", "cv", "ccp", "en_JP", "sbp_TZ", "en_IL", "lt_LT", "mfe", "en_GD", "moh_CA", "cy", "es_LC", "ca_FR", "ts_ZA", "ff_Latn_SN", "ug_CN", "es_BO", "en_SA", "fr_BL", "bn_IN", "uz_Cyrl_UZ", "lrc_IR", "az_Cyrl", "en_IM", "sw_KE", "en_SB", "pa_Arab", "ur_PK", "haw_US", "ar_SO", "en_IN", "cv_RU", "fil", "fr_MF", "scn", "en_WS", "es_CU", "es_BQ", "ja_JP", "fy_NL", "en_SC", "yue_Hant_HK", "en_IO", "pt_PT", "en_HK", "ks_Aran_IN", "en_GG", "fr_MG", "ff_Latn_MR", "de_LU", "tig", "zh_Hant_CN", "tzm_MA", "es_BR", "en_TH", "en_SD", "nds_DE", "ln_AO", "ny_MW", "shi_Tfng", "as_IN", "en_GH", "ms_MY", "ro_RO", "jgo_CM", "es_CW", "dua", "en_UM", "es_BS", "en_SE", "kn_IN", "en_KY", "vun_TZ", "kln", "lrc", "en_GI", "moh", "ca_ES", "mni_Mtei_IN", "rof", "pt_CV", "kok", "pt_BR", "ar_DJ", "yi_001", "fi_FI", "zh", "es_PY", "ar_SS", "arn", "ve", "mua", "sr_Cyrl_ME", "hi_Latn", "vai_Vaii_LR", "en_001", "nl_NL", "en_TK", "ca_AD", "en_SG", "fr_DZ", "si_LK", "sv_SE", "pt_AO", "mni_Beng", "vi", "xog_UG", "xog", "en_IS", "syr_SY", "nb", "seh_MZ", "es_AR", "sk_SK", "en_SH", "ti_ER", "nd", "az_Cyrl_AZ", "zu", "ne", "nd_ZW", "kcg_NG", "el_CY", "en_IT", "nl_BQ", "da_GL", "ja", "wal_ET", "rm", "fr_ML", "gaa_GH", "rn", "en_VU", "ff_Latn_BF", "ro", "ebu_KE", "rof_TZ", "ru_KG", "en_SI", "sa_IN", "sg_CF", "mfe_MU", "nl", "brx", "bs_Latn", "fa", "zgh_MA", "ff_Latn_LR", "en_GM", "shi_Latn", "en_FI", "nn", "en_EE", "ru", "yue", "kam_KE", "fur", "vai_Vaii", "ar_ER", "rw", "ti_ET", "ff", "luo", "nr", "ur_Arab_IN", "ba", "fa_AF", "nl_CW", "es_MQ", "en_HR", "en_FJ", "fi", "pt_MO", "be", "en_US", "en_TO", "en_SK", "bg", "mi_NZ", "arn_CL", "ny", "ru_BY", "it_IT", "ml_IN", "gsw_CH", "qu_EC", "fo", "ff_Latn_CM", "sv_FI", "en_FK", "nus", "ff_Latn_NE", "jv", "ta_LK", "vun", "sr_Latn", "es_BZ", "fr", "en_SL", "bm", "es_VC", "trv", "ar_BH", "guz", "bn", "bo", "ar_SY", "es_MS", "lo_LA", "ne_NP", "uz_Latn", "be_BY", "es_IC", "sr_Latn_XK", "ar_MA", "pa_Guru_IN", "br", "luy", "kde_TZ", "es_AW", "bs", "fy", "fur_IT", "gez_ER", "hu_HU", "ar_AE", "gaa", "en_HU", "sah_RU", "zh_Hans", "en_FM", "fr_MQ", "ko_KP", "en_150", "en_DE", "ce_RU", "en_CA", "hsb_DE", "sq_AL", "wuu", "en_TR", "ro_MD", "es_VE", "tg_TJ", "fr_WF", "mt_MT", "kab", "nmg_CM", "ms_SG", "en_GR", "ru_UA", "fr_MR", "xh_ZA", "zh_Hans_MO", "de_IT", "ku_TR", "ccp_BD", "kpe_GN", "ur_Aran_IN", "myv_RU", "bs_Cyrl", "nds_NL", "es_KN", "sw_UG", "tt_RU", "ko_KR", "yue_Hans", "en_DG", "bo_IN", "en_CC", "shi_Tfng_MA", "lag", "it_SM", "en_TT", "ms_Arab_MY", "os_RU", "sq_MK", "es_VG", "kaj", "bem_ZM", "kde", "ur_Aran", "ar_OM", "kk_KZ", "cgg", "gez_ET", "bas", "kam", "scn_IT", "es_MX", "sah", "wae", "en_GU", "zh_Hant", "fr_MU", "fr_KM", "ar_LB", "en_BA", "sat_Deva", "en_TV", "sr_Cyrl", "mzn_IR", "es_VI", "dje", "kab_DZ", "fil_PH", "se_SE", "vai", "hr_HR", "bs_Latn_BA", "nl_AW", "dav", "so_SO", "ar_PS", "en_FR", "uz_Cyrl", "jbo_001", "en_BB", "ki_KE", "en_TW", "naq", "en_SS", "mg_MG", "mas_KE", "ff_Latn_GN", "en_RO", "en_PG", "mgh", "dyo_SN", "wal", "mas", "agq", "bn_BD", "haw", "yi", "nb_NO", "da_DK", "en_DK", "saq", "st_LS", "ug", "cy_GB", "fr_YT", "jmc", "ses_ML", "en_PH", "de_DE", "ar_YE", "es_TC", "bm_ML", "yo", "lkt_US", "uz_Arab_AF", "jgo", "sl_SI", "gn_PY", "pt_LU", "sat", "en_CH", "asa", "en_BD", "uk", "lg_UG", "nds", "qu_PE", "mgo", "id_ID", "en_NA", "en_GY", "ff_Latn_NG", "zgh", "dsb", "fr_LU", "pt_MZ", "mas_TZ", "en_DM", "ia", "es_GD", "en_BE", "mg", "sd_PK", "ta_MY", "fr_GA", "ka_GE", "nmg", "en_TZ", "ur", "eu_ES", "ar_DZ", "mi", "ur_Arab", "id", "so_DJ", "kpe", "hsb", "yav", "mk", "ml", "pa_Arab_PK", "en_ER", "ig", "se_FI", "mn", "ksb", "uz", "vi_VN", "ii", "qu", "en_RS", "en_PK", "ee", "ast_ES", "yue_Hant", "mr", "ms", "en_ES", "ha_GH", "it_CH", "sq_XK", "mt", "en_CK", "br_FR", "en_BG", "io", "es_GF", "sr_Cyrl_XK", "ksf", "en_SX", "bg_BG", "tk_TM", "en_PL", "af", "el", "cs_CZ", "fr_TD", "ks_Deva", "zh_Hans_HK", "is", "ksh", "my", "mn_MN", "en", "it", "dsb_DE", "ii_CN", "eo", "iu", "en_CL", "en_ZA", "en_AD", "smn", "mni_Beng_IN", "ak", "en_RU", "kkj_CM", "am", "es", "et", "uk_UA"] 246 | 247 | 248 | 249 | UserDefaults.standard.removeObject(forKey: "AppleLanguages") 250 | DDLogInfo("666666Language NSLocale.preferredLanguages \(NSLocale.preferredLanguages)") 251 | DDLogInfo("666666Language UserDefault \(String(describing: UserDefaults.standard.array(forKey: "AppleLanguages")))") 252 | DDLogInfo("666666Language languageCode \(NSLocale.current.languageCode ?? "code blank")") 253 | DDLogInfo("666666Language regionCode \(NSLocale.current.regionCode ?? "regionCode blank")") 254 | DDLogInfo("666666Language currencyCode \(NSLocale.current.currencyCode ?? "currencyCode blank")") 255 | DDLogInfo("666666Language default \(NSLocalizedString(self, comment: ""))") 256 | 257 | DDLogInfo("666666Language Bundle \(Bundle.main.preferredLocalizations)") 258 | DDLogInfo("666666Language special \(NSLocale.availableLocaleIdentifiers)") 259 | ``` 260 | 261 | [UITextView 设置不允许选中,允许链接跳转](https://www.jianshu.com/p/6d941e81cfd7) --------------------------------------------------------------------------------