你应该知道的 Swift 实用建议

本文介绍了一些Swift中的高级特性,包括@noescape和@autoclosure的使用、内联lazy属性的简化、函数柯里化的应用、可变参数的便利性、dynamic关键字的作用以及一些特殊字面量和循环标签的妙用。

你应该知道的 Swift 实用建议



本文会详细介绍一些Swift中不为大多数人知,又很有用的知识点。您不必一次性看完,不过或许哪一天这些知识就能派上用场,项目Demo在我的github,您可以下载下来亲自实验一番,如果觉得有用还望点个star以示支持。


本文主要的知识点有:


  • @noescape和@autoclosure

  • 内联lazy属性

  • 函数柯里化

  • 可变参数

  • dynamic关键字

  • 一些特殊的字面量

  • 循环标签


@noescape和@autoclosure


关于这两个关键字的含义,在我此前的文章——第六章——函数(自动闭包和内存)中已经有详细的解释,这里就简单总结概括一下:


  • @noescape:这个关键字告诉编译器,参数闭包只能在函数内部使用。它不能被赋值给临时变量,不能异步调用,也不能作为未标记为@noescape的参数传递给其他函数。总之您可以放心,它无法在这个函数作用域之外使用。除了安全性上的保证,swift还会为标记为@noescape的参数做一些优化,闭包内访问类的成员时您还可以省去self.的语法。


  • @autoclosure:这个关键字将表达式封装成闭包,优点在于延迟了表达式的执行,缺点是如果滥用会导致代码可读性降低。


内联lazy属性


标记为lazy的属性在对象初始化时不会被创建,它直到第一次被访问时才会创建,通常情况下它是这样实现的:


class PersonOld {

    lazy var expensiveObject: ExpensiveObject = {

        return self.createExpensiveObject()    // 传统实现方式

    }()

 

    private func createExpensiveObject() -> ExpensiveObject {

        return ExpensiveObject()

    }

}


lazy属性本质上是一个闭包,闭包中的表达式只会调用一次。需要强调的是,虽然这个闭包中捕获了self,但是这样做并不会导致循环引用,猜测是Swift自动把self标记为unowned了。


这样的写法其实可以进行简化,简化后的实现如下:


class Person {

    lazy var expensiveObject: ExpensiveObject = self.createExpensiveObject()

 

    private func createExpensiveObject() -> ExpensiveObject {

        return ExpensiveObject()

    }

}


函数柯里化


函数柯里化也是一个老生常谈的问题了,我的这篇文章——第六章——函数(函数的便捷性)对其有比较详细的解释。


简单来说,柯里化函数处理一个参数,然后返回一个函数处理剩下来的所有参数。直观上来看,它避免了很多括号的嵌套,提高了代码的简洁性和可读性,比如这个函数:


func fourChainedFunctions(a: Int) -> (Int -> (Int -> (Int -> Int))) {

    return { b in

        return { c in

            return { d in

                return a + b + c + d

            }

        }

    }

}

fourChainedFunctions(1)(2)(3)(4)


对比一下它的柯里化版本:


func fourChainedFunctions(a: Int)(b: Int)(c: Int)(d: Int) -> Int {

    return a + b + c + d

}


不过在 Swift 3.0 中,这种柯里化语法会被移除,你需要使用之前完整的函数声明。感谢 @没故事的卓同学 指出。


您可以在Swift Programming Language Evolution中查看更多细节:


或者您也可以点击这篇文章查看更多细节——Removing currying func declaration syntax


可变参数


如果在参数类型后面加上三个”.”,表示参数的数量是可变的,如果您有过Java编程的经验,对此应该会比较熟悉:


func printEverythingWithAKrakenEmojiInBetween(objectsToPrint: Any...) {

    for object in objectsToPrint {

        print("\(object)")

    }

}

printEverythingWithAKrakenEmojiInBetween("Hey", "Look", "At", "Me", "!")


此时,参数可以当做SequenceType类型来使用,也就是说可以使用for in语法遍历其中的每一个参数。


可变参数并不是什么罕见的语法,比如print函数就是用了可变参数,更多详细的分析请移步:你其实真的不懂print(“Hello,world”)


dynamic关键字


如果您有过OC的开发经验,那一定会对OC中@dynamic关键字比较熟悉,它告诉编译器不要为属性合成getter和setter方法。


Swift中也有dynamic关键字,它可以用于修饰变量或函数,它的意思也与OC完全不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。


因此,标记为dynamic的变量/函数会隐式的加上@objc关键字,它会使用OC的runtime机制。


虽然静态分发在效率上可能更好,不过一些app分析统计的库需要依赖动态分发的特性,动态的添加一些统计代码,这一点在Swift的静态分发机制下很难完成。这种情况下,虽然使用dynamic关键字会牺牲因为使用静态分发而获得的一些性能优化,但也依然是值得的。


class Kraken {

    dynamic var imADynamicallyDispatchedString: String

 

    dynamic func imADynamicallyDispatchedFunction() {

        //Hooray for dynamic dispatch!

    }

}


使用动态分发,您可以更好的与OC中runtime的一些特性(如CoreData,KVC/KVO)进行交互,不过如果您不能确定变量或函数会被动态的修改、添加或使用了Method-Swizzle,那么就不应该使用dynamic关键字,否则有可能程序崩溃。


特殊的字面量


在开发或调试过程中如果能用好下面这四个字面量,将会起到事半功倍的效果:


  • __FILE__:当前代码在那个文件中

  • __FUNCTION__:当前代码在该文件的那个函数中

  • __LINE__:当前代码在该文件的第多少行

  • __COLUMN__:当前代码在改行的多少列


举个实际例子,您可以在demo中运行体验一番:


func specialLitertalExpression() {

    print(__FILE__)

    print(__FUNCTION__)

    print(__LINE__)

    print(__COLUMN__)   // 输出结果为11,因为有4个空格,print是五个字符,还有一个左括号。

}


一般情况下最常用的字面量是__FUNCTION__,它可以很容易让程序员明白自己调用的方法的方法名。


循环标签


通常意义上的循环标签主要是continue和break,不过swift在此基础上做了一些拓展,比如下面这段代码:


let firstNames = ["Neil","Kt","Bob"]

let lastNames = ["Zhou","Zhang","Wang","Li"]

for firstName in firstNames {

    var isFound = false

    for lastName in lastNames {

        if firstName == "Kt" && lastName == "Zhang" {

            isFound = true

            break

        }

        print(firstName + " " + lastName)

    }

 

    if isFound {

        break

    }

}


目的是希望找到分别在两个数组中找到字符串”Kt”和”Zhang”,在此之前会打印所有遍历到的字符。


在结束内层循环后,我希望外层循环也随之立刻停止,为了实现这个功能,我不得不引入了isFound参数。然而实际上我需要的只是可以指定停止哪个循环而已:


outsideloop: for firstName in firstNames {

    innerloop: for lastName in lastNames {

        if firstName == "Kt" && lastName == "Zhang" {

            break outsideloop    //人为指定break外层循环

        }

        print(firstName + " " + lastName)

    }

}


以上两段代码等价,可以看到使用了循环标签后,代码明显简洁了很多。


一、数据采集层:多源人脸数据获取 该层负责从不同设备 / 渠道采集人脸原始数据,为后续模型训练与识别提供基础样本,核心功能包括: 1. 多设备适配采集 实时摄像头采集: 调用计算机内置摄像头(或外接 USB 摄像头),通过OpenCV的VideoCapture接口实时捕获视频流,支持手动触发 “拍照”(按指定快捷键如Space)或自动定时采集(如每 2 秒采集 1 张),采集时自动框选人脸区域(通过Haar级联分类器初步定位),确保样本聚焦人脸。 支持采集参数配置:可设置采集分辨率(如 640×480、1280×720)、图像格式(JPG/PNG)、单用户采集数量(如默认采集 20 张,确保样本多样性),采集过程中实时显示 “已采集数量 / 目标数量”,避免样本不足。 本地图像 / 视频导入: 支持批量导入本地人脸图像文件(支持 JPG、PNG、BMP 格式),自动过滤非图像文件;导入视频文件(MP4、AVI 格式)时,可按 “固定帧间隔”(如每 10 帧提取 1 张图像)或 “手动选择帧” 提取人脸样本,适用于无实时摄像头场景。 数据集对接: 支持接入公开人脸数据集(如 LFW、ORL),通过预设脚本自动读取数据集目录结构(按 “用户 ID - 样本图像” 分类),快速构建训练样本库,无需手动采集,降低系统开发与测试成本。 2. 采集过程辅助功能 人脸有效性校验:采集时通过OpenCV的Haar级联分类器(或MTCNN轻量级模型)实时检测图像中是否包含人脸,若未检测到人脸(如遮挡、侧脸角度过大),则弹窗提示 “未识别到人脸,请调整姿态”,避免无效样本存入。 样本标签管理:采集时需为每个样本绑定 “用户标签”(如姓名、ID 号),支持手动输入标签或从 Excel 名单批量导入标签(按 “标签 - 采集数量” 对应),采集完成后自动按 “标签 - 序号” 命名文件(如 “张三
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值