Swift 知识小集
以下内容均是笔者学习过程中收集的知识点,顺序比较跳跃,初衷是为了方便查阅,顺便加深记忆。内容会不断更新,如果有什么问题或者有好的 Swift 方面的语法糖或者知识点也可以提出来,我会挑选斟酌后收录,欢迎大家关注~ 环境: Swift 4.0 Associated ObjectObjective-C 的 runtime 里的 Associated Object 允许我们在使用 Category 扩展现有的类的功能的时候,直接添加实例变量。在 Swift 中 extension 不能添加存储属性,我们可以利用 Associated Object 来实现,比如下面的 // MyClass.swift class MyClass {} // MyClassExtension.swift private var key: Void? extension MyClass { var title: String? { get { return swift_getAssociatedObject(self,&key) as? String } set { swift_setAssociatedObject(self,&key,newValue,.swift_ASSOCIATION_RETAIN_NONATOMIC) } } } // 测试 func printTitle(_ input: MyClass) { if let title = input.title { print("Title: (title)") } else { print("没有设置") } } let a = MyClass() printTitle(a) a.title = "Swifter.tips" printTitle(a) // 输出: // 没有设置 // Title: Swifter.tips” Delegate 声明为 weakSwift 中 Delegate 需要被声明成 protocol MyClassDelegate { func method() } class MyClass { weak var delegate: MyClassDelegate? } class ViewController: UIViewController,MyClassDelegate { // ... var someInstance: MyClass! override func viewDidLoad() { super.viewDidLoad() someInstance = MyClass() someInstance.delegate = self } func method() { print("Do something") } //... } // 编译失败 // 'weak' may only be applied to class and class-bound protocol types,not 'MyClassDelegate' 这是因为 Swift 的 protocol 是可以被除了 class 以外的其他类型遵守的,而对于像 想要在 Swift 中使用 weak delegate,我们就需要将 protocol 限制在 class 内:
@objc protocol MyClassDelegate { func method() }
protocol MyClassDelegate: class { func method() } 可选协议和协议扩展Objective-C 中的 protocol 里存在 class ViewController: UIViewController,MyProtocol { } // 编译失败 // Type 'ViewController' does not conform to protocol 'MyProtocol' 如果我们想要像 Objective-C 里那样定义可选的协议方法,就需要将协议本身和可选方法都定义为 Objective-C 的,也即在 protocol 定义之前加上 @objc protocol MyProtocol { @objc optional func myMethod() } 另外,对于所有的声明,它们的前缀修饰是完全分开的,也就是说你不能像是在 Objective-C 里那样用一个 @objc protocol MyProtocol { @objc optional func optionalMethod() // 可选 func necessaryMethod() // 必须 @objc optional func anotherOptionalMethod() // 可选 } 一个不可避免的限制是,使用 protocol MyProtocol { func optionalMethod() // 可选 func necessaryMethod() // 必须 func anotherOptionalMethod() // 可选 } extension MyProtocol { //默认的可选实现 func optionalMethod() { print("optionalMethod") } //默认的可选实现 func anotherOptionalMethod() { print("anotherOptionalMethod") } } class ViewController: UIViewController,MyProtocol { // 必须的实现 func necessaryMethod() { print("necessaryMethod") } override func viewDidLoad() { super.viewDidLoad() self.optionalMethod(); self.necessaryMethod(); self.anotherOptionalMethod(); } } // 输出: // optionalMethod // necessaryMethod // necessaryMethod 单例Swift 中的单例非常简单,Swift 1.2 以及之后: class Singleton { static let sharedInstance = Singleton() private init() {} } 这种写法不但是线程安全的,也是懒加载的, 另外,这个类型中加入了一个私有的初始化方法,来覆盖默认的公开初始化方法,这让项目中的其他地方不能够通过 init 来生成自己的 输出格式化在 Objective-C 中的 float a = 1.234567; NSString *b = @"Helllo"; NSLog(@"float:%.2f str:%p",a,b); // 输出: // float:1.23 str:0x1024a1078 对应 Swift 中我们可以这样: let a = 1.234567 let b = "Helllo" let c = String(format:"float:%.2f str:%p",b) print(c) // 输出: // float:1.23 str:0x604000249e10 Selector
let btn = UIButton.init(type: .system) btn.backgroundColor = UIColor.red btn.frame = CGRect(x: 100,y: 100,width: 150,height: 40) btn.setTitle("Button",for: .normal) //无参数 btn.addTarget(self,action: #selector(btnClick),for: .touchUpInside) view.addSubview(btn) @objc func btnClick() { print("button click !") } ... //有参数 btn.addTarget(self,action: #selector(btnClick(_ :)),for: .touchUpInside) ... @objc func btnClick(_ button: UIButton) { print("button click !") } 将 protocol 的方法声明为 mutatingSwift 的 protocol 不仅可以被 class 类型实现,也适用于 protocol Vehicle { func changeColor() } struct MyCar: Vehicle { var color = "blue" func changeColor() { color = "red" } } // 编译失败 // Cannot assign to property: 'self' is immutable 我们应该加上 protocol Vehicle { mutating func changeColor() } struct MyCar: Vehicle { var color = "blue" mutating func changeColor() { color = "red" } } override func viewDidLoad() { super.viewDidLoad() var car = MyCar() print(car.color) car.changeColor() print(car.color) } // 输出: // blue // 输出: // red 数组遍历 enumerate使用 NSArray 时一个很常遇见的的需求是在枚举数组内元素的同时也想使用对应的下标索引,在 Objective-C 中最方便的方式是使用 NSArray 的 let arr = ["a","b","c","d","e"] for (idx,str) in arr.enumerated() { print("idx: (idx) str: (str)") } // 输出: idx: 0 str: a idx: 1 str: b idx: 2 str: c idx: 3 str: d idx: 4 str: e 输入输出参数 inout函数参数默认是常量,如果试图在函数体中更改参数值将会导致编译错误,比如下面的例子中尝试着交换值: func swapTwoInts(_ a: Int,_ b: Int) { let temporaryA = a a = b b = temporaryA } // 编译失败 // Cannot assign to value: 'a' is a 'let' constant // Cannot assign to value: 'b' is a 'let' constant 如果想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters): func swapTwoInts(_ a: inout Int,_ b: inout Int) { let temporaryA = a a = b b = temporaryA } Default 参数Swift 的方法是支持默认参数的,也就是说在声明方法时,可以给某个参数指定一个默认使用的值。在调用该方法时要是传入了这个参数,则使用传入的值,如果缺少这个输入参数,那么直接使用设定的默认值进行调用。和其他很多语言的默认参数相比较,Swift 中的默认参数限制更少,并没有所谓 "默认参数之后不能再出现无默认值的参数"这样的规则,举个例子,下面两种方法的声明在 Swift 里都是合法可用的: func sayHello1(str1: String = "Hello",str2: String,str3: String) { print(str1 + str2 + str3) } func sayHello2(str1: String,str3: String = "World") { print(str1 + str2 + str3) } sayHello1(str2: " ",str3: "World") sayHello2(str1: "Hello",str2: " ") //输出都是 Hello World 延迟加载 lazy延时加载或者说延时初始化是很常用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费不少时间,如果有一些对象的属性和内容非常复杂的话,这个时间更是不可忽略。另外,有些情况下我们并不会立即用到一个对象的所有属性,而默认情况下初始化时,那些在特定环境下不被使用的存储属性,也一样要被初始化和赋值,也是一种浪费。在 Objective-C 中,一个延迟加载一般是这样的: // ClassA.h @property (nonatomic,copy) NSString *testString; // ClassA.m - (NSString *)testString { if (!_testString) { _testString = @"Hello"; NSLog(@"只在首次访问输出"); } return _testString; } 对应在 Swift 中,使用 class ClassA { lazy let str: String = { let str = "Hello" print("只在首次访问输出") return str }() } // 编译失败 // 'lazy' cannot be used on a let class ClassA { lazy var str = { let str = "Hello" print("只在首次访问输出") return str }() } // 编译失败 // Unable to infer complex closure return type 我们应该声明为 class ClassA { lazy var str: String = { let str = "Hello" print("只在首次访问输出") return str }() } override func viewDidLoad() { super.viewDidLoad() let ca = ClassA() print(ca.str) print(ca.str) print(ca.str) } // 输出: // 只在首次访问输出 // Hello // Hello // Hello 如果不需要做什么额外工作的话,也可以对这个 lazy var str: String = "Hello" 我们还可以利用 let data = 1...3 let result = data.map { (i: Int) -> Int in print("正在处理 (i)") return i * 2 } print("准备访问结果") for i in result { print("操作后结果为 (i)") } print("操作完毕") // 输出: // 正在处理 1 // 正在处理 2 // 正在处理 3 // 准备访问结果 // 操作后结果为 2 // 操作后结果为 4 // 操作后结果为 6 // 操作完毕 而如果我们先进行一次 let data = 1...3 let result = data.lazy.map { (i: Int) -> Int in print("正在处理 (i)") return i * 2 } print("准备访问结果") for i in result { print("操作后结果为 (i)") } print("操作完毕") // 准备访问结果 // 正在处理 1 // 操作后结果为 2 // 正在处理 2 // 操作后结果为 4 // 正在处理 3 // 操作后结果为 6 // 操作完毕 对于那些不需要完全运行,可能提前退出的情况,使用 lazy 来进行性能优化效果会非常有效。 编译标记在 Objective-C 中,我们经常在代码中插入
在 Objective-C 中还有一个很常用的编译标记,那就是
脚本: TAGS="TODO:|FIXME:" echo "searching ${SRCROOT} for ${TAGS}" find "${SRCROOT}" ( -name "*.swift" ) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*$" | perl -p -e "s/($TAGS)/ warning: $1/" 效果: 换行符在 Swift 3 中,需要换行时是需要 let str = "xxxxnxxx" // 输出: // xxxx // xxx 在 swift 4 中,我们可以使用 let jsonStr = """ { "id": 123455,"nickname": "xxxx","isMale": true,"birthday": "2000年3月24日","personalURL": "https://xxxxxx.github.io" } """ // 输出: { "id": 123455,"personalURL": "https://xxxxxx.github.io" } 字符串切割 split我们需要切割某个字符串时可以用 let str = "Hello,world !" print(str.split(separator: ",")) // 输出: // ["Hello","world !"] KVCclass MyClass { var name = "ifelseboyxx" } Swift 4 中 Apple 引入了新的 KeyPath 的表达方式,现在,对于类型 let object = MyClass() print("name: (object.name)") // set object[keyPath: MyClass.name] = "ifelseboy" // get print("name: (object[keyPath: MyClass.name])") // 输出: // name: ifelseboyxx // name: ifelseboy 另外 Swift 4 中 struct MyStruct { var age: Int } var obj = MyStruct(age: 18) print("我今年 (obj.age) 岁了") obj[keyPath: MyStruct.age] = 8 print("我今年 (obj[keyPath: MyStruct.age]) 岁了") // 输出: // 我今年 18 岁了 // 我今年 8 岁了 Swift 中值类型和引用类型注意点KVC 一节中代码里有个注意点: var obj = MyStruct(age: 18) //替换为 let obj = MyStruct(age: 18) 是编译不过的,会报错: Cannot assign to immutable expression of type 'Int' 笔者初次也犯了这样的错误,想当然的认为 let object = MyClass() 其实原因很简单,swift 中 Class 是引用类型的,而 struct 是值类型的:值类型在被赋给一个变量,或被传给函数时,实际是做了一次拷贝。引用类型在被赋给一个变量,或被传给函数时,传递的是引用。 KVO很遗憾,依然只有 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |