Swift开发你可能没用过的Tips

主要针对于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方法。

  1. 创建一个名为main.swift的文件, 这个文件中的代码将作为main函数来执行。在文件中写上以下代码,程序就可以正常运行了。
import UIKit
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
  1. 第三个参数是确定程序的类,, 如果传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;

持续更新笔记

你可能感兴趣的:(Swift开发你可能没用过的Tips)