阅读更多

0顶
0踩

编程语言

原创新闻 Swift编程的15个技巧

2016-03-03 10:42 by 副主编 mengyidan1988 评论(0) 有6017人浏览
相对于Objective-C,Swift是一种编译代码时速度更快、安全性与可靠性更高、同时具有可预测性的语言。下面我们列出了在实践中使用这种新语言时,所获取一些Swift使用技巧。这些技巧有助于让开发者编写出更干净的代码,并能帮助更熟悉Objective-C的程序员适应Swift编程,同时适用于在Swift上具有各种背景经历的人,请继续往下看。

章节的顺序是按照使用者对Swift的熟悉程度来排列的。第一部分是针对不太了解Swift的人,第二部分是针对初级入门者,而最后一部分是对于已在使用Swift的人。

你应当了解,但有可能不知道的Swift技巧
提高常数的可读性
在Swift中使用struct的简洁办法,就是在应用中制作一个适用所有常数的文件。由于Swift允许我们嵌用下面的结构,这种办法非常有用:
import Foundation
struct Constants {
    struct FoursquareApi {
        static let BaseUrl = "https://api.foursquare.com/v2/"
    }    
    struct TwitterApi {
        static let BaseUrl = "https://api.twitter.com/1.1/"
    }
    struct Configuration {
        static let UseWorkaround = true
    }    
}

嵌套让我们可以为常数生成一个命名空间(namespace)。例如:我们可以使用Constants.FoursquareApi.BaseUrl来访问Foursquare的BaseUrl常数,这样会使得数据可读性更高,并为相关的常数提供一系列封装。

为了提高性能,要避免NSObject与@objc
Swift允许我们将分类进行扩展,从NSObject到获取对象的Objective-Cruntime系统功能。还允许我们用@objc来注释Swift方法,以便在Objective-C runtime中使用。

支持Objective-C runtime,代表着系统不再通过通过静态或vtable分配,而是动态分配来调用方法。结果就是:在调用支持Objective-C运行的方法时,性能损失会高达四倍。在实际应用中,这种情况对性能的影响也许微不足道,不过这样一来,我们就知道通过Swift执行方法调用要比使用Objective-C快四倍。

在Swift中使用方法调配(Method Swizzling)
方法调配是替换一个已存在的方法实现。如果对此不熟悉,可以阅读这篇文章。Swift优化后,不再像Objective-C中那样,在runtime寻找方法的位置,而是直接调用内存地址。因此默认情况下,在Swift类中调配无法起效,除非:
  • 用动态关键字禁用这种优化。这是最佳选择,如果数据库完全以Swift构建的话,这种选择也是最合理的方式。
  • 扩展NSObject。如果单纯为了方法调配的话,不要用这种方式(而要采用动态的)。需要了解:在将NSObject作为基础类的已存在类中,方法调配是有效的,不过最好使用动态选择的方法。
  • 在要调配的方法中使用@objc注释。如果我们想要调配的方法同时也需要使用Objective-C的代码,那么这种方法是最合适的。

引用

更新:根据要求,我们增加了一个完全使用Swift的调用样例。在这个样例中仍需要Objective-C runtime,不过类并非继承自NSObject,方法也未标记成@objc。

import UIKit
class AwesomeClass {
    dynamic func originalFunction() -> String {
        return "originalFunction"
    }  
    dynamic func swizzledFunction() -> String {
        return "swizzledFunction"
    }
}
let awesomeObject = AwesomeClass()
print(awesomeObject.originalFunction()) // prints: "originalFunction"
let aClass = AwesomeClass.self
let originalMethod = class_getInstanceMethod(aClass, "originalFunction")
let swizzledMethod = class_getInstanceMethod(aClass, "swizzledFunction")
method_exchangeImplementations(originalMethod, swizzledMethod)
print(awesomeObject.originalFunction())  // prints: "swizzledFunction"

入门者所需的Swift技巧
清理异步代码
Swift在编写补齐函数(completion function)上语法非常简洁。在Objective-C中有completion block,不过出现的很晚,语法也有些粗糙,如下:
[self loginViaHttpWithRequest:request completionBlockWithSuccess:^(LoginOperation *operation, id responseObject) {
  [self showMainScreen];
} failure:^(LoginOperation *operation, NSError *error) {
  [self showFailedLogin];
}];

在Swift中有一种更简单的新型闭包语法。任何将闭包作为末尾参数的方法都可以使用Swift的新语法,让回调更简洁,如下:
loginViaHttp(request) { response in
  if response.success {
    showMainScreen()
  } else {
    showFailedLogin()
  }
}

控制对代码的访问
应该坚持用合适的访问控制修饰符(access control modifier)来封装代码。如果封装的好,无需记下思维过程,也无需询问代码编写者,就能理解这段代码是如何交互的。

Swift常见的访问控制机制有三种:私人访问、内部访问和公共访问。不过Swift中并没有常见于其它面向对象语言中的protected访问控制修饰符。为什么会这样呢?那是因为在子类中通过新的公共方法或属性,就可以显示protected方法或属性,因此实际上保护是无效的。而且由于从任何地方都能重写,因此protected并未给Swift编译器开启优化的机会。最后,由于protected阻止子类helper访问子类能够访问的信息,会让封装变差。想要了解Swift团队关于protected更多的想法,请点击这里查看。

实地实验与验证
Playground是苹果在2014年随Swift一起推出的一款交互式编程工具,可以用来测试及验证想法、学习Swift、与其他人分享概念。无需创建新项目,只需在运行Xcode的时候将playground选中就可以了。

也可以在Xcode中创建新的playground:



一旦有了playground,在编程时便能实时看到结果:



通过Playground可以将想法原型化,并以代码形式展示,同时还不会造成开启新项目的额外开销。



安全地使用可选值
可选值(optional)属性指的是这个属性或有效值或无值(为空)。通过可选值的名称+感叹号,格式为optionalProperty!,便可隐式解开一个可选值。 一般这是需要避免的,因为感叹号暗示着“危险”。

不过有些情况下,隐式解开可选值是可以接受的。比如IBOutlets就是默认将可选值隐式解开的(在Interface Builder中点击拖拽时),因为UIKit假定我们是将对象接口(outlet)与IB连接起来的。IBOutlets在初始化之后已经设置好了,因此接口是可选值的,同时根据Swift规则,在初始化之后所有非可选值的属性必须有值。另一个通过名称获得UIImage的案例是存在于我们的asset catalog之中的:
let imageViewSavvyNewYearsParty = UIImageView(image: UIImage(named: "Savvy2016.png")!)

将默认值设置为常量属性,在不隐式打开可选值的情况下是无法做到的。也就是说,!仍旧代表“危险!”但在这种情况下,是告知我们需要当心错误,并在运行前验证名称是否相符。一般来讲,假如我们必须使用空值,app就会有崩溃的风险。用!来隐式打开值会让编译器知道,我们已经知道在运行时可选值不会为空。在几乎所有场景之中,这都是带有赌 博性质的,因此最好使用if let模式来确定可选值是有有效值还是为空:
if let name = user.name {
    print(name)
} else {
    print("404 Name Not Found")
}

抛弃数字对象(NSNumber)
Objective-C使用C primitives来代表数字,用Foundation Objective-C API来提供数字对象类型,将primitives装箱拆箱。需要在primitives与对象类型之间切换时,代码会像 [array addObject:@(intPrimitive)]和[array[0] intValue]这样。Swift就不会有这种不当的机制。相对的,我们实际上可以向Swift字典和数组中添加Int / Float / AnyObject值。
下面是代替数字对象的一些Swift最常用的类型:
  • Swift: Objective-C
  • Int: [NSNumber integerValue]
  • UInt: [NSNumber unsignedIntegerValue]
  • Float: [NSNumber floatValue]
  • Bool: [NSNumber boolValue]
  • Double: [NSNumber doubleValue]

在用Objective-C编写的不同类型中,我们仍可以用数字对象来进行转换,不过在Swift中,转化值的常用方式是使用目标类型的构造函数。举个例子,如果我们从API中获得一个数字userID,将其在数字对象中打开并显示为字符串,在Objective-C中需要输入[userId stringValue]。而在Swift中数字对象不再使用(除非要向后兼容Objective-C),因为在Swift中,数字结构与在Objective-C中限制不同。
引用

注意:在使用Objective-C或依赖没有Swift封装的旧式代码库中,可能仍得使用数字对象。在这种情况下,数字对象API基本没什么变化。

相反,在Swift中我们通过构造函数进行等效转换。举个例子,如果userID是一个Int,而我们想要字符串的话,只需通过String(userId)进行转换。这比一直将数字对象装箱拆箱容易多了,不过数字对象所提供的各种各样的转换,确实让API简单易用。

通过默认参数减少样板文件代码
在Swift中,函数自变量现在可以有默认值了。这些默认的参数减少了杂乱程度。如果某函数的被调用者选择使用默认值,由于默认参数可以省略,这个函数调用就能更短一些了。例如:
unc printAlertWithMessage(message: String, title: String = "title") {
   print("Alert: \(title) | \(message)")
}
printAlertWithMessage("message") // prints: Alert: title | message
printAlertWithMessage("message", title: "non-default title") // prints: Alert: non-default title | messagex

为更熟练的使用者提供的一些Swift技巧
通过Guard来验证方法
Swift的guard语句让代码更简洁、更安全。guard语句会检查一到多个情况,找出不符合else部分的调用。而else部分需要return,break,continue或throw语句来终止方法的执行,也就是说终止程序控制的执行。

我们使用guard语句来减少代码混乱,并避免在if/else语句中的嵌入。由于在guard语句的else部分中,代码必须转移程序控制的范围,如果出现无效的情况,简单地采用if语句来调用return语句更为安全。在编译时这些bug仍有可能出现。如果guard语句的情况通过的话,在我们的范围中,解包后的可选值仍旧可用。
class ProjectManager {

    func increaseProductivityOfDeveloper(developer: Developer) {

        guard let developerName = developer.name else {
            print("Papers, please!")
            return
        }
        let slackMessage = SlackMessage(message: "\(developerName) is a great iOS Developer!")
        slackMessage.send()
    }
}

用Defer管理程序控制流
defer语句会推迟包含这个命令的代码执行,直到当前范围终止。也就是说,在defer语句中清理逻辑是可以替换的,而且只要离开相应的调用范围,这段命令就肯定就会被调用。这样可以减少冗余步骤,更重要的是增加安全性。
func deferExample() {
    defer {
        print("Leaving scope, time to cleanup!")
    }
    print("Performing some operation...")
}

// Prints:
// Performing some operation...
// Leaving scope, time to cleanup!

简化单例模式(Singleton)
在任何语言中对单例模式的使用都属于热议话题,不过它仍是大多数开发人员非常熟悉的模式。在Objective-C中,实现单例模式包括多个步骤,以便确保不会多次创建单例模式类。在Swift中这种使用有了大幅简化。下面我们会看到在Objective-C中实现单例模式的代码行数,是在Swift中实现单例模式代码的两倍。除此之外,由于使用了dispatch token,不仅可读性较差,也很难记住。

Objective-C:
@implementation MySingletonClass

    +(id)sharedInstance {
        static MySingletonClass *sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[self alloc] init];
        });
        return sharedInstance;
    }

Swift:
class MySingletonClass {
    static let sharedInstance = MySingletonClass()
    private init() {
    }
}

通过协议扩展减少重复的代码
在Objective-C中,我们通过分类来扩展已有的类型,不过这种做法对协议无效。Swift允许向协议中添加功能,使用Swift可以扩展单协议(甚至在标准数据库中的那些!),并将其应用在实现协议的类中。协议扩展足够将我们的整个编程范式从面向对象式改为面向协议式。这个概念的关键在于,我们默认通过协议来添加功能,而不是通过类,以便增加代码的可复用性。想要了解更多面向协议编程的知识,请查看这个视频,摘自WWDC 2015。

创建全局Helper函数
全局变量和函数经常被合称为“坏东西”,不过事实是两者都能让代码更干净,真正的坏东西是全局状态。全局函数经常需要全局状态来完成相关工作,因此很容易理解它们为什么会有这样的坏名声。下面是一些Grand Central Dispatch的helper函数样例,不是建立在全局状态之上,而且多少有些语法糖(指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用)的性质。下面我们会采用dispatch_after函数,用Swift的方式来解包:
import Foundation

/**
    Executes the closure on the main queue after a set amount of seconds.

    - parameter delay:   Delay in seconds
    - parameter closure: Code to execute after delay
*/
func delayOnMainQueue(delay: Double, closure: ()->()) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}

/**
    Executes the closure on a background queue after a set amount of seconds.

    - parameter delay:   Delay in seconds
    - parameter closure: Code to execute after delay
*/
func delayOnBackgroundQueue(delay: Double, closure: ()->()) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), closure)
}

下面是新解包的函数样例:
delayOnBackgroundQueue(5) {
    showView()
}

下面是未解包的函数样例:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
    showView()
}

用Swift语法来解包C函数,让我们的代码更易于一眼理解。找到你最喜欢的函数,试一下吧!只要在正确方法命名上尽责,将来程序的维护者肯定感激我们。如果我们将上面的方法签名修改为delay(delay: Double, closure: ()->()),这就成了不负责任的方法命名反例,因为dispatch_after需要GCD队列,而从名称中看不出来使用的哪个队列。然而,如果我们使用的代码库在主线程所有方法的执行上有既定规范,除非在名称或评论上另有指示,delay(delay: Double, closure: ()->())就可以是一个正确的方法名称。无论我们如何命名helper函数,它们都是为了通过包装样本代码节省时间,让代码更易读。

扩展集合性能
Swift增加了一些方法,帮助我们对集合进行简洁的查询和修改。这些集合方法受到了函数式语言的启发。我们使用集合将多个值保存到一个单独的数据结构中,通常我们也会查询和修改集合。这些函数是基于Swift的标准数据库构建,协助简化常见的任务。为了协助诠释下面这些函数,我们使用了这些样例:let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]。

  • 对集合中的每个值执行闭包映射(map),之后返回填充有映射值的映射结果类型数组。下面我们将Int数组转化为字符串数据:

let strings = ints.map { return String($0) }
print("strings: \(strings)") // prints: strings: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

  • 对数组中的每个值执行函数筛选(filter),返回Bool值。在结果数组中,只会返回true值,而不会返回false值。下面我们从ints数组中筛选奇数:

let evenInts = ints.filter { return ($0 % 2 == 0) }
print("evenInts: \(evenInts)") // prints: evenInts: [0, 2, 4, 6, 8]

  • reduce比map和filter更复杂,不过因为非常有用,花时间学习也是有价值的。第一个参数就是第一个reduce值(在下面的案例中为0)。第二个参数是访问之前reduce值和数组现值的函数。在本例中,我们的函数是将之前的函数值简单加到数组的现值中。

let reducedInts = ints.reduce(0, combine: +)
print("reducedInts: \(reducedInts)") // prints: reducedInts: 45

// defined another way: 

let reducedIntsAlt = ints.reduce(0) { (previousValue: Int, currentValue: Int) -> Int in
    return previousValue + currentValue
}
print("reducedIntsAlt: \(reducedIntsAlt)") // prints: reducedIntsAlt: 45

通过map,filter,reduce方面的技巧,就能减少筛选时和处理集合时的工作量,并增加可读性,方便以后的人维护。

结论
这份列表来自于我们团队的建议,收集了一些最常用的技巧,其中很多在整个代码库中都很常见。随着Swift这门编程语言的发展,像这样的技巧也在继续增加。我们希望能继续看到Swift的变化,并期待在应用中更多地使用这种语言。

英文来源:15 Tips to Become a Better Swift Developer
作者:NATHAN HILLYER,全栈开发者
翻译:孙薇
  • 大小: 122 KB
  • 大小: 112 KB
  • 大小: 41.7 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 关于开发在线网页编辑器按回车会输入<p>而不是<br>的解决方法

    其实以前早有很多人讨论过,代码比较多方法太可怕,自己用不到就没时间去研究,现在帮你试了一下,其实解决起来很简单了:)关于微软HTML编辑控件单击回车会插入&lt;p>而不是&lt;br>的解决方案 未经过脚本处理的编辑控件//*********************************//原作者:风云舞,原出处:http://www.lshdic.com/bbs//本程序需要IE浏览器版本高

  • 老版本富文本编辑器回车出现p嵌套br 问题

    解决方法: 01. 输入内容换行用shift+enter解决此问题 02.实例化配置页面 &lt;script type="text/javascript"&gt; //实例化编辑器 // 添加enterTag: 'br' var ue = UE.getEditor('editor', { enterTag: 'br' }); &lt;/script&gt; ...

  • 使用vue-quill-editor向后端传值时出现p标签的解决方案

    富文本编辑器发布文章的时候,所显示的内容会自动带一个p标签。 搜索大佬们的解决方案,发现我不会加,所以想了一个笨办法。 在向后端传输值之前,加上第一句 这样就可以去掉p标签了 ...

  • 前端富文本编辑react-quill的使用

    react-quill回显图片时替换图片的src

  • html编辑器 br 被div,去掉百度编辑器div被替换成p标签

    百度编辑器默认会把div替换成p标签,这样我们在编写一些自定义模板的时候就会改变我们的结构,造成一些问题。解决方法:在ueditor.all.js文件内搜索allowDivTransToP,找到如下的代码,将true设置为falseme.setOpt({'allowDivTransToP':false,'disabledTableInTable':true});然后在ueditor.config....

  • selenium自动化的输入和回车操作

    python selenium自动化的输入和回车操作

  • html,js div开启contenteditable属性可编辑时,按回车键会出现div,如何禁止生成

    div开启contenteditable属性时,当前div变成了一个可编辑的状态,按下回车键会生成一个div,如何禁止生成: 方案一:让改元素以下只显示文本就不会显示其他标签了 .read-write-plaintext-only { //纯文本 -webkit-user-modify: read-write-plaintext-only; } 方案二:通过jquery的事件,手动生成标签 $('#chat_editor[contenteditable]').keydown(functi

  • Vue基于Quill-Editor富文本实现去除HTML标签功能

    文章目录效果预览前端代码js提交时点击图标 效果预览 这里尝试过自定义富文本toolbar,不过没有用,时间成本上解决,使用了自定义的方法,简单易懂 前端代码 import { quillEditor } from "vue-quill-editor"; //调用编辑器 components: { quillEditor, }, &lt;div class="edit_contain...

  • 微信小程序 修改键盘的回车键及监听事件

    在input标签中有一个confirm-type 属性,即设置键盘右下角按钮的文字,仅在type=’text’时生效 示例: &amp;lt;input type=&quot;text&quot; placeholder=&quot;搜索&quot; confirm-type=&quot;search&quot;/&amp;gt; confirm-type 有效值: send 右下角按钮为“发送” search 右下角按钮为“搜索” next ...

  • Ueditor富文本编辑器替换去除自动追加p标签

    最近,做了一个模块,需要用到ueditor,然后突然发现其中的很多细节需要处理。 在项目中,一个文章修改模块用到ueditor, 但是每次修改完文章点击保存后,ueditor就会自动加一个p标签, 多次修改后,ueditor就会不断的在外层套一个p标签,以至于明明没有修改,但是它却不断的换行。

  • Ueditor指定用br做换行标签

    Ueditor富文本框中对每段换行文字都会自动追加标签进行包裹,看到的换行效果其实是由ueditor中标签的样式实现的,在实际使用时这可能带来问题。我们可以指定用br换行,整段文字只有最外层有标签包裹,换行则是,处理时只需抹除最外层的标签即可 var editor = UE.getEditor('editor',{         'enterTag' : 'br' });

  • 百度编辑器Ueditor回车换行自动加p换行新解决方案

    Ueditor回车换行自动加p是个巨坑无论怎么修改,回车始终会给套个P标签,严重影响排版美观,下面就这个问题,说说我自己的经验 工具/原料 Ueditor编辑器源码 txt文本编辑器或notepad++ 方法/步骤 复制备份ueditor编辑器文件夹里面的ueditor.all.js 重要! 打开编辑器文件夹里面的ueditor.all.js 用txt文本编辑器打开 ctrl+h查找替换,查找’p’替换为’div’ 关闭保存,清空浏览器缓存,重启浏览器打开ueditor编辑器。 经

  • 关于使用vue-quill-editor出现p标签的解决方案

    最近在写自己的个人博客项目,由于需要发表文章,因此需要引入富文本编辑器,关于相关的导入细节网上有很多。 这次主要说明使用vue-quill-editor的时候出现的一个“bug”,就是在发布文章的时候,所显示的内容会自动带上一个p标签。 可以看下图 我看网上很多文章关于这个去除p标签的解决方案都很复杂,有的甚至还提到了vue-quill-editor的源码,有点让人头大。 那么既然学过vue...

Global site tag (gtag.js) - Google Analytics