主要针对于Swift中级开发较少用到的tips,本章内容主要来自《Swifter100个tips》中。
1. mutating
作用:Swift 的 mutating 关键字修饰方法是为了能在该方法中修 改 struct 或是 enum 的变量,但是对于class无效,因为在class中可以随意更改变量的值
示例代码:
protocol Car {
var color: UIColor? { get set }
mutating func changeColor();
}
struct Toyota: Car {
var color: UIColor?
mutating func changeColor() {
self.color = UIColor.red;
}
}
class ToyotaClass: Car {
var color: UIColor?
func changeColor() {
self.color = UIColor.orange;
}
}
@autoclosure 和 ??
@autoclosure 的作用是把一句表达式自动的封装成一个闭包,但是它不能修饰带参数的闭包。其实 && 和 || 这两个操作符里也用到了 @autoclosure
例:
func testClosure(block: () -> Bool) {
if block() {
print("block 返回值是true");
}
}
testClosure(block: {2 > 1});
func testAutoclosure(block: @autoclosure () -> Bool) {
if block() {
print("block 返回值是true");
}
}
testAutoclosure(block: 2 > 1);
?? 可以对一个optional的变量 提供一个默认值
例子:
func testOptional() -> Int? {
return 100;
}
//后面的1 其实是被封装成了一个闭包处理的
let value = testOptional() ?? 1;
//??定义是这样:
public func ?? (optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
//实现大概是这样:
func ??(optional: T?, defaultValue: @autoclosure () -> T) -> T {
switch optional {
case .some(let value): return value
case .none:
return defaultValue()
}
}
尝试自己写一个类似于?? 的运算符函数 ~~
infix operator ~~ : DefaultPrecedence
extension Int {
static func ~~ (leftSideParameter: Int, rightSideParameter: @autoclosure () throws -> Int) rethrows -> Int {
print("leftSideParameter == \(leftSideParameter) rightSideParameter == \(try! rightSideParameter()) ");
return 1000;
}
}
let result = 100 ~~ 10;
print(result);
//打印日志: leftSideParameter == 100 rightSideParameter == 10
1000
func 参数的修饰符 inout
如果想像oc那样在一个函数内部更改函数外作为形参的变量值,就要使用inout 修饰该形参
示例:
var parameter = 100;
func testInout( p1: inout Int) {
p1 += 10;
}
testInout(p1: ¶meter);
print("parameter == \(parameter)");
//打印日志: parameter == 110
字面量的转换
所谓字面量,就是指像特定的数字,字符串或者是布尔值这样,能够直接了当地指出自 己的类型并为变量进行赋值的值。
Swift 为我们提供了一组非常有意思的接口,用来将字面量转换为特定的类型。对于那 些实现了字面量转换接口的类型,在提供字面量赋值的时候,就可以简单地按照接口 方法中定义的规则“无缝对应”地通过赋值的方式将值转换为对应类型
- ExpressibleByStringLiteral
- ExpressibleByArrayLiteral
- 懒得都列出来了 感觉这个功能有点鸡肋 还不知道在什么样的场景中能用到
示例:
class Person: ExpressibleByStringLiteral {
typealias StringLiteralType = String
let name: String
required init(stringLiteral value: String) {
self.name = value;
}
}
let p:Person = "dongye";
print("这个人的名字 == \(p.name)");
//打印日志 这个人的名字 == dongye
自定义下标
在swift中 数组和字典都支持下标的方式访问数据,但是在其他的类型也支持定义这种方式。只需实现subscript方法即可。
- 先尝试为数组添加一串无序的下标方式 访问数组:
extension Array {
//first为解决只有一个参数时与原有的会有冲突
subscript( first: Int, inputs: Int...) -> [Element]{
get {
var newInputs = [first];
newInputs.append(contentsOf: inputs);
var results = [Element]();
for index in newInputs {
assert(index < self.count, "index out of range");
results.append(self[index]);
}
return results;
}
set {
var newInputs = [first];
newInputs.append(contentsOf: inputs);
for value in newInputs {
assert(value < self.count, "index out of range");
self[value] = newValue[value];
}
}
}
}
var array = [1,2,3];
array[2,1,0] = [20,30,5];
print(array);
print(array[0,2]);
//打印日志 :[20, 30, 5] [20, 5]
条件编译
在swift中 保留了 #if #elseif #endif 的写法,但是并不能像OC那样随便define一个宏,系统自带的条件编译有 os()系统 和arc()架构判断 os包括 OSX和iOS ; arc 包括x86_64, arm, arm64, i386 ; 其中x86_64和i386 分别是64位模拟器和32位模拟器,arm和arm64 就是32位和64位的真机区别了。
自定义宏也是可以的,在项目的build setting -> swift compiler - custom flags -> other swift flags 加上一个就行。比如: -D kFREE_VERSION
示例:
var isOS = false;
#if os(OSX)
isOS = true
#endif
var isSimulator = false;
#if arch(x86_64)
isSimulator = true;
#elseif arch(arm64)
isSimulator = false;
#endif
print("当前运行环境:\(isOS ? "电脑系统" : "手机系统") \(isSimulator ? "64位模拟器" : "64位真机")")
//打印日志:当前运行环境:手机系统 64位模拟器
@UIApplicationMain
当创建一个swift工程的时候,@UIApplicationMain会出现在Appdelegate.swift 的上方,它的作用就是创建main函数(程序入口),并设定appdelegate为委托,监听程序的生命周期。
现在做一件有意思的事情,删掉@UIApplicationMain,自己去创建main方法。
- 创建一个名为main.swift的文件, 这个文件中的代码将作为main函数来执行。在文件中写上以下代码,程序就可以正常运行了。
import UIKit
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
- 第三个参数是确定程序的类,, 如果传nil,就会默认使用UIApplication。如果我们想使用自己的呢?
- 创建一个继承自UIApplication的MyApplication类,并重写sendEvent:方法,你就会收到这个应用的任意事件了。
class MyApplication: UIApplication {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event);
print(event);
}
}
- 将MyApplication 设置到main函数中
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(MyApplication.self), NSStringFromClass(AppDelegate.self))
protocol组合
其实就是一个类同时实现了好久协议,但是还有一种匿名写法ProtocolA & ProtocolB,这样就组合成了一个新的协议只不过匿名的。不过也可以使用typealias 给一个组合定义一个新的名字。
- 对于组合协议有一个问题:在组合协议中有可能有重名的方法,在调用的时候怎么区分和调用呢?
示例:
protocol A {
func test() -> Int;
}
protocol B {
func test() -> String;
}
typealias C = A & B
class TestProtocol: C {
func test() -> Int {
print("test....A")
return 0;
}
func test() -> String {
print("test....B")
return "0";
}
}
let p = TestProtocol.init();
func testProtocolAsParameter(p :A & B) {
//如果不区分会默认调用最后一个定义的方法
(p as A).test();
(p as B).test();
}
testProtocolAsParameter(p: p);
/*打印日志:
test....A
test....B
*/
static 和 class
static 主要用于非类类型中,如struct enum,可以表示静态方法和静态属性。
class则只能用于类中,可以表示类型方法和计算型的类属性,并不支持存储型属性。
还有一个区别: 在类中可以使用static 但是在struct和enum中不能使用class。
在protocol中使用class修饰。
protocol 的可选方法
在swift中的protocol没有可选方法,定义的都必须实现,如果一定要这样,就在protocol前加@objc,并在可选方法之前添加@objc opational,但是使用@objc定义的protocol不能再struct或enum中实现了。
@objc protocol A {
func test() -> Int;
@objc optional
func testOpational();
}
weak 和 unowned
两个都是为了解决循环引用用的。 拿闭包说明,如果当前对象有可能会被释放,使用weak;如果对象一定不会释放苹果官方式推荐使用unowned
C 指针在swift中的运用
如果想在swift中使用C的指针,它的类型是UnsafePointer和UnsafeMutablePointer,也就是 const和 非const的区别。
swift中的添加C的一些基本类型:CInt CBool CFloat...
对于不知道具体类型的C指针 使用COpaquePointer
函数指针 CFunctionPointer
- 用到的时候再具体研究
Self的使用
接口其实本身是没有自己的上下文类型信息的,在声明接口的时候,我 们并不知道最后究竟会是什么样的类型来实现这个接口,Swift 中也不能在接口中定义 泛型进行限制。而在声明接口时,我们希望在接口中使用的类型就是实现这个接口本 身的类型的话,就需要使用 Self 进行指代。
protocol ProtocolCopySelf {
func copy() -> Self;
}
class Person: ProtocolCopySelf {
var age:Int?
func copy() -> Self {
//如果不使用type 就会报错
let p = type(of: self).init();
p.age = self.age;
return p;
}
required init() {
}
}
let p = Person.init();
p.age = 20;
let copyP = p.copy();
p.age = 10;