• Swift3.0要注意的地方

    2017-01-04 10:29:44
    都知道苹果要在下个版本的Xcode中移除Swift2.3的支持,强制开发者使用Swift3.0,这是一个很悲痛的现实��。然而正好公司的项目是OC和Swift混编的项目,里面用到了一个第三方库SwiftBond,当时SwiftBond还没有升级...

    都知道苹果要在下个版本的Xcode中移除Swift2.3的支持,强制开发者使用Swift3.0,这是一个很悲痛的现实��。然而正好公司的项目是OC和Swift混编的项目,里面用到了一个第三方库SwiftBond,当时SwiftBond还没有升级Swift3.0,老大害怕是个坑,所以就让我使用RxSwift去替换掉这个库,然而正当我要动手的时候,突然发现我要把项目升级到Swift3.0啊,不然换了RxSwift没有卵用啊!!����


    beishang.jpeg

    让我45度角仰望星空,我的悲伤逆流成河!

    没办法谁让苹果逼的紧呢,正好也能提升一下自己Swift的水平,所以就开干了,没想到在这个过程中我的悲伤却逆流成海了��。

    我发现原来项目中使用的Swift写的代码简直不能瞅,像我这种对代码洁癖的很多地方都进行了重写。并且原来的Swift代码也并没有按照Swift文件中的标准来写,导致里面坑巨多,使用Convert转换以后,每个Swift文件中几乎都是一二百个错误,我只能一个一个手动去改,而且还遇到了非常难以发现的巨坑,不过到头来我还是成功地把项目迁移到了Swift3.0,并且把SwiftBond替换为了RxSwift��。由于这个过程中坑非常多,特此总结下来,以免大伙遇到此坑以后无从下手。

    去除@objc

    项目中很多地方都使用了@objcdynamic关键字修饰,例如:

    @objc var clockInShare: Int = 0
    @objc dynamic func setupModels() { ... }

    将所有的继承了NSObject的里面的非private方法和属性前的@objcdynamic关键字去掉,因为继承了NSObject的类,Swift会默认在前面添加@objc关键字,而dynamic关键字一般使用KVO等动态特性的时候才用的到。

    使用extensnion进行归类

    有些文件中的类在一个大括号{}中包含了全部的属性和方法,或者还是和OC写法一样,一下继承了UITableViewDataSourceUITableViewDelegate,在里面使用了//MARK: 进行分类,但我感觉这种写法太乱了。所以将他们全部使用extension进行分类,这样子更符合Swift语言的优美风格

    // MARK: - Actions   
    @objc dynamic func bi_UnselectedWord() {
    }

    更改为:

    extension CCSequenceExerciseViewController {
        func bi_UnselectedWord() {}
    }
    extension CCSequenceExerciseViewController: UITableViewDataSource, UITableViewDelegate { ... }

    更改为:

    extension CCSequenceExerciseViewController: UITableViewDataSource { ... }
    extension CCSequenceExerciseViewController: UITableViewDelegate { ... }

    使用extension分类的时候也有一个改变,原来类中使用的private关键字,Swift2中extension中是可以访问这个private属性,但是到了Swift3.0中private属性作用域变为了{}之间,所以extension就不能访问了。苹果又新添加了一个关键字为fileprivate表示只能在这个文件中被访问,换成这个关键字就可以了

    闭包更改

    原来Swift2.3中闭包的声明是这样子写的:

    typealias Command = ()->()
    var buttonCommand = Command?()

    Swift3.0编译会提示修改,更改为如下:

    typealias Command = ()->()
    var buttonCommand: Command?

    去除Swift文件中的NS前缀的类

    Swift3.0中把大量的带有NS的类型去掉了NS前缀,与OC交互的时候,Swift调用OC方法中的返回值会默认为Swift中类型,也就是说默认把类类型转换为了Swift中的值类型,比如OC方法返回NSArray那么Swift中会默认为Array,我简单测试了几个常用的返回类型,如下:

    OC Swift
    NSArray Array
    NSDictionary Dictionary
    NSString String
    id Any
    NSDate Date
    NSNumber NSNumer
    NSInteger Int

    可以看到原来OC中的id对应Swift中的AnyObject,现在更改为对应Swift中的Any类型,灵活性更高了,这个要注意。

    OC中的NSNumbe仍然对应Swift中的NSNumber(使用NSNumber会有一个大坑,后面会说到)。

    发现我们项目中的Swift文件中使用了很多的NSArray,NSDictionary,NSString,NSDate,这可能是历史的原因吧。因为Swift建议尽量使用Swift中内置的一些类型,并且Swift3.0已经默认转为不带NS前缀的类型了,虽然项目使用NS前缀的也能运行,但是我对代码有洁癖,把所有使用到NS的地方全部重写了,换成了不带NS前缀的Swift类型。

    比如:

    let cloudTime = NSDate().dateByAddingTimeInterval(NSUserDefaults.standardUserDefaults().cc_TimeDiffToServer)

    更改为

    let cloudTime = Date().addingTimeInterval(UserDefaults.standard.cc_TimeDiffToServer)

    再比如:

    @objc dynamic func getSavedCheckInInfo() -> NSDictionary{
         .....
        return CCDataDownHelper.fetchDataWithKey(key) as! NSDictionary
    }

    更改为

    func getSavedCheckInInfo() -> Dictionary<String, AnyObject>? {
        .....
        return (checkInInfo as? Dictionary<String, AnyObject>)
     }

    不带NS前缀的类型没有某个方法

    注意有时候Swift内置类型并没有包含带有NS前缀类型里面的所有方法,如果我们使用Swift类型调用这些方法,会提示没有这个方法,细心的你会发现这个方法是带有NS前缀的类型才有的方法,所以我们必须将Swift类型转换为NS前缀的类型才能调用此方法。
    例如:

    let userDic = ["ttf": "123"]
    userDic.write(toFile: filePath, atomically: true)

    这时候会报一个错误:value of type [String: String] has no member write,意思就是没有这个方法,这时候我们就需要将他转为带有NS前缀的类型了

    let userDic = ["ttf": "123"]
    (userDic as NSDictionary).write(toFile: filePath, atomically: true)

    但还是要注意在Swift文件中尽最大可能滴使用Swift的数据类型。

    可选值的使用

    因为Swift的出现,OC中也添加了几个关键字nullable, nonnull等关键字来修饰参数和返回值。OC文件中的返回值如果不包含这几个关键字,Swift调用OC的方法默认的返回值类型是一个optional类型,如果你添加了nonnull关键字来修饰,Swift中得到的值就是一个非optional的普通值。

    然而我们项目中原来的OC方法的返回值都是不包含任何关键字的,所以Swift去使用OC的时候就很蛋疼了,每个返回值都要去处理一下。而且我看到原来文件里面有这样去处理这个值的:

    let bgcfg = CCBgcfgService()
    let copywriterMode = bgcfg.inquireDataWithType(.Copywriter, subType:.CopywriteCheckInShare)
    var array = NSArray()
    if copywriterMode != nil {
        array = copywriterMode.valueForKey("texts") as! NSArray
    }

    看到这里我又默默地重写了整个Swift的文件,这里copywriterMode是OC方法返回的一个可选值,不应该使用OC里面的处理方式这个optional值。更改为:

    let copyWriterMode = bgcfg.inquireData(with: .Copywriter, subType:.CopywriteCheckInShare)
    
    var array: Array<AnyObject>? = nil
    if let writeMode = copyWriterMode as? CCBackgroundCfgCopywriterModel {
        array = writeMode.value(forKey: "texts") as? Array<AnyObject>
    }

    最好使用可选绑定,或者使用guard let来处理optional的值,项目中有很多这样的地方全部让我重写了��,想想都累。

    下面这个是处理服务器端返回的值

    let obj:AnyObject = response.originalContent
    if !(obj is NSDictionary) {
        failure(reason: "")
        return;
    }
    success(dic: (obj as! NSDictionary))

    更改为:

    guard let response = response else { return }
    let obj = response.originalContent as? Dictionary<String, AnyObject>
    if let obj = obj {
        success(obj)
    } else {
        failure("")
    }

    注意:如果你写OC方法一定要加上nullable, nonnull等关键字修饰,Swift中处理optional值的时候尽量去选择使用可选绑定或者guard let

    巨坑一 NSNumber

    其实更改Swift3.0,我搞了两遍,第一遍手动把编译错误全部消除掉以后,发现木有错误了,我小心翼翼地按下了common+B,编译的正爽的时候,突然一个红色感叹号出来了,一个错误编译错误
    Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code
    但是每个页面都确实没有错误啊?而且没有任何错误提示,也实在找不到任何有用的错误信息。


    error.png

    后来搞了好久,实在没有办法,就搞了一个笨办法,重搞项目,把所有的Swift写的模块全部移除,一个模块一个模块的添加,一个模块一个模块的迁移Swift3.0,保证每个模块编译通过以后添加下一个模块,后来添加了一个swift文件,编译又报了这个错误,我就在这个文件中一行一行的注释,最终发现了问题的所在:

    let attributeTitle = NSAttributedString(string: "PK", attributes: [NSBaselineOffsetAttributeName : NSNumber(int: -1)])

    就是因为这个NSNumber的使用导致这个Swift编译器的错误,而且页面也不报错,不知道是不是Swift编译器的bug还是其他原因,有知道的小伙伴可以留言告诉我一下。

    更改为:

    let attributeTitle = NSAttributedString(string: "PK", attributes: [NSBaselineOffsetAttributeName : -1.0])

    说实话这个坑实在是太难找,后来又添加一个Swift文件,又出现这个问题,我就直接搜NSNumber,果然是有,把NSNmber去掉以后,编译通过。如果有小伙伴也遇到这个错误,可以尝试搜下NSNumber更换,错误应该会解决。

    巨坑二 重写OC方法

    我们项目中有几个使用Swift写的Interceptor,他继承一个OC的协议,并且重写了OC的方法,每打开一个页面都是去执行每个拦截器文件中的方法,但是我把项目升级到Swift3.0以后,这几个Swift写的拦截器就再也没有执行过,对比了好多遍重写的方法确实和OC定义的一模一样啊?页面上也没有任何报错,项目也可以编译成功啊?

    后来实在搞不懂了就去请教了公司的一位大神,才明白因为Swift3.0的API大变,Swift去重写OC方法的时候,其实并不是要去重写OC声明的方法,而是要去重写OC转换为Swift所声明的方法。例如一个OC协议是这样

    @protocol NavigatorInterceptor <NSObject>
    @optional
    - (void)interceptOpenWithContext:(HJNavigatorInterceptorContext *)context;
    @end

    Swift文件继承这个协议不能去直接实现这个方法

    extension StrangeWordBookNavigatorInterceptor: NavigatorInterceptor {
        func interceptOpenWithContext(context: HJNavigatorInterceptorContext!) { }
    }

    在Swift2.3中这样实现是可以的,但是到了Swift3中,这样子实现就错误了。永远都不会执行这段代码。重写OC方法的时候首先要看OC方法生成的Swift方法是什么样


    swift.png

    可以看到转换成Swift对应的文件中声明的方法是和原来的不一样的,我们应该实现Swift中对应的方法。

    extension CCStrangeWordBookNavigatorInterceptor: HJNavigatorInterceptor {
        func interceptOpen(with context: HJNavigatorInterceptorContext!) {}
    }

    这样子程序就正常运行了,每一个使用Swift所写的拦截器都会走了。

    另外提醒大伙一句:从这个坑可以知道,以后我们使用Swift调用OC的方法的时候都要先去看看OC生成对应Swift版本的方法是什么样子,这样子才能保证程序的稳定,虽然我测试的Swift直接调用OC类型的方法暂时不会有啥问题,但最好还是改为Swift的。我就花了很多时候将项目中Swift调用OC的方法全部改为对应Swift的版本了。

    巨坑三 介词

    Swift3.0将方法中的介词都转移到了括号里面。比如:

    • UIFont.systemFontOfSize(14)改为UIFont.systemFont(ofSize: 14)
    • writeToFile()改为write(toFile:)
    • initWithName(name: String)改为init(with name: String)
    • NSJSONSerialization.dataWithJSONObject(JSONArray, options:)改为JSONSerialization.data(withJSONObject: JSONArray as Any, options:)

    反正只要有介词的方法都做了改变,包括OC方法的Swift版本,完全不一样了,这就是为什么调用或者重写OC方法的时候一定要去看一下他所对应的Swift版本。

    最坑的就是如果你Swift中有些地方还是原来的介词在外面的写法,但是Xcode并不给错误提示,编译也可以通过,但是你运行程序走到那个地方程序直接就crash了,真是无语,例如下面这个地方就一直crash但没有错误提示

    let s = subjects.removeAtIndex(index)
    
    if s.subjectType.rawValue == 9 {
        s.options = s.options.lowercaseString
        s.answerOption = s.answerOption.lowercaseString
    }
    
    self.subjects.append(s)
    s.index = self.subjects.indexOf(s)!

    更改为:

    let s = subjects.remove(at: index)
    
    if s.subjectType.rawValue == 9 {
        s.options = s.options.lowercased()
        s.answerOption = s.answerOption.lowercased()
    }
    
    self.subjects.append(s)
    s.index = self.subjects.index(of: s)!

    剩下的大部分更改也只是语法问题,如果你的Swift项目是按照Swift语言标准来写的,那么你Convert到Swift3.0非常轻松,几乎没有什么错误,有的话也只是一点小小的语法问题,就像我们项目中的watch版本完全纯Swift写的,一键convert swift3.0 一点错误都没有,直接运行。

    总结


    1. 尽量按照Swift推荐规范写代码
    2. 多使用extension进行分类
    3. 使用Swift的内置类型
    4. 避免使用NSNumber
    5. 尽量使用Swift调用OC而不是OC调用Swift
    6. 调用OC方法的时候要注意他对应的Swift版本的方法
    7. OC接口添加nullable,nonnull等关键字修饰
    8. 使用可选绑定或者guard处理optional

    展开全文
  • 如果你正为移动设备开发应用程序,并且你还没有研究Swift,那么注意:当Swift涉及到Mac、iPhone、ipad、Apple Watch和未来设备的应用开发时,它不仅会排挤掉Objective-C,而且还会取代在Apple平台中做嵌入式开发的...

    这里写图片描述
    是时候使用易入手又全面的Swif语言为iOS和mac OS X做应用开发了。
    虽然编程语言不会那么容易消逝,但坚持衰落范例的开发小组正在这么做。如果你正为移动设备开发应用程序,并且你还没有研究Swift,那么注意:当Swift涉及到Mac、iPhone、ipad、Apple Watch和未来设备的应用开发时,它不仅会排挤掉Objective-C,而且还会取代在Apple平台中做嵌入式开发的C语言。
    由于几个关键特性,在未来几年,Swift有很大潜力成为创造身临其境的、响应迅速的、面向用户的应用程序的实际编程语言。

    苹果公司似乎在Swift上还有更大的目标。它的编译器性能和开发语言都被优化了,苹果公司在Swift的文档中暗示Swift被设计成小能(显示)“hello,world”,大能(完成)整个操作系统。苹果公司还没把这门语言的目标说全,Xcode6,Playgrounds和Swift的推出就一起揭露苹果的意图:更简单的应用开发,更易用的开发工具链。
    这是从现在起使用Swift工作,并走在比赛前列的10个原因。

    1. Swift 容易阅读

     如你所能预计到的一门基于 C 构建的语言,Objective-C 身上所有的毒疣子都有。为了将关键词和类型同C的类型作区分,Objective-C 使用@符号引入了新的关键词。因为 Swift 不是基于C构建的,它同意了所有的关键词,并将 Objective-C 类型和对象相关的关键词前面大量的@符号移除了.
     Swift 丢弃了遗留下来的约定。因而你不再需要行尾的分号,以及 if/else 语句中围绕条件表达式的括弧。另外一个大变化就是方法的调用不再互相嵌套成中括号的深坑 -- 再见吧,[[[ ]]]。Swift 中的方法和函数的调用使用行业内标准的在一对括弧内使用逗号分隔的参数列表。这样做的结果就是一种带有简化了句法和语法的更加干净有表现力的语言。
    除了其它当代流行的编程语言之外,Swift 更像是自然的英语了。这种可读性是的其很容易能被其它来自 JavaScript,Java,Python,C#,以及 C++ 的开发者纳入到他们的工具链之中 -- 一点也不像 Objective-C 这只笨笨的黄小鸭。
    

    2. Swift 更易于维护

    历史遗留问题会让 Objective-C 越来越倒退 -- C 没有演进的话,这个语言也就跟着无法进行演进。C 需要程序员维护两套代码文件,以优化构建的时间以及创建可执行 app 的效率, 这种需要延续到了 Objective-C 上。
    Swift 丢掉了对着俩文件的要求。Swift1.2 中 Xcode 和 LLVM 编译器可以自动计算出以来并执行增量构建。如此,将内容清单 (头文件) 同内容主体(实现文件)相分离。Swift 将 Objective-C 头文件(.h) 和实现文件 (.m) 合并成了一个代码文件 (.swift)。
    
    Objective-C 的两份文件系统存在强加给程序员的额外工作 -- 而这些工作会让程序员难免分心而不能顾全大局. 在 Objective-C 中你不得不手动去同步文件之间的方法名称和注释, 有时候要寄希望于一个约定好的标准,不过除非团队的规矩和代码审查制度到位,否则这是不会为你提供什么保障的.    
    Xcode 和 LLVM 编译器可以在幕后做一些工作来减轻程序员的工作负担. 使用 Swift, 程序员可以少做些费脑力的记忆性工作,从而能在创建app逻辑的工作上面赢得更多的时间. Swift 为我们程序员裁掉了那些样板式的工作,同时对代码、注释以及所要支持的特性的质量都有所提升.
    

    3. Swift 更加安全

     Objective-C 有意思的一个方面是指针 -- 特别是 nil (null) 指针 -- 它们被处理的方式. 在Objective中-C, 如果你调用方法的是一个值为 nil (未初始化)的指针变量,什么事情都会不发生. 表达式或者一行操作变成了一项空操作(no-operation (no-op)), 而这就使得其看起来会有不会奔溃的好处, 但其实它已经变成了一个巨大的bug来源. no-op 会导致不可预测的行为, 这是程序员在尝试找出并修复某种随机的奔溃,或者要停止反常的行为时所要面对的敌人.
    
    在Swift代码中的可选类型使得一个nil可选值的可能性变得非常的明确, 这意味它能在你写下一段糟糕的代码时会生成一个编译器错误. 这就建立了一种短程反馈的循环,可以让程序员带着目标去写代码. 问题在代码被写就时就可以被修复, 这大大节省了你要在修复有关来自 Objective-C 指针逻辑的bug时需要耗费的时间和金钱.
    
    在 Objective-C 的传统中, 如果某个值返回自一个方法, (使用注释以及方法的命名约定来)说明指针变量被返回的行为是程序员的责任.在 Swift 中, 可选类型和值类型使得方法定义中值是否存在,或者其有可能是可选的(即值可能存在也可能为nil),这些问题都是很明确清楚的. 
    
    为了提供对行为的预测,Swift会在nil可选值被使用时触发一次运行时崩溃。 崩溃提供的就是一种一致的行为,它能减轻修复bug过程的压力,因为它会直白地强制让程序员修复好这个问题. Swift 运行时崩溃的时候会停在nil可选值被使用到的那行代码处。这就意味着bug能更早的被修复,并能在Swift代码中被完全的规避掉.
    

    4. Swift 的内存管理是统一化的

    Swift 以一种 Objective-C 从未有过的方式进行了统一。对自动引用计数 (ARC) 的支持是在整个过程化的和面向对象的代码路径上完成的。在。Objective-C。中, ARC 在 Cocoa API 和面向对象代码中获得支持;然而它并不支持过程式的 C 语言代码和像 Core Graphics 这样的 API。这意味着在使用 Core Graphics API 以及其它 iOS 上的底层 API 时,内存管控的处理都是程序员的责任。程序员在 Objective-C 上会遇到的大量内存溢出问题在 Swift 上是不可能的。
    
    程序员不应该为他或她创建的数字对象去考虑内存的问题。因为 ARC 在编译时就处理了所有的内存管理事务, 内存管理所有消耗的脑力现在可以被用来专注于核心的应用逻辑以及新的功能特性。因为 Swift 中的 ARC 在过程式的和面向对象的代码中都能起作用,它也就不再需要程序员进行心理上的上下文切换了, 即使是他们在编写要触及底层API的代码时也不需要 -- 这在目前版本的 Objective-C 中就是一个实实在在的问题。
    
    自动和高性能的内存管理是一个已经被解决的问题,而苹果公司已经证明了这个问题的解决可以提高生产力. 另外一个副作用就是 Objective-C 和 Swift 不会像 Java,Go 或者 C# 那样遇到垃圾收集器针对没有被使用的内存运行清理作业的情况. 这对于那些将会被用于相应图形和用户输入的编程语言而言就是一个非常重要的要素, 特别是在诸如 iPhone、Apple Watch 以及 iPad 这样的(如果响应滞后就会让用户感知上以为应用是坏的)触摸屏设备上。
    

    5. Swift 代码更少

    Swift 减少了重复性语句和字符串操作所需要的代码量。在 Objective-C 中, 使用文本字符串将两块信息组合起来的操作非常繁琐。Swift 采用当代编程语言的特性,比如使用“+”操作符将两个字符串加到一起,这在 Objective-C 中是没有。像这样支持对字符和字串的组合对于任何要在屏幕上向用户展示文本的编程语言的基础设施。
    
    Swift中的类型系统减少了代码语句的复杂性--作为编译器可以理解的类型。比如,Objective-C要求程序员记住特殊字符标记(%s,%d,%@)并且提供了一个用逗号分隔的变量来代替每个标记。Swift支持字符串插入,这就消除了需要记住的标记和允许程序员直接插入变量到面向用户的字符串中,比如标签或者按钮的标题。这类推理系统和字符串插入减少错误来源在Objective-C中都是很常见的。
    
    在Objective—C中,搞乱了顺序或者使用了错误字符串标记会造成app崩溃。这里,Swift再次将你从反锁的工作中解放出来,翻译成更少要编写的代码(代码现在已经不容易出错)因为它的对处理文本字符串和数据的内嵌支持。
    

    6. Swift 更快

     删除遗留下来的C语言约定大大提升了引擎盖之下Swift的性能. Swift代码性能的基准测试一直都瞄向苹果公司所致力于的Swift运行app逻辑的速度提升.
    
    根据灵长类动物研究所(Primate Lab)——时下流行的 GeekBench 性能工具的创造者——的调查, 2014年12月中使用曼德尔布罗算法(Mandelbrot algorithm)进行计算密集型任务的性能上,Swift已经逼近C++的表现了.
    
    在2015年2月,灵长类动物研究所发现 Xcode 6.3 测试版提升了Swift在GEMM算法上的性能 -- 这是一种受制于内存限制的算法,需要对大型数组进行顺序访问 -- 有1.4倍. 初始的FFT实现 -- 这是一种会对大型数组进行随机访问的受限于内存的算法 -- 拥有2.6倍的性能提升.
    
    通过应用最佳实践,可以观察到更进一步的性能提升, 结果是 FFT 算法上8.5倍的性能 (差上 C++ 1.1倍)。这些改进也使得 Swift 在曼德尔布罗算法上实际超越了 C++ 1.03 倍。
    
    Swift 在 FFT 和曼德尔布罗算法上几乎能与 C++ 比肩。根据 Primate Labs 的研究发现,GEMM 算法的性能表现说明 Swift 编译器还不能实现 C++ 编译器支持的矢量代码 -- 所以 Swift 的下一个版本可能会比较容易的获得一次性能提升。
    

    7. 开源项目中更少的名称冲突

    Objective-C 代码中一直令人很困扰的问题就是缺乏对命名空间的正式支持, 它是 C++ 处理文件名冲突的解决方案。当名称冲突发生在 Objective-C 中时,就会是一个连接器错误,会导致 app 无法运行。解决的办法倒是有,可它们都有潜在的隐患。一般的约定是使用两到三个字母前缀来区分编写的 Objective-C 代码, 比方说,通过 Facebook 来展现出你自己的代码。
    
    Swift 提供了隐含的命名空间,允许相同的代码文件存在于多个项目,而不会造成构建失败,或者需要向 NSString (Next Step -- Steve Jobs 被 Apple 炒鱿鱼之后创建的公司) 或者 CGPoint (Core Graphics)这样的名称。最终,Swift 中的这一特性使得开发者更加的有生产力,并且也意味着他们没必要再做 Objective-C 需要的备忘式记忆工作。在简单如 Array,Dictionary 以及 String 这样的名字中你可以看到 Swift 的影响力,而不是脱胎于缺少命名空间的 Objective-C 中的 NSArray、NSDictionary 以及 NSString。
    
    Swift 的命名空间是基于一份代码文件所属的目标位置。这就意味可以使用命名空间标识来区分出不同的类和值。Swift 中的这个改变很大,它极大的方便了将开发员项目、框架以及库集成到你代码中来的操作。命名空间使得在集成开源项目时,不用担心来自不同软件公司的同名代码文件会发生冲突。现在 Facebook 和苹果公司可以同时使用一个叫做 FlyingCar.swift 的对象代码文件,不会有任何错误或者失败。
    

    8. Swift 支持动态库

    Swift 中没有受到足够重视的一个最大的问题是静态库向动态库的切换,其在主要发布版(iOS8,iOS7等等)会被更新。动态库是可以被链接到 app 的可执行代码块。这一特性可以让现有的 Swift 应用可以链接到随着时间推移所产生的更新版本的 Swift 语言。
    
    开发者将 app 连同库文件一并提交,它们都用开发者证书打上了数字签名,以确保完整性 (你好, NSA)。这就意味着 Swift 可以比 iOS 更快地进化,对于一种现代编程语言而言这是必要的。对库文件的修改可以被全部引入 AppStore 上某个 app 的最新更新中,一起运行起来都如此简单.
    
    动态库在 iOS 上从未受到支持,直到 Swift 和 iOS 8 的发布,尽管已经在 Mac 获得支持很久了。动态库处在应用可执行文件之外,不过会被包含在从 AppStore 上下载的应用包中。它减小了 app 被加载到内存中的初始大小,因为外部代码只在被用到时才会被链接进来。
    
    移动应用程序或者是 Apple Watch 上的嵌入式应用所具有的延迟加载能力,将提升应用面向用户的感知性能。这是使得 iOS 生态系统更具感官上的响应性的区别之一。苹果公司原先只专注于运行时加载资料和资源,现在代码的编译和链接也可以在运行时进行。运行时加载减少的等待时间,直到资源需要被用于展示在屏幕上时,才会被加载进来。
    
    Swift 中的动态库让编程语言的修改升级比以往更快的传播出去成为了可能。用户不在需要等待指定的iOS 版本发布才能享受到 Apple 引入 Swift 中的性能和可靠性改进.
    

    9. Swift Playgrounds 鼓励交互式编码

    Swift 新引入的 Playgrounds 是有经验的开发者的福音。Playgrounds 的灵感来自于苹果公司前雇员 Brett Victor 的工作。Playgrounds 可以让程序员用比如说5到20行代码来测试一种新的算法或者图形程序,不用去创建一个完整的 iPhone 应用。
    
    苹果公司已经将内联代码执行操作加入到了 Playgrounds 中,以帮助程序员创建代码块或者编写某种算法时获得反馈。这样的反馈循环可以提升代码编写的速度,因为传统程序员所需要的心智模型已经为 Playground 的数据可视化形式所替代。编程是一个反复的过程,任何可能压力上的减轻或者创造的补充都会使得开发者更具生产力,并能释放出他们的精力来解决更大的问题,而不是死盯着传统编译器来增加程序员的繁琐细节。
    
    注意:据我教授新手程序员的经验,Playgrounds 对于入门者而言不会像对有经验的程序员那么有用。如果只是简单的展示 Swift 中一个变量是如何工作的,Playggrounds 显然不能对帮助新手理解对于一个浮点指针变量与一个整型变量的需要。当你要展示一个能记忆你最后在 Facebook 新闻提要中的滚动位置时,这种需要才会变得明显。对于新手而言,“为什么”这个问题只能用一个可以运行示例:也就是一个 iPhone 应用,来回答。
    

    10. Swift 是一个你可以影响的未来

    Objective-C 没有任何出路,你将不会看到它发生改变,我们要感谢 Swift 的引入. 一些 Swift 特性可能会集成到 Objective-C, 但 Objective-C 的 C 语言遗留物还是注定了它只能吸收那么点 Swift 的好东西。
    
    Swift 向开发者社区提供了一个直接的方式,去影响一门语言,它将会被用于应用的创建,嵌入式系统(如果苹果公司向第三方的嵌入式框架和芯片进行了授权)以及像 Apple Watch 这样的设备.
    
    苹果公司专注于提供最佳的消费者体验,而且只构建值得注意的功能特性. 随着 Xcode6.3 中 Swfit1.2 的发布,苹果公司已经利用流行的 Apple Bug Reporter 工具修复了数以千计的 bug。支撑 Swfit 开发和改进的团队对于如何提升语言,以更好的支持那些使用 Swift 构建应用和系统的开发社区很感兴趣。
    
    Swift: 更易上手,特性丰富的语言
    
    从丢弃 Objective-C 赖以构建的传统语言开始,一大堆变化让 Swift 超越了 Objective-C。Apple 并没有丢弃 Cocoa—— 这是他们的用于创建苹果风格体验的 API 和代码库——而是提供了一个完整功能的等价物,使得同支持 Force Touch 或者 Taptic Feedback 这类特性的新 API 交互起来更加简单。
    
    许多旧的设计决定都旨在让编译器的设计更加容易。Swift 则专注于通过抛弃传统的紧张心理和编码实践,来使得应用开发者的工作更加轻松。随着现代编译器的发展,少量的代码可以表示更多的信息.
    
    使用 Swift,程序员只要维护原来一半量的代码文件,手动的代码同步工作为零,标点输入出错的概率也远远低于以前 -- 这样就能腾出更多的时间写高质量的代码。通过使用可选类型 —— 一种针对返回或不返回值的编译时安全机制,而返回值是同步操作、网络失效时无效的用户输入以及数据验证错误发生时普遍会遇到的问题。ARC 在 Swift 中对过程式 C 风格的代码,还有苹果公司 Cocoa 框架使用的面向对象代码都进行了统一。
    
    开发者会发现他们写的 Swift 比较少,而现代的编程语言特性则支持着他们行行代码都保持更多的可读性。随着其不断发展,Swift 会保持整个苹果公司的生态系统在编程领域的先进性,这都要感谢 iOS 和 Swift 中对动态库的支持。开源项目、第三方 SDK 以及框架可以更容易的集成进家居自动化设备以及社交服务中,不会有编译时间的增长。Swift 在某些算法的速度上几乎与 C++ 一样的快,而最新版的Xcode 6.3 和 Swift 1.2 则在这一起跑线上把目标指向更多的性能优化。
    
    再加上 playgrounds 和 swift 允许用一个新的方法来开发视觉反馈协助使用内联数据可视化算法程序,让一个较短的反馈回路和图形描述迭代译码过程更容易开始。
    

    结束

    关注个人技术公众号:
    这里写图片描述

    展开全文
  • Swift 框架搭建篇

    2016-01-28 19:27:04
    本片将会以UITbaleView为例,实现框架搭建,更详细深入的剖析Swift~~~~~当然Swift的使用基本分两种,一是在OC中使用Swift,二是在Swift中使用OC,那么这两种分别要注意什么,请看下面详细说明1.Swift文件中使用OC...

    前两篇大概的介绍了一下Swift的组成,结构,语法及与OC的去区别,本片将会以UITbaleView为例,实现框架搭建,更详细深入的剖析Swift~~~~~

    当然Swift的使用基本分两种,一是在OC中使用Swift,二是在Swift中使用OC,那么这两种分别要注意些什么,请看下面详细说明

    1.Swift文件中使用OC文件
    1)创建一个桥接文件,(如果在原工程中创建的OC文件,系统会自动帮我们创建一个桥接文件,如果是导入的OC文件,就需要我们自己创建一个.h的头文件当做桥接文件)
    2)如果想要使用自定义的.h文件当做桥接文件,需要在Bulid Settings里面修改一下路径,搜索bridging就会感应出来
    3)在桥接文件中导入OC 文件的头文件,在Swift中就可以直接使用了
    这里写图片描述

    2.在OC文件中使用Swift文件
    1)在蓝色工程光标里面的Bulid Settings搜索product module
    2)搜索出什么名字就在你想使用的地方导入搜索出来你想要的名字加Swift.h,如下图
    这里写图片描述
    这里写图片描述

    下面一起来看一下,Swift中tableView的写法
    1.创建一个Swift状态下的ViewController
    这里写图片描述
    创建好以后,会出现下面这种格式画面
    这里写图片描述

    由于Swift没有.h文件所以,属性要写在方法外面,就是写在override func viewDidLoad()这个方法上面

    2.下面一起来创建一个控件(UITableView)
    控件还是写在override func viewDidLoad()这个方法里面

    let myTableView = UITableView(frame: CGRectMake(0, 0, 320, 568))
    
        self.view.addSubview(myTableView)
            myTableView.delegate = self
            myTableView.dataSource = self

    3.添加一个数组属性,用来当做表视图的数据源(当Swift的数据类型不能满足需求的时候,可以使用OC 的数据类型)

     var DataArr = NSMutableArray()

    4.添加代理,代理名写在类名后面用逗号隔开,如下图
    这里写图片描述
    5.实现代理方法,两个必须是实现的方法

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1
        }
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            //在后面需要加as
            let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTableViewCell
    
            return cell
        }
    

    注意:cell的创建和OC一样,两种方法,任选一种,下面是注册方法(个人觉得注册简便一点)

     //MARK:注册cell
            myTableView.registerClass(MyTableViewCell.classForCoder(), forCellReuseIdentifier: "cell")

    6.向数组中添加数据
    创建一个类。这里以person类为例

    for var i=0;i<10;i++ {
                let p = Person(Name: "name\(i)", Age: "age\(i)", Address: "address\(i)")
                DataArr.addObject(p)
            }

    7.给cell赋值

     let p =  DataArr.objectAtIndex(indexPath.row)
            cell.lable.text = p.name

    8.完成这一步以后运行一下,就可以看到下面这个界面

    这里写图片描述

    9.最后说一下 UINavigationBar的添加
    选中storyboard,按照图片指示,就可以添加上了
    这里写图片描述

    以上就是关于Swift中tableView框架的搭建,其他的控件搭建原理基本都是类似

    展开全文
  • https://github.com/LeoMobileDeveloper注意:本文的代码是用Swift 2.2写的。前言引自Apple Swift团队的一句话 At the heart of Swift’s design are two incredibly powerful ideas: protocol-

    来自Leo的原创博客,转载请著名出处

    我的StackOverflow

    profile for Leo on Stack Exchange, a network of free, community-driven Q&A sites

    我的Github
    https://github.com/LeoMobileDeveloper

    注意:本文的代码是用Swift 2.2写的。


    前言

    引自Apple Swift团队的一句话

    At the heart of Swift’s design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of these concepts benefit predictability, performance, and productivity, but together they can change the way we think about programming.

    通过值类型和面相协议协议来编写你的App,你会发现原来Swift对比Objective C来说,是如此的Powerful

    在阅读本文之前,建议有时间的同学去看下WWDC 2015和WWDC 2016的三个视频,相信会很有收获的。


    值类型

    首先,我们来看看OC中常见的需要Copy的类型,比如NSString,NSArray,NSDictionary…
    通常,你需要将一个NSStrig声明为Copy,so,why?
    如果我们将一个NSString声明为strong

    @property (strong,nonatomic)NSString * str;

    然后,这么调用

     NSMutableString * mutableStr = [@"Leo" mutableCopy];
     self.str = mutableStr;
     [mutableStr appendString:@" MobileDeveloper"];
     NSLog(@"%@",self.str); //Leo MobileDeveloper

    所以,问题就在这了,本身NSString是一个不可变对象,但是我的str潜在的被修改了都不知道。所谓为了更安全,很多类型都采用了Copy来进行操作。

    Swift中,Class是引用类型,Struct和Enum是值类型。引用类型在传递的时候,具有implict sharing. 也就是,默认是同一个内存对象的引用。而值类型在传递的时候,都是copy。
    比如

    class Person{
        var name:String
        var age:UInt
        init(name:String,age:UInt){
            self.name = name
            self.age = age
        }
    }
    

    然后,当你进行传递,然后不小心在某一个地方修改了一个属性,你会发现原始的对象被修改了(即使我将Person声明为let)

    let jack = Person(name: "Jack", age: 23)
    let temp = jack
    temp.age = 24
    print(jack.age) //24

    这样做导致的结果就是:

    • 你的实例在多个地方都可能被修改,导致了很多你注意不到的代码耦合
    • 你的实例依赖于外部状态,所以,单元测试变的困难。甚至,你的代码通过了单元测试,仍然会有Bug

    然后,我们再来看看值类型。

    struct Person{
        var name:String
        var age:UInt
    }
    
    let jack = Person(name: "Jack", age: 23)
    
    var temp = jack
    temp.age = 24
    print(jack.age) //23

    值类型的最大优势是:不可变状态,拿到值类型你不需要考虑别处会修改它

    如果你懂一点函数式编程语言,比如Haskell,你一定会了解不可变状态带来的代码可读性,可维护性,可测试行的好处。

    值类型用在哪些地方?

    如果你在网上搜索,那么一定会有很多博客,很多问答告诉你:“值类型用在简单的Model上”

    网上别人的观点一定是正确的吗?

    当然不是。

    值类型不仅仅能用在Model上,从MVVM的角度,他也能用在View,Controller,和viewModel上。并且,带来很多好处。WWDC 2016的这个视频Protocol and Value Oriented Programming in UIKit Apps介绍了值类型用在View的Layout以及Controller的Undo。这里我就不拾人牙慧,再写一遍了。

    引自Swift官方的描述,以下场景你可以优先考虑值类型

    • 当这个数据结构主要是为了封装一系列相关的值
    • 当进行传递时候,应该传递的是Copy,而不是引用
    • 这个数据结构本身存储的类型也是值类型
    • 这个数据结构不需要继承

    Equatable

    通常值类型,你都要让其遵循Equatable来保证可以用符号==来比较。
    比如

    struct Point:Equatable{
        var x,y:CGFloat
    }
    func == (lhs:Point,rhs:Point)->Bool{
        return lhs.x == rhs.x && lhs.y == rhs.y
    }

    值类型性能

    看到这,有经验的你一定在想,值类型每次都Copy,不会有性能问题吗?
    绝大部份时候,没有性能问题。
    并且相对于Class,Struct往往性能更好,因为:

    • 通常Struct在栈上分配内存,Class在堆上分配内存。在堆上分配内存的性能是要低于栈上的,因为堆上不得不做很多锁之类的操作来保证多线程的时候不会有问题。
    • Struct无需引用计数,而Classs需要。引用计数会造成隐式的额外开销
    • Struct不能继承,所有在方法执行的时候,是static dispatch.而Class是dynacmic dispatch。意味着Class需要通过virtual table来动态的找到执行的方法。

    Tips:如果你希望深入了解Swift的性能问题,建议看看WWDC 2016的这个视频 Understanding Swift Performance


    协议的定义

    A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. (协议定义了一个功能需要的方法,属性以及其它必要条件的蓝图)

    也就是说,协议是一个功能抽象的体现,协议本身只提供一套规则。

    Swift协议的优秀特性

    协议是一种类型

    Swift协议是一种类型。也就意味着你可以

    • 把它作为函数的参数或者返回值
    • 声明一个常量,变量或者属性
    • 作为容器类型(Array/Dictionary)的Element
      比如
    protocol Drawable{
        func draw()
    }
    
    struct Circle:Drawable{
        var center:CGPoint
        var radius:CGFloat
        func draw(){
            print("Draw cirlce at \(center) with radius \(radius)")
        }
    }
    struct Line:Drawable{
        var from,to:CGPoint
        func draw(){
            print("Draw line from \(from) to radius \(to)")
        }
    }
    
    struct Chart:Drawable{
        var items:[Drawable]
        func draw() {
            items.forEach{
                $0.draw()
            }
        }
    }
    let circle = Circle(center: CGPointMake(10, 10), radius: 5)
    let line = Line(from: CGPointMake(10, 10), to:CGPointMake(15, 10))
    let chart = Chart(items: [circle,line])
    chart.draw()
    //Draw cirlce at (10.0, 10.0) with radius 5.0
    //Draw line from (10.0, 10.0) to radius (15.0, 10.0)

    因为协议本身是一种类型,你所需要关注的就是抽象的本身,比如上文的代码里我只需要关注Drawable(可绘制),至于具体是怎么实现的,我并不关心。

    可继承

    protocol Drawable{
        func draw()
    }
    protocol AntoherProtocol:Drawable{
        func anotherFunc()
    }

    协议本身也是可扩展的

    通过扩展,来提供协议的默认实现

    比如,利用extension来扩展协议Drawable

    extension Drawable{
        func printSelf(){
            print("This is a drawable protocol instance")
        }
    }
    //然后,我们就可以这么调用了
    let circle = Circle(center: CGPointMake(10, 10), radius: 5)
    circle.printSelf()

    让现有Class/Struct枚举遵循协议

    可以通过Extension的方式,来让一个现有类实现某一个协议。
    比如,以下是最简单的Swift Model,Person和Room。

    struct Person{
        var name:String
        var age:Int
        var city:String
        var district:String
    }
    struct Room{
        var city:String
        var district:String
    }

    然后,突然有一天,我们有个需求是需要Log出Person和Room的地址信息。这时候怎么做?

    方式一,通过修改Person和Room的实现。这样其实就违背了“对扩展开放,对修改封闭的原则”。

    方式二,通过Swift协议扩展

    //定义新的需求的协议
    protocol AddressPrintAble{
        var addressDescription:String {get}
    }
    //通过扩展,让Person和Room实现协议
    extension Person:AddressPrintAble{
        var addressDescription:String{
            return "\(city) \(district)"
        }
    }
    extension Room:AddressPrintAble{
        var addressDescription:String{
            return "\(city) \(district)"
        }
    }
    

    然后,你就可以把Person,Room结构体的实例,当作AddressPrintAble(新的可打印地址)类型来使用了。因为协议是一种类型,你可以把他们存到一个数组里。

    let leo:AddressPrintAble = Person(name: "Leo", age: 24, city: "Shanghai", district: "XuHui")
    let room:AddressPrintAble = Room(name: "XinSi", city: "Shanghai", district: "Xuhui")
    
    var array = [AddressPrintAble]()
    array.append(leo)
    array.append(room)
    
    array.forEach { (address) in
        print(address.addressDescription)
    }

    通过协议来扩展Cocoa类是一个很常见也是很有用的一个技巧

    条件扩展

    可以在扩展协议的时候,用where语句进行约束,只对满足制定条件的约束协议进行扩展。比如,我们在初始化一个NSObject子类的时候,我们希望有一些初始化配置,如果能够让NSObject提供一个操作符来让我们进行初始化配置就好了
    于是,我们可以这么扩展

    public protocol SetUp {}
    extension SetUp where Self: AnyObject {
        public func SetUp(@noescape closure: Self -> Void) -> Self {
            closure(self)
            return self
        }
    }
    extension NSObject: SetUp {}

    然后,你就可以这么调用了

    let textfield = UITextField().SetUp {
         $0.frame = CGRectMake(0, 0,200, 30)
         $0.textAlignment = .Center
         $0.font = UIFont.systemFontOfSize(14)
         $0.center = view.center
         $0.placeholder = "Input some text"
         $0.borderStyle = UITextBorderStyle.RoundedRect
     }

    其它

    仅能用在Class上的协议

    protocol myProtocol:class 

    协议组合Protocol Composition

    同时遵循两个协议

    protocol Named {
        var name: String { get }
    }
    protocol Aged {
        var age: Int { get }
    }
    func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
        print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
    }

    Self

    协议方法中的Self,表示实际的遵循协议的类型。

    protocol Orderable{
        func compare(other:Self)->Bool
    }

    然后

    protocol Orderable{
        func compare(other:Self)->Bool
    }
    extension CGPoint:Orderable{
        func compare(other: CGPoint) -> Bool { //注意,这里的Other是CGPoint 类型了
            return self.x > other.x && self.y > other.y
        }
    }

    associatedtype

    当你的协议中,有不确定类型的对象,你可以通过associatedtype来声明
    比如

    protocol Listable{
        associatedtype Content
        var dataList:[Content]{get}
    }
    extension CGPoint:Listable{
        typealias Content = CGFloat
        var dataList:[Content]{
            get{
                return [x,y]
            }
        }
    }

    总结

    协议会让你的代码变的前所未有的灵活,比如我在写PullToRefreshKit的时候,就用协议增强了其可扩展性。
    举个例子,对于下拉刷新Header。
    定义了Protocol RefreshableHeader来表示任意可刷新的View的抽象。

    public protocol RefreshableHeader:class{
        func distanceToRefresh()->CGFloat
        func percentUpdateWhenNotRefreshing(percent:CGFloat)
        func releaseWithRefreshingState()
        func didBeginEndRefershingAnimation(result:RefreshResult)
        func didCompleteEndRefershingAnimation(result:RefreshResult)  
    }

    然后,利用协议和范型,任何遵循这个协议的UIView子类,都可以作为我的Refresh Header

    public func setUpHeaderRefresh<T:UIView where T:RefreshableHeader>(header:T,action:()->())->T{
        //...
    }

    本来想把《面相协议编程》和《面相值类型编程》放到这一篇里来写的,写到这里发现篇幅有点过长了。这篇文章就当做是基础复习吧。后面再写一篇

    展开全文
  • Swift 4.0 新特性

    2017-08-15 19:37:46
    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。...

    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。

    体验

    Swift 4包含在Xcode 9中,您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

    版本迁移

    由于Swift 4新增了很多的新的语法特性,这些语法和思想完全区别于Swift 3及以下版本。因此,使用Swift迁移工具将为您处理大部分更改,在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

    语法改进

    extension 中可以访问 private 的属性

    例如有如下代码:

    struct Date: Equatable, Comparable {
        private let secondsSinceReferenceDate: Double
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    上面代码定义了一个 Date 结构体,并实现 Equatable 和 Comparable 协议。为了让代码更清晰,可读性更好,一般会把对协议的实现放在单独的 extension 中,这也是一种非常符合 Swift 风格的写法。

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    

    但是在 Swift 3 中,编译就报错了,因为 extension 中无法获取到 secondsSinceReferenceDate 属性,因为它是 private 的。所以,在 Swift 3 中必须把 private 改为 fileprivate。但是如果用 fileprivate,属性的作用域就会更大,可能会不小心造成属性的滥用。

    struct Date {
        fileprivate let secondsSinceReferenceDate: Double
    }
    ...

    而在 Swift 4 中,private 的属性的作用域扩大到了 extension 中,并且被限定在了 struct 和 extension 内部,这样就不需要再改成 fileprivate 了。

    类型和协议的组合类型

    考虑以下如下代码:

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [???]) {
        for control in controls where control.state.isEnabled {
        }
        control.shake()
    }
    

    ???处怎么写呢?在Swift 3中可以这么写。

    func shakeEm(controls: [UIControl]) {
        for control in controls where control.isEnabled {
            if control is Shakeable {
                (control as! Shakeable).shake()
            }
        }
    }

    在Swift 4中,如果将类型和协议用 & 组合在一起使用,代码就可以这么写了。

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [UIControl & Shakeable]) {
        for control in controls where control.state.isEnabled {
            control.shake()
        }// Objective-C API
    @interface NSCandidateListTouchBarItem<CandidateType> : NSTouchBarItem
    @property (nullable, weak) NSView <NSTextInputClient> *client;
    @end
    }

    Associated Type 追加Where 约束语句

    在 Swift 4 中可以在 associated type 后面声明的类型后追加 where 语句,其语法格式如下:

    associatedtype Element where <xxx>

    下面是 Swift 4 标准库中 Sequence 中 Element 的声明:

    protocol Sequence {
        associatedtype Element where Self.Element == Self.Iterator.Element
        // ...
    }

    它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。

    Key Paths 语法

    先来看看Swift 3的Key Paths语法:

    @objcMembers class Kid: NSObject {
        dynamic var nickname: String = ""
        dynamic var age: Double = 0.0
        dynamic var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 5.5)
    
    let kidsNameKeyPath = #keyPath(Kid.nickname)
    
    let name = ben.valueForKeyPath(kidsNameKeyPath)
    ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
    
    

    在Swift 4中上面的代码可以这样写:

    struct Kid {
        var nickname: String = ""
        var age: Double = 0.0
        var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 8, friends: [])
    
    let name = ben[keyPath: \Kid.nickname]
    ben[keyPath: \Kid.nickname] = "BigBen"
    
    

    相比 Swift 3,Swift 4 的 Key Paths 具有以下优势:

    1. 类型可以定义为 class、struct;
    2. 定义类型时无需加上 @objcMembers、dynamic 等关键字;
    3. 性能更好;
    4. 类型安全和类型推断,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的类型是
      Any,ben[keyPath: \Kid.nickname] 直接返回 String 类型;
    5. 可以在所有值类型上使用;

    下标支持泛型

    Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。在Swift 4中,下标也可以使用泛型。

    struct GenericDictionary<Key: Hashable, Value> {
        private var data: [Key: Value]
    
        init(data: [Key: Value]) {
            self.data = data
        }
    
        subscript<T>(key: Key) -> T? {
            return data[key] as? T
        }
    }
    
    let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
    
    let name: String? = dictionary["Name"] // 不需要再写 as? String
    
    

    字符串

    Unicode 字符串

    在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 \u{65}\u{301}。例如:

    这里写图片描述
    这个 family 是一个由多个字符组合成的字符,打印出来的结果为 一个家庭。上面的代码在 Swift 3 中打印的 count 数是 4,在 Swift 4 中打印出的 count 是 1。

    更快的字符处理速度

    Swift 4 的字符串优化了底层实现,对于英语、法语、德语、西班牙语的处理速度提高了 3.5 倍。对于简体中文、日语的处理速度提高了 2.5 倍。

    去掉了 characters

    Swift 3 中的 String 需要通过 characters 去调用的属性方法,在 Swift 4 中可以通过 String 对象本身直接调用,例如:

    let values = "one,two,three..."
    var i = values.characters.startIndex
    
    while let comma = values.characters[i...<values.characters.endIndex].index(of: ",") {
        if values.characters[i..<comma] == "two" {
            print("found it!")
        }
        i = values.characters.index(after: comma)
    }
    
    

    在Swift 4 可以把上面代码中的所有的 characters 都去掉:

    let values = "one,two,three..."
    var i = values.startIndex
    
    while let comma = values[i...<values.endIndex].index(of: ",") {
        if values[i..<comma] == "two" {
            print("found it!")
        }
        i = values.index(after: comma)
    }
    

    One-sided Slicing

    Swift 4 新增了一个语法糖 … 可以对字符串进行单侧边界取子串。例如:

    let values = "abcdefg"
    let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
    let subvalues = values[startSlicingIndex...] // One-sided Slicing
    // defg
    
    

    将String 当做 Collection 来用

    Swift 4 中 String 可以当做 Collection 来用,并不是因为 String 实现了 Collection 协议,而是 String 本身增加了很多 Collection 协议中的方法,使得 String 在使用时看上去就是个 Collection。例如:
    翻转字符串

    let abc: String = "abc"
    print(String(abc.reversed()))
    // cba

    遍历字符

    let abc: String = "abc"
    for c in abc {
        print(c)
    }
    /*
    a
    b
    c
    */

    Map、Filter、Reduce

    // map
    let abc: String = "abc"
    _ = abc.map {
        print($0.description)
    }
    
    // filter
    let filtered = abc.filter { $0 == "b" }
    
    // reduce
    let result = abc.reduce("1") { (result, c) -> String in
        print(result)
        print(c)
        return result + String(c)
    }
    print(result)
    
    

    Substring

    这里写图片描述

    在 Swift 中,String 的背后有个 Owner Object 来跟踪和管理这个 String,String 对象在内存中的存储由内存其实地址、字符数、指向 Owner Object 指针组成。Owner Object 指针指向 Owner Object 对象,Owner Object 对象持有 String Buffer。当对 String 做取子字符串操作时,子字符串的 Owner Object 指针会和原字符串指向同一个对象,因此子字符串的 Owner Object 会持有原 String 的 Buffer。当原字符串销毁时,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不会释放,造成极大的内存浪费。
    在 Swift 4 中,做取子串操作的结果是一个 Substring 类型,它无法直接赋值给需要 String 类型的地方。必须用 String() 包一层,系统会通过复制创建出一个新的字符串对象,这样原字符串在销毁时,原字符串的 Buffer 就可以完全释放了。例如:

    let big = downloadHugeString()
    let small = extractTinyString(from: big)
    
    mainView.titleLabel.text = small // Swift 4 编译报错
    
    mainView.titleLabel.text = String(small) // 编译通过
    
    

    多行字符串字面量

    Swift 3 中写很长的字符串只能写在一行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    

    字符串中间有换行只能通过添加 \n 字符来代表换行。Swift 4 可以把字符串写在一对 “”” 中,这样字符串就可以写成多行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = """
            Q: Why does \(name) have \(n) \(character)'s in their name?
            A: I don't know, why does \(name) have \(n) \(character)'s in their name?
            Q: Because otherwise they'd be called \(punchline).
            """
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    
    

    Swift 标准库

    Encoding and Decoding

    当需要将一个对象持久化时,需要把这个对象序列化,往常的做法是实现 NSCoding 协议,写过的人应该都知道实现 NSCoding 协议的代码写起来很痛苦,尤其是当属性非常多的时候。Swift 4 中引入了 Codable 帮我们解决了这个问题,这和Java等面向对象语言有异曲同工之妙。例如:

    struct Language: Codable {
        var name: String
        var version: Int
    }

    想让这个 Language 对象的实例持久化,只需要让 Language 符合 Codable 协议即可,Language 中不用写别的代码。符合了 Codable 协议以后,可以选择把对象 encode 成 JSON 或者 PropertyList。

    Encode操作

    let swift = Language(name: "Swift", version: 4)
    if let encoded = try? JSONEncoder().encode(swift) {
        // 把 encoded 保存起来
    }

    Decode操作

    if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
        print(decoded.name)
    }

    Sequence

    在Swift 3中,

    protocol Sequence {
        associatedtype Iterator: IteratorProtocol
        func makeIterator() -> Iterator
    }

    由于 Swift 4 中的 associatedtype 支持追加 where 语句,所以 Sequence 做了这样的改进。

    protocol Sequence {
        associatedtype Element
        associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
        func makeIterator() -> Iterator
    }
    
    

    Swift 4 中获取 Sequence 的元素类型可以不用 Iterator.Element,而是直接取 Element。例如:

    protocol Sequence {
        associatedtype SubSequence: Sequence 
            where SubSequence.SubSequence == SubSequence,
                  SubSequence.Element == Element
    }
    
    

    通过 where 语句的限定,保证了类型正确,避免在使用 Sequence 时做一些不必要的类型判断。

    Protocol-oriented integers

    整数类型的协议也做了修改,新增了 FixedWidthInteger 等协议,具体的协议继承关系如下:
    这里写图片描述

    Dictionary and Set enhancements

    这里简单的罗列了Dictionary 和 Set 增强的功能点:

    • 通过 Sequence 来初始化;
    • 可以包含重复的 Key
    • Filter 的结果的类型和原类型一致
    • Dictionary 的 mapValues 方法
    • Dictionary 的默认值
    • Dictionary 可以分组
    • Dictionary 可以翻转

    NSNumber

    在 Swift 4 中,把一个值为 999 的 NSNumber 转换为 UInt8 后,能正确的返回 nil,而在 Swift 3 中会不可预料的返回 231。

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    MutableCollection.swapAt(::)

    MutableCollection 现在有了一个新方法 swapAt(::) 用来交换两个位置的值,例如:

    var mutableArray = [1, 2, 3, 4]
    mutableArray.swapAt(1, 2)
    print(mutableArray)
    // 打印结果:[1, 3, 2, 4]

    Xcode改进

    New Build System

    Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings… 中选择开启。
    这里写图片描述

    预编译 Bridging Headers 文件

    对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging Headers 文件,然后把 Swift 要调用的 Objective-C 类的头文件都写在里面,编译器会读取 Bridging Headers 中的头文件,然后生成一个庞大的 Swift 文件,文件内容是这些头文件内的 API 的 Swift 版本。然后编译器会在编译每一个 Swift 文件时,都要编译一遍这个庞大的 Swift 文件的内容。

    有了预编译 Bridging Headers 以后,编译器会在预编译阶段把 Bridging Headers 编译一次,然后插入到每个 Swift 文件中,这样就大大提高了编译速度(苹果宣称 Xcode 9 和 Swift 4 对于 Swift 和 Objective-C 混合编译的速度提高了 40%)。

    COW Existential Containers

    Swift 中有个东西叫 Existential Containers,它用来保存未知类型的值,它的内部是一个 Inline value buffer,如果 Inline value buffer 中的值占用空间很大时,这个值会被分配在堆上,然而在堆上分配内存是一个性能比较慢的操作。

    Swift 4 中为了优化性能引入了 COW Existential Containers,这里的 COW 就代表 “Copy-On-Write”,当存在多个相同的值时,他们会共用 buffer 上的空间,直到某个值被修改时,这个被修改的值才会被拷贝一份并分配内存空间。

    移除未调用的协议实现

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    
    

    例如,上面的代码中,Date 实现了 Equatable 和 Comparable 协议。编译时如果编译器发现没有任何地方调用了对 Date 进行大小比较的方法,编译器会移除 Comparable 协议的实现,来达到减小包大小的目的。

    减少隐式 @objc 自动推断

    在项目中想把 Swift 写的 API 暴露给 Objective-C 调用,需要增加 @objc。在 Swift 3 中,编译器会在很多地方为我们隐式的加上 @objc,例如当一个类继承于 NSObject,那么这个类的所有方法都会被隐式的加上 @objc。这样很多并不需要暴露给 Objective-C 也被加上了 @objc。大量 @objc 会导致二进制文件大小的增加。

    class MyClass: NSObject {
        func print() { ... } // 包含隐式的 @objc
        func show() { ... } // 包含隐式的 @objc
    }

    在 Swift 4 中,隐式 @objc 自动推断只会发生在很少的当必须要使用 @objc 的情况,比如:

    1. 复写父类的 Objective-C 方法
    2. 符合一个 Objective-C 的协议

    其它大多数地方必须手工显示的加上 @objc。减少了隐式 @objc 自动推断后,Apple Music app 的包大小减少了 5.7%。

    兼容

    Xcode 9 中同时集成了 Swift 3.2 和 Swift 4。

    1. Swift 3.2 完全兼容 Swift 3.1,并会在过时的语法或函数上报告警告。
    2. Swift 3.2 具有 Swift 4 的一些写法,但是性能不如 Swift 4。
    3. Swift 3.2 和 Swift 4 可以混合编译,可以指定一部分模块用 Swift 3.2 编译,一部分用 Swift 4 编译。
    4. 迁移到 Swift 4 后能获得 Swift 4 所有的新特性,并且性能比 Swift 3.2 好。

    当 Xcode 正式版发布后,现有的 Swift 代码可以直接升级到 Swift 3.2 而不用做任何改动,后续可以再迁移到 Swift 4。或者直接迁移到 Swift 4 也可以,Swift 4 相比 Swift 3 的 API 变化还是不大的,很多第三方库都可以直接用 Swift 4 编译。Swift 1 到 2 和 Swift 2 到 3 的迁移的痛苦在 3 到 4 的迁移上已经大大改善了。

    参考资料:

    展开全文
  • 首先就是在 targets->... 然后创建swift文件时会生成"文件名-Bridging-Header.h"这样一个桥接文件 怎样使用cocoapods就不多说了 最重要的就是在Podfile里面加上use_frameworks! 因为swift使用的是框架而不是静态库
  • IBAN和SWIFT是在金融机构之间传递交易的两种标准化格式。IBANs (国际银行账号)主要在欧洲使用,并识别跨国界的特定账户。SWIFT (全球银行间金融电信协会)代码识别机构。 1、专业一些的说法是BIC是银行识别码,意思...
  • 前言14年Swift刚出的时候开始学习的Swift,后来由于项目需要,一直写App还是用的OC。最近打算把Swift重新捡起来,这个Objective C转Swfit系列就当成是我的复习笔记,顺便写成博客记录下来吧。这个系列不是讲解Swift...
  • Swift关键字总结下篇

    2019-06-03 16:53:35
    Swift 中有多少关键字? 在Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数字符号#开头的关键字, 以及特定上下文环境使用的关键字。本文中涉及的代码...
  • Swift闭包详解

    2016-04-13 15:41:28
    大家注意哦,由于swift的更新,参考网址里的代码已经不能跑起来了,我结合自己的理解整理如下。 在Swift函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一: 全局函数是一个有名字但...
  • 在 Objective-C 中 @encode 使用起来很简单,通过传入一个类型,我们就可以获取代表这个类型的编码 C 字符串:char *typeChar1 = @encode(int32_t); char *typeChar2 = @encode(NSArray); // typeChar1 = "...
  • Swift_3.0 相对于 Swift_2.2 版本发生了哪些的变化。本文件将以最直观的代码对比以及文字分析阐述。
  • Swift:什么时候使用结构体和类发布于 2015 年 8 月 14 日世界上对swift持续不断的讨论话题中有一个就是什么时候使用结构体什么时候使用类。我想我今天要贡献一些自己的想法。值 VS 引用答案其实很简单:当你需要值...
  • 犹豫中,终于写完第一个Swift的项目,一直都在用oc 写项目,之前没有机会使用swift 写项目。 我先说一下我这个项目的架构 1.项目整体是storyBoard 布局的 2.使用的是MVC 模式 3.解析数据是swift原生的解析 4.网络...
  • Java与Swift对比

    2016-12-09 14:02:14
    从Java到Swift还是比较简单的,相比Object-C,Swift和Java的语法更加接近,和最近的Kotlin就更像了。Swift同时支持面向对象编程和函数式编程。Swift比Java功能更加强大,用法更加友好。网上有一份Java和Swift的粗略...
  • Swift关键字总结上篇

    2019-06-20 11:11:21
    Swift 中有多少关键字? 在Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数字符号#开头的关键字, 以及特定上下文环境使用的关键字。 本文中涉及的代码...
  • 当我们开始写Swift,首先要注意的就是按照Swift的风格写,而不是沿用OC的风格。省略句末的分号swift推崇简洁的语法。如果一行里只有一句代码,句末不要写分号。省略self.不在闭包里的时候调用自身的属性或者方式时...
  • swift 对枚举的进行了更加灵活的实现,比如支持关联值的枚举,还有可以设置原始值的枚举。这都扩展了枚举类型的用途。下面我们就来品味下枚举以及它在 swift 中的实现吧。 枚举定义语法 首先,我们来看看在 swift 中...
  • Swift 3 中的新特性

    2016-07-04 14:28:18
    原文:What’s New in ...如果你没有密切关注 Swift Evolution 项目,那么在将代码迁移到 Swift 3 时,你可能不知道它到底有什么改变,以及它会对你的代码带来什么影响。那么你可能需要读一下这篇文章了。 通过本文,
  • 本人是个IOS开发新手,之前没有接触过Objective-C语言,直接从最新的Swift语言学起,进行IOS8的学习开发。由于Swift语言是一门全新的语言,网上的资料很少,遇到问题百度搜到的也全是英文页面。通过一段时间的学习,...
1 2 3 4 5 ... 20
收藏数 36,888
精华内容 14,755