• // 结果为[1,2,3]复制代码但是如果数组元素为String时,有一个特别的方法joined(separator:),作用也是将集合里的元素连接起来(只是元素必须是String) 看定义: extension Array where Eleme...

    flatten的作用之一可以将集合里的类型为集合的值连接起来。直接看例子:

    [[1,2],[3]].flatten()              
    
    // 结果为[1,2,3]复制代码

    但是如果数组元素为String时,有一个特别的方法joined(separator:),作用也是将集合里的元素连接起来(只是元素必须是String) 看定义:

    extension Array where Element == String {
    
        /// Returns a new string by concatenating the elements of the sequence,
        /// adding the given separator between each element.
        ///
        /// The following example shows how an array of strings can be joined to a
        /// single, comma-separated string:
        ///
        ///     let cast = ["Vivien", "Marlon", "Kim", "Karl"]
        ///     let list = cast.joined(separator: ", ")
        ///     print(list)
        ///     // Prints "Vivien, Marlon, Kim, Karl"
        ///
        /// - Parameter separator: A string to insert between each of the elements
        ///   in this sequence. The default separator is an empty string.
        /// - Returns: A single, concatenated string.
        public func joined(separator: String = default) -> String
    }复制代码

    显然针对过去flatten的使用方式,它的真实意图就是join。所以在swift 3中,将flatten()重命名为joined()。 这么一来可读性也提高了。也增加了一种使用方法:在连接集合内元素时可以指定连接的元素。比如:

    let nestedNumbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    let joined = nestedNumbers.join(separator: [-1, -2])
    print(Array(joined))
    // Prints "[1, 2, 3, -1, -2, 4, 5, 6, -1, -2, 7, 8, 9]"复制代码

    相关链接:

    SE0133-Rename flatten() to joined() A (mostly) comprehensive list of Swift 3.0 and 2.3 changes

    展开全文
  • Swift写服务端 — Perfect框架学习(一)一、Perfect简介Perfect是一组完整、强大的工具箱、软件框架体系和Web应用服务器,可以在Linux、iOS和macOS (OS X)上使用。该软件体系为Swift工程师量身定制了一整套用于...

    用Swift写服务端 — Perfect框架学习(一)

    一、Perfect简介

    Perfect是一组完整、强大的工具箱、软件框架体系和Web应用服务器,可以在Linux、iOS和macOS (OS X)上使用。该软件体系为Swift工程师量身定制了一整套用于开发轻量、易维护、规模可扩展的Web应用及其它REST服务的解决方案,这样Swift工程师就可以实现同时在服务器和客户端上采用同一种语言开发软件项目。

    由于建立在一个高性能异步网络引擎基础上,Perfect还能够在FastCGI上运行,支持安全套接字加密(SSL)。该软件体系还包含很多其它互联网服务器所需要的特点,包括WebSockets和iOS消息推送,而且很快会有更多强大的功能支持。


    无论您是资深程序员还是入门级的软件工程师,本文都能够帮助您快速启动Perfect实现服务器项目开发运行。

    二、Perfect项目快速上手

    1.编译入门项目

    我们在Perfect官网的git上直接下载一个入门项目。编译后就可以启动一个本地的服务,监听你的8181端口:

    git clone https://github.com/PerfectlySoft/PerfectTemplate.git
    cd PerfectTemplate
    swift build
    .build/debug/PerfectTemplate



    我们可以在控制台看到以下内容:

    Starting HTTP server on 0.0.0.0:8181 with document root ./webroot

    服务器现在已经运行并等待连接。从浏览器打开http://localhost:8181/ 可以看到欢迎信息。


     在终端控制台中输入组合键“control-c”可以随时终止服务器运行。


    2.Xcode管理

    Swift软件包管理器(SPM)能够创建一个Xcode项目,并且能够运行PerfectTemplate模板服务器,还能为您的项目提供完全的源代码编辑和调试。在您的终端命令行内输入:

    swift package generate-xcodeproj

    然后打开产生的文件“PerfectTemplate.xcodeproj”,确定选择了可执行的目标文件,并选择在“我的Mac”运行。现在您可以运行并调试服务器了。



    直接运行XCode,然后在浏览器中输入0.0.0.0:8181也是能直接运行的!

    三、搭建HTTP服务器

    编辑main.swift文件

    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    //HTTP服务
    var routesArr = [Dictionary<String, Any>]()
    
    var someDict1 : [String:String] = ["method":"GET","url":"/api"]
    
    routesArr.append(someDict1)
    
    let networkServer = NetworkServerManager(root: "webroot", port: 8080, routesArr: routesArr)
    
    networkServer.startServer()
    创建NetworkServerManager.swift文件

    //
    //  NetworkServerManager.swift
    //  PerfectTemplatePackageDescription
    //
    //  Created by ZFJ on 2018/1/9.
    //
    
    import PerfectLib
    import PerfectHTTP
    import PerfectHTTPServer
    
    open class NetworkServerManager {
        fileprivate var server: HTTPServer
        internal init(root: String, port: UInt16, routesArr: Array<Dictionary<String, Any>>) {
            server = HTTPServer.init()                             //创建HTTPServer服务器
            for dict: Dictionary in routesArr {
                let baseUri : String = dict["url"] as! String      //跟地址
                let method : String = dict["method"] as! String    //方法
                var routes = Routes.init(baseUri: baseUri)         //创建路由器
                let httpMethod = HTTPMethod.from(string: method)
                configure(routes: &routes, method: httpMethod)     //注册路由
                server.addRoutes(routes)                           //路由添加进服务
            }
            server.serverName = "localhost"                        //服务器名称
            server.serverPort = port                               //端口
            server.documentRoot = root                             //根目录
            server.setResponseFilters([(Filter404(), .high)])      //404过滤
        }
        
        //MARK: 开启服务
        open func startServer() {
            do {
                print("启动HTTP服务器")
                try server.start()
            } catch PerfectError.networkError(let err, let msg) {
                print("网络出现错误:\(err) \(msg)")
            } catch {
                print("网络未知错误")
            }
            
        }
        
        //MARK: 注册路由
        fileprivate func configure(routes: inout Routes,method: HTTPMethod) {
            routes.add(method: .get, uri: "/selectUserInfor") { (request, response) in
                let path = request.path
                print(path)
                let jsonDic = ["hello": "world"]
                let jsonString = self.baseResponseBodyJSONData(code: 200, message: "成功", data: jsonDic)
                response.setBody(string: jsonString)                           //响应体
                response.completed()                                           //响应
            }
            
    //        if method == .get{
    //            //get请求
    //        }else if method == .post{
    //            //post请求
    //            let postParams = request.postParams
    //            print(postParams)
    //        }
        }
        
        //MARK: 通用响应格式
        func baseResponseBodyJSONData(code: Int, message: String, data: Any!) -> String {
            var result = Dictionary<String, Any>()
            result.updateValue(code, forKey: "code")
            result.updateValue(message, forKey: "message")
            if (data != nil) {
                result.updateValue(data, forKey: "data")
            }else{
                result.updateValue("", forKey: "data")
            }
            guard let jsonString = try? result.jsonEncodedString() else {
                return ""
            }
            return jsonString
        }
        
        //MARK: 404过滤
        struct Filter404: HTTPResponseFilter {
            func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
                callback(.continue)
            }
            func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
                if case .notFound = response.status {
                    response.setBody(string: "404 文件\(response.request.path)不存在。")
                    response.setHeader(.contentLength, value: "\(response.bodyBytes.count)")
                    callback(.done)
                } else {
                    callback(.continue)
                }
            }
            
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    运行结果




    接口访问


    四、搭建MySql服务器

    我的电脑上安装的有Homebrew,所以我直接通过Homebrew安装MySql,安装命令:

    brew install mysql

    配置MySql

    #开启MySQL服务
    mysql.server start
    #初始化MySQL配置向导
    mysql_secure_installation
    我电脑上数据库已经而配置好了,这里面我就不演示了,如果有不了解的可以加我QQ或者QQ群;


    五、安装Navicat Premium

    Navicat premium是一款数据库管理工具,是一个可多重连线资料库的管理工具,它可以让你以单一程式同时连线到 MySQL、SQLite、Oracle 及 PostgreSQL 资料库,让管理不同类型的资料库更加的方便。


    Navicat Premium_12.0.22破解版下载


    这里面下载好了以后会让你输入安装密码,密码为:xclient.info 

    如下图:


    安装成功以后如果打开出现如下图的错误,只需要在终端输入以下代码就好;


    执行以下命令开启
    sudo spctl --master-disable


    这样就可以打开了,然后链接MySQL数据库,如下图


    然后创建数据库userInforsTable,然后创建了一个userTable表,并向userTable表中添加了三条数据;如下图:


    这样你就可以操作MySQL数据库了,当然你也可以通过终端直接操作数据库;


    六、编辑Perfect服务端

    创建DataBaseManager.swift数据库管理类,在这里我们对数据库进行增删改查操作;

    //
    //  DataBaseManager.swift
    //  PerfectTemplatePackageDescription
    //
    //  Created by ZFJ on 2018/1/17.
    //
    
    import MySQL
    
    //MARK: 数据库信息
    let mysql_host = "127.0.0.1"
    let mysql_user = "root"
    let mysql_password = "12345678"
    let mysql_database = "userInforsTable"
    
    //MARK: 表信息
    let userTable = "userTable"                    //用户信息表
    
    open class DataBaseManager {
        fileprivate var mysql : MySQL
        internal init() {
            mysql = MySQL.init()                       //创建MySQL对象
            guard connectDataBase() else{            //开启MySQL连接
                return
            }
        }
        
        //MARK:开启链接
        private func connectDataBase() -> Bool{
            let connected = mysql.connect(host: mysql_host, user: mysql_user, password: mysql_password, db: mysql_database)
            guard connected else{
                print("MySql链接失败" + mysql.errorMessage())
                return false
            }
            print("MySql链接成功")
            return true
        }
        
        //MARK: 执行SQL语句
        /// 执行SQL语句
        ///
        /// - Parameter sql: sql语句
        /// - Returns: 返回元组(success:是否成功 result:结果)
        @discardableResult
        func mysqlStatement(_ sql:String) -> (success:Bool,mysqlResult:MySQL.Results?,errorMsg:String) {
            guard mysql.selectDatabase(named:mysql_database) else {
                //指定操作的数据库
                let msg = "未找到\(mysql_database)数据库"
                print(msg)
                return(false, nil, msg)
            }
            
            let successQuery = mysql.query(statement:sql) //sql语句
            guard successQuery else{
                let msg = "SQL失败:\(sql)"
                print(msg)
                return(false, nil, msg)
            }
            let msg = "SQL成功:\(sql)"
            print(msg)
            return (true, mysql.storeResults(), msg)                            //sql执行成功
        }
        
        /// 增
        ///
        /// - Parameters:
        ///   - tableName: 表
        ///   - keyValueDict: 键:值 对字典
        func insertDataBaseSQL(tableName:String, keyValueDict:Dictionary<String, Any>) -> (success: Bool, mysqlResult: MySQL.Results?, errorMsg: String) {
            var keys: [String] = []
            var values: [String] = []
            for (key, value) in keyValueDict {
                if let str = value as? String {
                    keys.append(key)
                    values.append(str)
                }
            }
            let fieldNameAll: String = keys.joined(separator: ",")
            let valueAll: String = values.joined(separator: ",")
            let SQL = "insert into \(tableName)(\(fieldNameAll)) values(\(valueAll))"
            return mysqlStatement(SQL)
        }
        
        /// 删
        ///
        /// - Parameters:
        ///   - tableName: 表
        ///   - key: 键
        ///   - value: 值
        func deleteDatabaseSQL(tableName: String, key: String, value: String) -> (success: Bool, mysqlResult: MySQL.Results?, errorMsg: String) {
            
            let SQL = "DELETE FROM \(tableName) WHERE \(key) = '\(value)'"
            return mysqlStatement(SQL)
            
        }
        
        /// 改
        ///
        /// - Parameters:
        ///   - tableName: 表
        ///   - keyValue: 键值对( 键='值', 键='值', 键='值' )
        ///   - whereKey: 查找key
        ///   - whereValue: 查找value
        func updateDatabaseSQL(tableName: String, keyValue: String, whereKey: String, whereValue: String) -> (success: Bool, mysqlResult: MySQL.Results?, errorMsg: String) {
            
            let SQL = "UPDATE \(tableName) SET \(keyValue) WHERE \(whereKey) = '\(whereValue)'"
            return mysqlStatement(SQL)
            
        }
        
        /// 查所有
        ///
        /// - Parameters:
        ///   - tableName: 表
        ///   - key: 键
        func selectAllDatabaseSQL(tableName: String) -> (success: Bool, mysqlResult: MySQL.Results?, errorMsg: String) {
            
            let SQL = "SELECT * FROM \(tableName)"
            return mysqlStatement(SQL)
            
        }
        
        /// 查
        ///
        /// - Parameters:
        ///   - tableName: 表
        ///   - keyValue: 键值对
        func selectAllDataBaseSQLwhere(tableName: String, keyValue: String) -> (success: Bool, mysqlResult: MySQL.Results?, errorMsg: String) {
            
            let SQL = "SELECT * FROM \(tableName) WHERE \(keyValue)"
            return mysqlStatement(SQL)
            
        }
        
        //获取表中所有数据
        func mysqlGetHomeDataResult() -> [Dictionary<String, String>]? {
            let result = selectAllDatabaseSQL(tableName: userTable)
            var resultArray = [Dictionary<String, String>]()
            var dic = [String:String]()
            result.mysqlResult?.forEachRow(callback: { (row) in
                dic["userid"] = row[0]
                dic["userNumber"] = row[1]
                dic["userName"] = row[2]
                dic["userSex"] = row[3]
                dic["userBirthday"] = row[4]
                resultArray.append(dic)
            })
            return resultArray
            
        }
    }
    

    然后在NetworkServerManager中调用DataBaseManager,注册子路由/selectUserInfor查询用户表里的所以信息;

        //MARK: 注册路由
        fileprivate func configure(routes: inout Routes,method: HTTPMethod) {
            routes.add(method: .get, uri: "/selectUserInfor") { (request, response) in
                let path = request.path
                print(path)
    //            let jsonDic = ["hello": "world"]
    //            let jsonString = self.baseResponseBodyJSONData(code: 200, message: "成功", data: jsonDic)
    //            response.setBody(string: jsonString)                           //响应体
    //            response.completed()                                           //响应
                let queryParams = request.queryParams
                if queryParams.count == 0{
                    let result = DataBaseManager().mysqlGetHomeDataResult()
                    let jsonString = self.baseResponseBodyJSONData(code: 200, message: "成功", data: result)
                    response.setBody(string: jsonString)
                    response.completed()
                }else{
                    //有参数
                    //let value : String
                    for i in 0...queryParams.count - 1{
                        let partArr = queryParams[i]
                        print(partArr)
                    }
                    let jsonString = self.baseResponseBodyJSONData(code: 200, message: "成功", data: nil)
                    response.setBody(string: jsonString)
                    response.completed()
                }
            }
        }
        
    然后调取接口访问数据http://0.0.0.0:8080/api/selectUserInfor;如下图:


    注意事项

    1.如果你在NetworkServerManager中无法调用DataBaseManager,或者说调用DataBaseManager查找不到,那是因为你创建DataBaseManager的时候没有选择在项目中引用,默认选择了第一个第三方库了;


    如果你创建完成只需要稍微修改一下就好;


    2.如果提示MySQL找不到,那是因为你的工程中,或者我们开始下载的那个示例工程没有导入MySQL,你需要引用一下就好;

    首先修改Package.swift文件,引用https://github.com/PerfectlySoft/Perfect-MySQL.git 

    示例如下:

    import PackageDescription
    
    let package = Package(
        name: "PerfectTemplate",
        targets: [],
        dependencies: [
            .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 3),
            .Package(url: "https://github.com/PerfectlySoft/Perfect-MySQL.git", majorVersion: 2),
        ]
    )
    然后删除PerfectTemplate.xcodeproj文件, 接着终端重新生成PerfectTemplate.xcodeproj文件,最后打开工程就会发现MySQL库了,如下图:


    DEMO下载

    结束语

    欢迎大家加移动开发技术交流群,在这里大家可以一起讨论学习,这里有大佬,也有小菜鸟,没事还能斗斗图装装逼,如果需要换工作的还能相互推荐,期待大家的加入!大笑偷笑


                                                                 

                    












    展开全文
  • Swift、Xcode和Cocoa入门指南(iOS9 Programming Fundamentals With swift) 第四章 对象类型  第三章介绍了一些内建对象类型,不过还没有谈及对象类型本身(即 枚举 结构体 和 类);    本章结构: ...

    Swift(iOS9 Programming Fundamentals With swift


    第四章 对象类型


             第三章介绍了一些内建对象类型,不过还没有谈及对象类型本身(即 枚举 结构体 和 类);

             

             本章结构:

             1.介绍一下对象类型;

             2.详细介绍对象类型的3种风格:枚举、结构体与类;

             3.介绍Swift中用于增强对象类型灵活性的3种方式:协议、泛型与扩展;

             4.介绍3中保护类型;

             5.介绍3种集合类型;

             结束对Swift内建类型的介绍;


       4.10 扩展

             

             扩展是将自己的代码注入到其他地方声明的对象类型中的一种方式;

             

             你所扩展的是一个已有的对象类型,他可以是自定义的也可以是Swift或Cocoa的对象类型;

             

             扩展声明只能位于文件的顶部;

             要想声明扩展,请使用关键字extension,后跟已有的对象类型名,然后可以添加冒号,后跟该类型需要使用的协议列表名;最后是花括号,里面通常是对象类型声明的内容,具体限制如下:

             1)扩展不能重写已有的成员(不过可以重载已有的方法);

             2)扩展不能声明存储属性(不过可以声明计算属性);

             3)类的扩展不能声明指定初始化器和析构器(不过可以声明便捷初始化器);

             

             4.10.1 扩展对象类型

             

             示例1:纸牌游戏的洗牌,纸牌存储在数组中,我们通过扩展内建的Array类型,添加一个shuffle方法:(code)

    extension Array {
        mutating func shuffle(){
            for i in (0..<self.count).reversed() {
                let ix1 = i
                let ix2 = Int(arc4random_uniform(UInt32(i+1)))
                (self[ix1],self[ix2]) = (self[ix2],self[ix1])
            }
        }
    }

             示例2:CGRect中心点(CGPoint)的便捷方法;(code)

             

    extension CGRect {
        var center:CGPoint {//只读计算属性
            return CGPoint.init(x: self.midX, y: self.midY)
        }
    }

             ·扩展可以声明静态或类方法;

             由于对象类型通常是全局可见的,所以这是给全局函数指定恰当命名空间的绝佳方式;

             

             示例3:经常使用的一个颜色(UIColor),比将生成它的代码封装到全局函数中更好的方法是,使之成为UIColor的一个类方法;(code)

             

    extension UIColor {
        class func myGoldenColor() -> UIColor {
            return self.init(colorLiteralRed: 1.000, green: 0.894, blue: 0.541, alpha: 0.900)
        }
    }

             ·扩展的另一个用途是让内建的Cocoa类能够处理你的私有数据类型:可以通过在类的扩展中重载方法,使之能够处理我们希望的数据类型;

             

             ·对自定义对象类型的扩展有助于组织代码;

             经常应用的一个约定是为了对象类型需要使用每个协议添加扩展;(这种比较常用)

             

             ·在扩展Swift结构体时,我们可以声明一个初始化器,同时又保留隐式初始化器

             

    struct Digit {
        var number:Int
    }
    
    extension Digit {
        init() {
            self.init(number: 5)
        }
    }

            

            let di = Digit()//没有扩展到话就会报错
            print(di.number)//5

            

             有了扩展之后,你可以通过调用显示声明的初始化器Digit(),或是调用隐式初始化器Digit(number:5)来实例化一个Digit;

             因此,通过扩展显式声明的初始化器并不会导致隐式初始化器的丢失;(如果是在原来的结构体中声明了相同的初始化器,那么就会导致默认的隐式初始化器丢失的情况);

             

             4.10.2 扩展协议

             

             在协议扩展时,你可以向其中添加方法与属性,就行扩展对象类型那样;

             与协议声明不同,这些方法与属性并不仅仅要被添加协议使用者实现,它们还是要被协议使用者所继承的实际方法与属性;

             怎么理解呢,我们看个例子:

             

    protocol FlierEx {
        
    }
    extension FlierEx{
        func fly() {
            print("Flap flap flap!")
        }
    }

            struct BirdEx : FlierEx {
                
            }
            let bird1 = BirdEx()
            bird1.fly()//Flap flap flap!
            
            print(type(of: bird1))

            

             我们注意到BirdEx可以使用FlierEx而无须实现fly方法;

             即便fly方法作为一种要求添加到了FlierEx协议的声明中,BirdEx仍然可以使用FilerEx而无须实现fly方法;

             这是因为,FlierEx协议扩展支持了fly方法,fly方法同时会被BirdEx继承下来;

             使用者如果实现从协议扩展继承下来的方法的话,因此也可以重写这个方法;

             

             不过你要知道,这种继承不是多态:

                 使用者的实现并非重写;他只不过是另一个实现而已;

                 内在一致性原则并不适用;重要的是引用类型到底是什么?

                 虽然f本质上是一个BirdEx,但fly消息却是发送给类型为Flier的对象引用(如果不重新实现的话),因此调用的是fly方法的FlierEx实现而非BirdEx实现;

             

             要想实现多态继承,我们需要在原始协议中将fly声明为必须实现的方法:这样BirdEx才能实现其内在一致性;

                 这种差异有其现实意义,因此协议使用者并不会引入(也不能引入)动态分派的开销;

                 因此,编译器要做出静态的决定:如果方法在原始协议中声明为必须要实现的方法,那么我们就可以确保使用者会实现它,因此可以调用(也只能这么调用)使用者的实现;但如果方法只存在于协议扩展中,那么决定使用者是否重新实现了它就需要运行期的动态分布,这违背了协议的本质,因此 编译器会将消息发送给协议的扩展;

             

             协议扩展的主要好处在于可以将代码移到合适的范围中;比如多个枚举都需要相同的对外方法,那么就可以将方法统一到他们都遵循的一个协议的扩展中;

             

             在Swift标准库中,协议扩展使得很多全局函数都可以转换为方法;因为很多方法都是用的泛型约束到指定协议上,现在就可以通过协议扩展提供默认的实现;

             Swift2.0之后,有大量的全局函数都变成了方法,这种转变改变了语言的风格;

             

             4.10.3 扩展泛型

             

             扩展泛型类型时(请注意,扩展的是泛型类型),占位符类型名对于扩展声明来说是可见的;

             当然,这也会导致代码变得令人困惑,因为你看起来在使用未定义的类型,添加注释是个好主意:

             

    class DogEx<T> {
        var name:T?
    }
    
    extension DogEx {
        func sayYourname() -> T? {
            return self.name
        }
    }

            let dogex:DogEx<String> = DogEx()
            dogex.name = "haha"
            print(dogex.sayYourname() ?? "NIL")//haha

            

            泛型类型扩展可以使用一个where子句:这与泛型约束的效果是一样的,他会限制泛型的哪个解析者可以调用该扩展所注入的代码,并向编译器保证代码对于这些解析者来说是合法的;

             

             还记得之前介绍泛型类型约束时举的一个求多个可比较参数中最小值的例子吗?

             他像这样:

             func myMin<T:Comparable>(things:T...) -> T {

                 var minemum = things[0]

                 for ix in 0..<things.count {

                     if things[ix] < minemum {

                         minemum = things[ix]

                     }

                 }

                 return minemum

             }

             

             现在我们可以这样写:(基于占位符可见 以及 where子句可用)

     

    extension Array where Element:Comparable {
        func myMin() -> Element {
            var minium = self[0]
            for ix in 1..<self.count {
                if self[ix] < minium{
                    minium = self[ix]
                }
            }
            return minium
        }
    }

            let array = [2,4,1,5,6,3,6]
            print(array.myMin())//1

            

             该方法只能在Comparable元素的数组上调用;他不会注入到其他类型的数组中;

             

             Swift语言的这种变化导致了Swift标准库发生了大规模的重组,可以将全局函数移到结构体扩展与协议扩展中作为方法;

             

    4.11 保护类型

             

             Swift提供了几个内建的保护类型,他们可以通过一个声明表示多个实际类型;

             

             4.11.1 AnyObject

         

             实际开发中,最常用的保护对象就是AnyObject,他实际上是一个协议,没有属性也没有方法,是空白的;

             有个特别的特性:所有的类 类型都会自动遵循它;

             因此,在需要AnyObject 的地方,我们可以赋值或传递任何类实例,并且可以进行双向的类型转换;

             

             某些非类类型的Swift类型(如String和基本数字类型)都可以桥接到OC的类型上,他们是由Foundation框架定义的类类型;

             这意味着:在Foundation框架下,Swift桥接类型可以赋值、传递或转换为AnyObject,即便是非类类型也可以;这是因为背后,它会先被自动转换为相应的OC桥接类类型;

             AnyObject也可以向下类型装换为Swift桥接类型;

     

            let s_any :String = "flower"
            let any_s :AnyObject = s_any as AnyObject
            let s_any1 = any_s as! String
            print(s_any + (any_s as! String) + s_any1)
            
            let i_any = 1
            let any_i :AnyObject = i_any as AnyObject
            let i_any1 = any_i as! Int
            print(i_any,any_i,i_any1)

            

             我们常常会在与OC交换过程中遇到AnyObject;

             Swift可以将任何类类型转换为AnyObject;以及将AnyObject转换为类类型;(相当于Swift版本的id)

             

             注意,将Optional展开并将其从AnyObject向下类型转换是我们的职责;当然在使用as!对AnyObject进行向下类型转换时,要将其转换为正确的类型,否则将代码运行时程序会崩溃;如果不确定,可以使用is与as?运算符来确保装换是安全的;

             

             1.压制类型检查

             

             AnyObject一个令人惊叹的特性就是他可以将编译器对某条消息是否可以发送给某个对象的判断推迟;(OC中id类型会导致编译器对什么消息可以发送给他的判断)

             

             向AnyObject发送的消息需要满足如下条件之一:

             1)它是OC类的成员;

             2)他要是你自己定义的OC类的Swift子类(或扩展)的成员;

             3)他要是Swift类的成员,并且标记为@objc(或dynamic);

             类似于之前介绍的可选协议成员,但是有些区别;

             

             注:这种延迟判断已经不支持了,Swift要求明确的对象类型才能进行消息的发送;

     

            class DogAny{
                @objc var noise:String = "woof"
                @objc func bark() -> String{
                    return "woof"
                }
            }
            class CatAny{
                
            }
            let c_any:AnyObject = CatAny()
            let d_any:AnyObject = DogAny()
            
            print(c_any,type(of: c_any))
            
            //print(c_any.noise)//编译器报错
            //print(d_any.noise)//编译器报错
            

            

             2.对象恒等性与类型恒等性

             

             对于应用类型(值类型不会出现这种情况),如果需要知道对象本身是否是你所认为的那个特定的对象;Swift的解决方案是恒等运算符(===);

             该运算符可用于使用AnyObject协议的任何对象类型实例;他会比较对象引用,判断两个对象引用是否指向了相同的对象;(注意不是在比较值)

             

             和==一样,===运算符可与Optional无缝连接(因为它也是一种比较运算符);

             

             4.11.2 AnyClass

             

             AnyClass是AnyObject对应的类,它对应于OC的Class类型;在Cocoa API的声明中如果需要一个类,就会用到AnyClass;

             实际应用中,想要在自己的实现中返回一个实际的类,请向类名发送self消息;

             

    class TestAnyClass {
                class var whatDogSays:String {
                    return "woof"
                }
                class func testAnyClass() -> AnyClass {
                    return DogAny.self
                }
            }
            print(TestAnyClass.testAnyClass())//DogAny #1
            
            let c_anyClass:AnyClass = TestAnyClass.self             //TestAnyClass.self 是引用
            print((c_anyClass as! TestAnyClass.Type).whatDogSays)   //TestAnyClass.Type 是类型
                    

            

             对类的引用可以通过函数type(of:)获得,也可以通过向类型名发送self获得;

             

             类的引用类型使用了AnyClass,可以通过===运算符比较这种类型的引用;

             

            class SubTestAnyClass:TestAnyClass{
                
            }
            let ddd = TestAnyClass()
            let ddds = SubTestAnyClass()
            func typeTester(t:TestAnyClass , whattype:TestAnyClass.Type) -> Bool{
                if type(of: t) === whattype {
                    return true//true
                }else {
                    return false
                }
            }
            
            print(typeTester(t: ddd, whattype: TestAnyClass.self))//true
            print(typeTester(t: ddds, whattype: TestAnyClass.self))//false
            


             

             4.11.3 Any

             

             Any类型是被所有类型自动使用的一个空协议的类型别名;这样,在需要一个Any对象的地方,你可以使用任何对象;

             类型为Any的对象可以与任何对象或函数类型进行比较,也可以向下类型转换为它们;

             

             4.12 集合类型

             

             Swift拥有内建的集合类型:Array Dictionary Set;

             Swift为其提供的函数有限,不足的通过Cocoa的NSArray与NSDictionary补充;

             Array与NSArray Dictionary与NSDictionary彼此桥接, Set与NSSet桥接;

             

             4.12.1 Array         

             

             数组(Array,是一个结构体)是对象实例的一个有序集合(即数组元素),可以通过索引号进行访问,索引号是个Int,值从0开始;

             Swift数组不可能是稀疏数组:如果有一个元素索引为3,那么肯定还有一个元素索引为2,以此类推;

             

             Swift数组最显著的特征就是其严格的类型;数组必须包含相同类型的元素;甚至连空数组都必须要有确定的类型,尽管此时数组中并没有元素的存在;

             数组类型与元素类型一样也是多态的:如果NoisyDog是Dog子类,那么NoisyDog的数组就可以用在需要Dog数组的地方;

             

             Swift数组也是泛型,被声明为Array<Element>,其中占位符Element是特定数组元素的类型;

             一个数组中的元素类型限制可以是:

             1)父类数组,子类、父类实例;

             2)协议数组,使用者实例;

             3)AnyObject数组,类以及Swift桥接类型的实例;

             4)类型本身也可能是不同的可能类型的载体;本章之前介绍的Error枚举(关联String和Int)就是一个例子,这样Error元素的数组就可能包含Int值与String值;

             

             要声明或给定数组元素的状态,应该显示解析出泛型占位符;如Array<Int>;

             Swift提供了语法糖来表示数组的元素类型,通过将方括号包围元素类型来表示,如[Int];

             

             字面数组表示为方括号中逗号间隔的元素列表,[]表示空数组的字面值;

             

             数组初始化:

             1)数组默认的初始化器可以在数组类型后使用()调用;

     

            let arr1 = [Int]()
            print(arr1)//[]
            //也可以这样
            let arr2 :[Int] = []
            print(arr2)//[]


             2)数组又一个初始化器,其参数是个序列;这意味着,如果类型是个序列,你可以将其实例分割到数组元素中;

             

            print(Array("flower".characters))//["f", "l", "o", "w", "e", "r"]
    

             3)另一个数组初始化器 init(count: repeatValue:) 可以使用相同的值来装配数组;

     

     

            let strings:[String?] = Array.init(repeating: nil, count: 5)
            print(strings)//[nil, nil, nil, nil, nil]
           

            

             这是Swift中最接近稀疏数组的数组创建方式:有5个槽,每个槽可能包含或不包含一个字符串(一开始都不包括);

             

             当然还有很多其他的初始化方式,可自行查看API;

             

                1.数组转换与类型检测

     

             将一个数组类型赋值,传递或装换为另一个数组类型时,你操作的实际上是数组中的每个元素;

             1)let arr:[Int?] = [1,2,3]//将Int数组看做包装了Int的Optional数组意味着原始数组中的每个Int都要包装到Optional中;

             2)如下示例:

     

            class ElephontSuper{
                
            }
            class Elephont:ElephontSuper{
                
            }
            let ele1:ElephontSuper = Elephont();
            let ele2:ElephontSuper = Elephont();
            let elearr1 = [ele1,ele2];
            let elearr2 = elearr1 as! [Elephont];
            
            print(elearr2);//Elephont...

            

             我们将数组向下类型转换为它的子类型,这意味着我们将原来数组中的每个ElephontSuper都转换为了Elephont;

             3)可以将数组的所有元素与is运算符进行比较来判断数组本身;

     

           if elearr1 is [Elephont] {
                print("sonthing")//sonthing
            }
            if elearr2 is [Elephont] {
                print("sonthing")//sonthing
            }

            

             如果数组中的每个元素都是Elephont,那么结果就是true;

             4)与之类似:as?运算符会将数组装换为包装了数组的Optional,如果底层的转换无法进行,那么结果就是nil;

            let ele3:ElephontSuper = ElephontSuper()
            let elearr3 = [ele2,ele3]
            
            let eleasarr1 = elearr1 as? [Elephont]
            print(eleasarr1 ?? "err-Null")//Elephont #1...
            
            let eleasarr2 = elearr3 as? [Elephont]
            print(eleasarr2 ?? "err-Null")//err-Null
            

            

             注意是as?所在表达式的结果为nil;

             

             2.数组比较

             

             数组相等:如果两个数组包含相同数量的元素,并且相同位置上的元素全都相等,那么这两个数组就是相等的;

             比较两个数组,两个数组不必非得是相同类型的,可以是子父类关系,不过除非它们包含的对象都彼此相等,否则这两个数组就不会相等;

             

             3.数组是值类型

             

             数组是一个结构体,因此他是个值类型,这意味着每次将数组赋值给变量或作为参数传递给函数时,数组都会被复制;

             (Swift设计者已经考虑到了这种复制对性能的影响,并且在背后高效的进行了实现)

             

             虽然数组本身是一个值类型,但是元素会按照元素本身的情况对待;

             特别的:如果一个数组是类实例的数组,将其赋给多个变量,那么结果就是会有多个引用指向相同的实例;

             

             4.数组下标

             

             Array结构体实现了subscipt方法,可以通过对数组的引用后使用方括号来访问元素;

             1)可以在方括号中使用Int;

             2)可以在方括号中使用Int的Range;

             array[1...2],技术上会生成一个ArraySlice,叫做切片,类似数组,也可以使用下标,在需要数组的地方也可以传递ArraySlice过去;

             

             修改元素:

             1)如果对数组的引用是可变的(var),那么就可以对下标表达式赋值,这么做会改变槽中的值;

             2)如果下标是个范围,那么赋值就必须是一个数组;这会改变被赋值的数组长度;

            var testarray1 = [1,2,3,4]
            testarray1[1..<2] = []
            print(testarray1)//[1, 3, 4]
            

             如果访问数组元素时使用的下标大于最大值或小于最小值,会产生运行时错误,程序会崩溃;

             

             5.嵌套数组

             

             数组元素也可以是数组;

            let testarray2:[[Int]] = [[1,2],[2,3,4],[3,4,5,6]]
            print(testarray2)//[[1, 2], [2, 3, 4], [3, 4, 5, 6]]

             上面示例:类型声明是[[Int]],被包含的数组长度可不同;

             可以使用arr[i][j]的方式来访问;

             如果是var修饰的数组,就可以赋值了;

             

             6.基本的数组属性与方法

             

             数组是一个集合(CollectionType协议),集合本身又是个序列(SequenceType协议);

             1)-count只读属性:返回数组元素个数;

             2)-isEmpty属性;

             3)-first和last只读属性:返回第一个和最后一个元素,不过会被包装到Optional中,因为可能没有;

             注:这里会出现Swift中较少遇见的将一个Optional包装到另一个Optional中的情况;比如,考虑包装Int的Optional数字,回去它的last;

            let testarray3:[Int?] = [1,2,3]
            print(type(of: testarray3.last))//Optional<Optional<Int>>

             4)-数组可访问的最大索引比count小1;

             5)-如果想要访问数组的最后n个元素,可以使用suffix方法;类似的还有prefix;他们有一个指的注意的特性,那就是超出范围后并不会报错;

            let testarray4 = [1,2,3]
            print(testarray4.suffix(10))//[1, 2, 3]

             6)-相对于通过数量来描述后缀或前缀的大小;你可以通过索引来表示后缀或前缀的限制;

            let testarray5 = [1,2,3]
            let testarray6 = testarray5.suffix(from: 1)//从索引1到最后的元素(包括当前索引)
            let testarray7 = testarray5.prefix(upTo: 1)//从开始到索引1的元素(不包括到达索引)
            let testarray8 = testarray5.prefix(through: 1)//从开始到索引1的元素(包括到达索引)
            print(testarray6,testarray7,testarray8)//[2, 3] [1] [1, 2]
            print(testarray5.indices,type(of:testarray5.indices))//0..<3 CountableRange<Int>

             7)-数组的startIndex属性值是0,其endIndex属性值是其count;

             8)-数组的indices属性值是一个半开区间,端点分别是startIndex和endIndex,即访问整个数组的范围;

             

             9)-indexOf方法会返回一个元素在数组中首次出现位置处的索引,不过他被包装到了Optional中。如果数组不存在这个元素就会返回nil;

             10)-index(where:)还可以接收一个函数,这个函数接受一个元素类型并返回一个Bool,会得到返回true的第一个元素;

             

             11)-作为序列,数组的contains方法会判断它是否包含了某个元素;类似于上边的,contains也可以提供自定义函数,同样接受一个元素,返回一个布尔;

             

             12)-startWith方法判断数组的起始元素是否与给定的相同类型序列的元素相匹配;也可以接收自定义函数,该函数接收两个数组元素的类型值,并返回表示他们是否匹配的Bool;

           print(testarray5.starts(with: [1,2]))//true
            print(testarray5.starts(with: [1,-2], by: ({abs($0) == abs($1)})))//true

             13)-elementEqual方法是数组比较的序列泛化:两个序列长度必须相同,其元素要么是Equatables,要么是自己提供的元素;

             14)-min Element与max Element方法分别返回数组中最小与最大的元素;并且包装到了Optional中;

           var testarray9 = [3,1,-2]
            print(testarray9.min())//Optional(-2)
            print(testarray9.min(by: {abs($0) < abs($1)}) ?? "err-null")//1

             15)-append与appendContentsOf实例方法,在数组引用可变的情况下,会将元素添加到数组末尾;append接收单个元素,后者接收一个序列;

             16)-若+运算符左侧是个数组,那么+会被重载,行为类似appendContentsOf,只不过他会生成新的数组,这种即便数组是常量也是可行的;

             17)-数组引用可变时,实例方法insert(element: ,at:)或insert(contentsOf:Collection , at:Int)可以在指定索引处插入一个元素或多个元素;

             18)-数组引用可变时,可以调用remove相关方法:

                 removeAtIndex会删除指定所引出的元素;removeLast会删除最后一个元素,removeFirst会删除第一个元素;这些方法还会返回从数组中删除的值;这些方法不会讲返回值包装到Optional中,访问越界的索引会导致程序崩溃;testarray9 .removeLast(n),这是另一种形式,可以指定删除元素个数,不过他不返回值;如果数组中没有那么多元素,程序将崩溃;

             19)-popFirst和popLast会展开Optional中的返回值;这样即便数组为空也是安全的;如果引用不可变,那么可以通过dropFirst与dropLast方法返回删除了最后一个元素后的数组(实际是个切片);

           print(testarray9.popLast()) //Optional(-2)
            print(testarray9)   //[3, 1]
            testarray9.append(contentsOf: [1,2,3,4,5])
            print(testarray9.dropFirst())   //[1, 1, 2, 3, 4, 5]
            print(testarray9.dropLast())    //[3, 1, 1, 2, 3, 4]
            print(type(of: testarray9.dropLast()))//ArraySlice<Int>

             20)-joined(separator:)实例方法接收一个数组的数组,他会提取出每个元素,并将参数数组中的元素插入到提取出的每个元素序列之间;结果是一个叫JoinSequence的中间序列,如果必要,还要将他转换为Array;

                 调用joined(separator:)时将空数组作为参数可以将数组的数组打平;

     

            let arrj1 = [[1,2],[3,4],[5,6]]
            print(type(of: arrj1.joined(separator: [9,9])))
            let arrj2 = Array(arrj1.joined(separator: [9,9]))
            let arrj3 = Array(arrj1.joined(separator: []))
            print(arrj2)//[1, 2, 9, 9, 3, 4, 9, 9, 5, 6]
            print(arrj3)//[1, 2, 3, 4, 5, 6]

             21)-reversed实例方法会生成一个新数组,其元素顺序与原始数组的相反;

             22)-sort会对可变数组本身进行排序,sorted会返回一个排好序的新数组;可以提供一个比较函数,返回bool值,表示第一个参数是否应该在第二个参数之前;

            testarray9.sort()//[1, 1, 2, 3, 3, 4, 5]
            testarray9.sort(by:>)//[5, 4, 3, 3, 2, 1, 1]
            print(testarray9)

             23)-split实例方法会在 通过测试的元素位置处 将一个数组分解为数组切片的数组;通过测试的元素会被去除,这里的测试可以是一个元素也可以是一个函数;

                print(testarray9.split(separator: 3))//[ArraySlice([5, 4]), ArraySlice([2, 1, 1])]


             7.数组枚举与转换

             

             数组是一个序列,因此你可以对其进行枚举,并按照顺序查看或操作每个元素;

             

             for...in:

             forEach:

                 forEach实例方法,其参数是一个函数,该函数接收数组(或是其他序列)

             enumerate:

                 如果既需要索引号又需要元素,可以调用enumerate实例方法对结果进行循环;每次迭代得到的将是一个元组;

     

           let perboys = ["Manny","Moe","Jack"]
            for perboy in perboys {
                print(perboy)
            }
            perboys.forEach{print($0)}
            
            for (idx,perboy) in perboys.enumerated() {
                print(idx,perboy)
            }
            perboys.enumerated().forEach{print($0.0,$0.1)}//0 Manny   1 Moe   2 Jack

             Swift还提供了3个强大的数组转换实例的方法,和forEach一样这些方法都会枚举数组,这样循环就被隐藏到了方法调用中,代码也变得更加紧凑和整洁;

             

             map实例方法:

                 他会生成一个新的数组,数组中每个元素都是将原有数组中相应的元素传递给你所提供的函数进行处理后的结果;

                 该函数接收一个元素类型的参数,并返回可能是其他类型的结果;Swift通常会根据函数返回的类型推断出生成的数组元素的类型;

            let arrm1 = [1,2,3]
            let arrm2 = arrm1.map{$0 * 2}
            print(arrm2)//[2, 4, 6]

             实际上,map是CollectionType的一个实例方法,Range类型本身也是CollectionType类型,因此(0..<10).map{}的调用也是合法的;

             

             filter实例方法:

                 filter实例方法也会生成一个新数组,新数组中的每个元素都是老数组中的元素,顺序也相同;只不过,老数组中的一些元素会被过滤掉;

                 起过滤作用的是你所提供的函数;它接受一个元素类型的参数并返回一个Bool,表示这个元素是否应该被放到新数组中;

     

            let arrf1 = ["Manny","Moe","Jack"]
            let arrf2 = arrf1.filter{$0.hasPrefix("M")}
            print(arrf2)//["Manny", "Moe"]

             reduce实例方法:

                 这是一种将数组中(实际上是序列)所有元素合并为单个值的方式;这个值的类型(结果类型)不必与数组元素类型相同;

                 你提供一个函数,接受两个参数:第一个是结果类型,第二个是元素类型,结果是这两个参数的组合,他们作为结果类型;

                 没次迭代都会作为下一次迭代的第一个参数,同时数组的下一个元素会作为第二个参数;组合对不断累计的输出,以及最终的累计值就是reduce函数的最终结果;

                 你需要自己提供reduce调用的第一个参数;

            //求数组和
            let arrr1 = [1,23,44,5,6,77]
            let result = arrr1.reduce(0){$0 + $1}
            print(result)//156
            //更简洁的写法
            let result1 = arrr1.reduce(0, +)
            print(result1)//156

             我们接着举一个复杂一点的例子 会结合着三个实例方法一起使用:

            let arrs1 = [["Manny","Moe","Jack"],["Harpo","Chico","Groucho"]]
            let target = "M"
            let arrs2 = arrs1.map{
                $0.filter{
                    return $0.range(of: target, options: String.CompareOptions.caseInsensitive, range: nil, locale: nil) != nil
                }
            }.filter{$0.count > 0}
            print(arrs2)//[["Manny", "Moe"]]
            print(arrs2.reduce([],+))//["Manny", "Moe"]


             8.Swift Array与Objective-C NSArray

             

             在编写iOS应用时,你会导入Foundation框架(或是UIKit,因为它会导入Foundation),它包含了OC NSArray类型;

             Swift的Array类型与Objective-C的NSArray类型是桥接的;不过,前提是数组中的元素类型可以桥接;

             OC NSArray中元素,不必是同类型,但必须是对象,因为只有对象才能被OC所理解;

             

             一般来说,如果类型能够向上转换为AnyObject(这意味着他是个类类型),或者如Int,Double及String这样特殊的桥接结构体,那木才可以桥接到OC;

             

             Swift数组传递给OC很简单:要么通过赋值,要么作为函数调用的实参;(直接用即可)

             Swift数组上调用NSArray方法,需要将其转换为NSArray;

           let arrn1 = ["Manny","Moe","Jack"]
            let sarrn1 = (arrn1 as NSArray).componentsJoined(by: ",")
            print(sarrn1)//Manny,Moe,Jack

             可变数组:

                 Swift数组可以通过var引用看出;要想在OC中获得可变数组,你需要NSArray的子类NSMutableArray;

                 你不能将Swift数组通过类型转换、赋值或传递的方式赋给NSMutableArray,必须强制进行,最佳方法就是调用NSMutableArray的初始化器init(array:),你可以直接向其传递一个Swift数组;

            var arru1 = ["Manny","Moe","Jack"]
            print(arru1);//["Manny", "Moe", "Jack"]
            let arru2 = NSMutableArray(array:arru1)
            arru2.remove("Moe")
            arru1 = arru2 as NSArray as! [String]
            print(arru2);
            /*
            (
                Manny,
                Jack
            )
             */
            print(arru1);//["Manny", "Jack"]

             将NSMutableArray转换会Swift数组只需直接转换即可;如果需要一个拥有原始Swift类型的数组,那木需要转换两次才能通过编译;

             

             手工“桥接”数组元素:

                 如果Swift对象类型不能向上转换为AnyObject,那就无法无法桥接到OC;如果需要一个NSArray,但你传递了一个包含这种类型的Array,那么编译器就会报错;

                 这种就需要手工“桥接”数组元素;

             

             比如,Swift中的CGPoint结构体数组,就不能被放到NSArray中,如果在NSArray的地方传递了这个数组,那就会导致编译错误:[CGPoint]is not convertible to NSArray;

             解决办法就是:将每个CGPoint包装为NSValue,这是个OC对象类型,专门用作各种非对象类型的载体;

            let arrCGPoints = [CGPoint.init(x: 0, y: 0),CGPoint.init(x: 0, y: 1)]
            let arrNSValues = arrCGPoints.map{ NSValue.init(cgPoint: $0) }
            print(arrNSValues)//[NSPoint: {0, 0}, NSPoint: {0, 1}]

             另一种情况是Swift的Optional数组;OC集合不能包含nil(OC中,nil不是对象);

             在需要NSArray时如果传递Optional数组,那就需要事先对这些Optional进行处理;如果Optional包装了值,那么你可以将其展开;但如果没有包装值,就无法将其展开;

             一种解决办法:

                 采取OC中的做法:OC NSArray不能包含nil,因此Cocoa提供了一个特殊的类NSNull,当需要一个对象时,其单例NSNull()可以代替nil;

                 这样,如果有一个包装了String的Optional数组,那么我可以将那些不为nil的元素展开,并使用NSNull()代替nil元素;

           let arroc1:[String?] = ["flower",nil,"wohaha"]
            let arroc2:[AnyObject] = arroc1.map{ if $0 == nil {return NSNull()}else {return $0! as AnyObject} }
            print(arroc1)//[Optional("flower"), nil, Optional("wohaha")]
            print(arroc2)//[flower, <null>, wohaha]

             将NSArray从OC传递给Swift时会发生什么:

                 跨越桥接不会有问题:NSArray会安全地变成Swift Array;但是,就其本身来说,NSArray并没有携带关于它所包含的元素类型的任何信息;

                 因此,默认是OC NSArray转换为Swift的AnyObject数组(使用是,需要进行向下类型转换);

             庆幸的是,Xcode7之后,OC语法发生了变化:

                 NSArray,NSDictionary,NSSet(这3种集合类型会桥接到Swift)的声明已经包含了元素类型信息,OC称之为轻量级泛型;

                 ios9中,Cocoa的API也包含了这些;这样,多数情况下,从Cocoa接收到的数组都是带有类型的;

             

             

             4.12.2 Dictionary

             

             字典(Dictionary,是个结构体)是成对对象的无序集合;

                


    展开全文
  • Swift 4.0 学习笔记

    示例代码来源于 《iOS 11 Programming Fundamentals with Swift》

    概览

    语句分隔符

    Swift的语句是可以通过分析断句的,如果一个语句结束后换行就开始下一个语句,如果写了分号也表示结束这个语句,但是有了换行就不需要分号了,写上也没有问题。如果一个语句没有结束,换行没有实际效果。下边的代码都是合法的:

    print("hello")
    print("world")
    
    print("hello"); print("world")
    
    print("hello");
    print("world");
    
    print(
        "world")

    注释

    依旧是://和//,其中/* … */可以嵌套

    对象的类型

    Swift中一切都是对象。按照类型划分,有6种:

    • struct, Bool,Int, Double, String, Array,Dictionary等
    • enum, Optional类型
    • class, 用户自定义类型。这篇文章提到了Swift中有3个预定义的class类型,但是没有指出是哪个。

    • protocol,ExpressibleByIntegerLiteral

    • tuple,函数返回多值时使用。
    • function,print,自定义函数。

    举个例子:

    字面变量是对象:

    let s = 1.description

    #### 值类型和引用类型

    按照内存管理来划分,Swift对象有值类型和引用类型,值类型在赋值的时候是copy的(不考虑Swift优化),引用类型是共享内存的。

    官方文档对值类型和引用类型的解释:

    Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.

    在Swift中,class和function是引用类型,struct,enum,tuple都是值类型。protocol本身不允许有实例,但是采用protocol的可以是struct, enum或者class。

    数据类型

    变量与常量

    let one = 1
    var two = 2

    使用let声明的是常量,使用var声明的是变量

    类型推断

    从上面例子可以看出,如果在声明变量的时候就赋值,有时候是可以不写类型的,让编译器推断。上文中one和two都是Int类型。

    那么什么时候需要些类型呢?

    • 只声明,不初始化。
    var x : Int
    • 想要的类型和推断的类型不符合
    let separator : CGFloat = 2.0
    • 不能推断出类型
    let opts : UIViewAnimationOptions = [.autoreverse, .repeat]
    • 还有一种情况是提醒自己这个变量是啥类型
    let duration : CMTime = track.timeRange.duration

    基本类型用法

    Bool

    • Bool是一个struct类型
    • 只有true和false两个值,不能做它解释。

    Int

    • Int是struct类型
    • Int的取值在Int.max和Int.min之间,平台相关

    Double

    • Double是struct类型
    • 64位架构处理器上,Double的精度是15位
    • Double的边界是Double.infinity,还有Double.pi等
    • 使用isZero来判断Double是否为0

    数字类型转换

    只有字面变量可以被隐式转换!

    let d : Double = 10

    将字面变量10转换成了Double类型,但是变量就不可以,下列的代码不能通过编译:

    let i = 10
    let d : Double = i // compile error

    正确的写法是:

    let i = 10
    let d : Double = Double(i)

    String

    let str = "Hello World" //欧耶,终于不用写@了

    多行字面变量的写法:

    func f() {    
        let s = """    
        Line 1        
            Line 2    
        Line 3    
        """    
        // ...
    }
    
    func f() {    
        let s = """
        Line "1"        
            Line 2 \    
        and this is still Line 2    
        """    
        // ...
    }

    在String字面变量中使用(…)来计算表达式

    let n = 5
    let s = "You have \(n) widgets."

    String支持+号和+=号

    let s = "hello"
    let s2 = " world"
    let greeting = s + s2

    String的utf8编码:

    let s = "\u{BF}Qui\u{E9}n?"for i in s.utf8 {   
        print(i) // 194, 191, 81, 117, 105, 195, 169, 110, 63
    
    }

    String和数值的转化:

    let i = 7
    let s = String(i) // "7"
    
    let i = 31
    let s = String(i, radix:16) // "1f"

    Range

    Range是一个struct。 字面变量: a…b表示区间[a, b] a..< b表示区间[a, b)

    最常见的就是在for循环中使用:

    for ix in 1...3 {    
        print(ix) // 1, then 2, then 3
    }

    Range 有实例方法:

    let ix = // ... an Int ...
    if (1...3).contains(ix) { // ...
    
    let s = "hello"
    let ix2 = s.index(before: s.endIndex)
    let s2 = s[..<ix2] // "hell"

    Tuple

    tuple是一个有序的轻量级的collection。

    tuple的声明:

    var pair : (Int, String)

    初始化:

    var pair : (Int, String) = (1, "Two")
    var pair = (1, "Two")

    tuple可以同时给多个变量赋值:

    let ix: Int
    let s: String
    (ix, s) = (1, "Two")

    tuple在for-in中的应用:

    let s = "hello"
    for (ix,c) in s.enumerated() {
        print("character \(ix) is \(c)")
    
    }

    对Tuple中值的引用:

    let pair = (1, "Two")
    let ix = pair.0 // now ix is 1

    如果在声明的时候给值一个label,可以通过label引用:

    let pair : (first:Int, second:String) = (1, "Two")
    //or: let pair = (first:1, second:"Two")
    
    var pair = (first:1, second:"Two")
    let x = pair.first // 1
    pair.first = 2
    let y = pair.0 // 2

    还可以给Tuple起一个别名

    typealias Point = (x:Int, y:Int)
    
    func piece(at p:Point) -> Piece? {    
        let (i,j) = p    
        // ... error-checking goes here ...    
        return self.grid[i][j]
    
    }

    可选类型

    Swift中变量如果不初始化是不能使用的。这点和OC不同,OC中值类型会有一个默认值,引用类型默认为nil。Swift中如何表示nil呢?答案就是Optional(可选类型)

    Optional类型的底层是enum类型,可以包装一个其他类型,具体内部实现这里不讨论。
    比如:

    var stringMaybe = Optional("howdy")

    就定义了一个包装了String的Optional类型。包装不同类型的Opational也是不同的类型,不能互相赋值。Optional(String)类型可以简写为String?

    如果没有给Optional的变量装箱一个值,那么它就是空的,空的Optional变量可以和nil比较:

    var stringMaybe : String? = "Howdy"
    print(stringMaybe) // Optional("Howdy")
    if stringMaybe == nil {    
        print("it is empty") // does not print
    }
    stringMaybe = nilprint(stringMaybe) // nil
    if stringMaybe == nil {    
        print("it is empty") // prints
    }

    在Swift中nil是一个关键字,不是一个值,可以将nil赋值给Optional的类型。

    自动装箱,将一个值直接值给包装它的Optional类型。

    var stringMaybe: String? = "farewell

    根据自动装箱机制,可以在任何需要Optional类型的地方传入原始类型,但是反过来不行。

    let stringMaybe : String? = "howdy"
    let upper = stringMaybe.uppercased() // compile error

    不能给Optional类型直接发送消息,需要拆箱得到原始数据。

    拆箱

    let stringMaybe : String? = "howdy"
    let upper = stringMaybe!.uppercased()

    在变量后边加上叹号,就拆箱得到原始类型。

    自动拆箱,在定义变量的时候使用!而不是?就定义了一个自动拆箱的Opational变量,在需要使用原始类型的地方,直接传入自动解包的Opational变量即可。

    func realStringExpecter(_ s:String) {}
    var stringMaybe : String! = "howdy"
    realStringExpecter(stringMaybe) // no problem

    注意,如果自动解包的Optional是nil,会引起Crash。不能给一个是nil的Optional类型解压,这是Swift最重要的规则之一。 所以,如果不是必须,最好不要使用这个特性,因为这样就失去了Swift中可选类型的安全特性。

    !定义的Optional和?定义的Optional是同一个类型,比如self.view是一个UIView!,但是如下代码却产生编译错误。

    var stringMaybe : String! = "howdy"
    var anotherStr = stringMaybe //ok
    var pureStr: String = stringMaybe //ok
    var errStr: String = anotherStr // compile error

    stringMaybe是自动拆箱的String?,所以赋值给String类型是可以的;但是anotherStr却没有自动拆箱的标志,仅仅是一个String?,所以不能赋值给String类型。

    Optianal Chain是Swift中很重要的一个概念。

    拆箱nil会引起Crash,那么如果每次拆箱都得判断是否为nil,代码就会很难看。于是Swift提供了语法糖:

    var stringMaybe : String?
    // ... stringMaybe might be assigned a real value here ...
    let upper = stringMaybe?.uppercased()

    在拆箱的时候,不用!而是用?,这叫做选择性拆箱。英文很有意思:unwarp the Optional optionally。

    选择性拆箱实际上替你做了判断工作,就是如果stringMaybe是nil,那么什么也不做,如果不是nil,拆箱得到String,然后发送uppercased消息。

    这很好,但是如果“什么也不做”返回值upper是啥?答案是nil。那么nil是不能赋值给String类型的,于是又引入一个规则:

    如果一个Optional Chain上有一个可能的Optional的类型(选择性拆包才有),那么返回值就是Optional的

    也就是说虽然uppercased方法返回的是String类型,但是因为它在一个Optional Chain中,所以返回值自动被装箱,成为String?类型。一个Optional Chain返回一个Optional的类型也合情合理。

    因为自动装箱,给一个Opational Chain赋值会比较简单。

    // self is a UIViewController
    self.navigationController?.hidesBarsOnTap = true

    同样,如果navigationController是nil,什么也不会发。那么如何知道赋值成功了呢?

    let ok : Void? = self.navigationController?.hidesBarsOnTap = true

    如果ok不是nil,就是赋值成功。

    Optional类型是可以和原始类型直接比较的。下边的代码没有问题。

    let s : String? = "Howdy"
    if s == "Howdy" { // ... they _are_ equal!

    如果s是nil,返回false,如果s不是nil,拆箱之后再和”Howdy”比较。

    但是不能比较不等关系,下边的代码是不能通过编译的:

    let i : Int? = 2
    if i < 3 { // compile error

    因为Swift不能确定如果是nil,结果是什么。

    函数

    函数的定义

    func sum (_ x:Int, _ y:Int) -> Int {
        let result = x + y
        return result
    }
    • func 是keyword,sum是函数名。
    • 括号内部是参数,参数标签,变量名,冒号后是类型。和OC结构一样。
    • -> Int表示返回值是Int类型。如果函数返回Void,可以写成->()
    • 函数体在大括号内部。
    • 参数前边的”_”符号表示忽略参数的标签。

    参数标签

    func echoString(_ s:String, times:Int) -> String {
        var result = ""
        for _ in 1...times { result += s }
        return result
    }

    times就是参数的外部名字(external name),也可以叫参数标签。这是和OC语言的参数名字和变量名字分开是一致的。

    调用的代码应该是这样:

    let s = echoString("hi", times:3)
    • 默认的,参数的变量名(internal name)就是参数标签(external name)。
    • 如果使用了_,表示没有标签,调用方也不能使用标签调用了。
    • 具有相同的函数签名,但是参数标签不同的函数,是两个不同的函数。

    函数参数默认是不可变的。意思是,不能在函数中给函数参数再次赋值。对于引用类型,是可以改变内部属性的。

    func say(_ s:String, times:Int, loudly:Bool) {
        loudly = true // compile error
    }

    如果想要重新给参数赋值需要满足以下几个条件:
    - 给函数参数添加intout关键字
    - 传入的变量应该是var而不是let的
    - 传入变量的地址。

    func removeCharacter(_ c:Character, from s: inout String) -> Int {
        var howMany = 0
        while let ix = s.index(of:c) {
            s.remove(at:ix)
            howMany += 1
        }
        return howMany
    }

    调用

    var s = "hello"
    let result = removeCharacter("l", from:&s)

    Swift中函数是first-class object,意思是函数可以赋值给变量,可以作为函数的参数和返回值。

    func doThis(_ f:() -> ()) {
        f()
    }
    
    func whatToDo() { 
        print("I did it")
    }
    
    doThis(whatToDo) 

    函数是first-class object这一特点可以衍生出很多编程模式,装饰器,偏函数,函数工厂等等。

    class, struct & enum

    概览

    enum,struct在Swift中和class很像,都可以定义方法,初始化函数等,但是有两个重大的区别:
    - enum,struct是值类型,class是引用类型
    - enum,struct不能继承

    在这3种类型中,可以有的结构是:
    - 初始化函数。
    - 属性,分为成员属性和类属性。对于struct和enum用static关键字,对于class用class关键字。
    - 方法,成员方法和类方法。
    - 下标(subscripts)
    - 嵌套定义(值类型的不能嵌套自己的类型)。

    在Swift中没有一个像NSObject那样的公共基类。

    class

    初始化方法

    由于Swift中不允许使用未经初始化的变量,并且想在编译阶段强制的保证这一点。于是对于class类型的初始化,引入了很多规则。虽然规则条数很多,但都是围绕这一个原则:从初始化函数中返回的对象的所有属性也是初始化的,并且在初始化完成之前不能使用这个对象

    • 初始化函数必须初始化所有未初始化的属性
    class Dog {
        let name : String
        let license : Int
        init(name:String = "", license:Int = 0) {
            self.name = name
            self.license = license
        }
    }

    如果删除self.license = license,将会产生编译错误,因为license没有初始化。

    • 在初始化所有属性之前,不能使用self
    class Cat {    
        var name : String    
        var license : Int    
        init(name:String, license:Int) {        
            self.name = name        
            meow() // too soon - compile error        
            self.license = license    
        }    
    
        func meow() {        
            print("meow")    
        }
    
    }

    meow()实际上隐式的使用了self,即self.meow()。应该将meow()的调用放到最后。

    如果初始化函数之间发生调用关系,初始化函数就分成了两类:designated initializer 和convenience initializer。

    designated initializer就是能独立完成对象的初始化的初始化函数,而convenience initializer必须直接或者间接的调用designated initializer来完成初始化工作。

    class Dog{
        var name: String
        var age: Int
    
        init(){
            self.name = "test"
            self.age = 10
        }
    
        convenience init(name:String){
            self.init(name: name, age: 10)
        }
    
        init(name: String, age: Int){
            self.name = name
            self.age = age
        }
    }

    在class中designated initializer不需要特别指明,但是convenience initializer必须使用convenience关键字。(这一条只是对class来讲,如果把class换成struct就不需要使用convenience,这和class是能继承有关系,稍后会介绍到继承)

    这又有一条规则: convenience initializer在使用self之前,必须调用designated initializer

    举个例子:

    class Dog{
        var name: String
        var age: Int
    
        init(){
            self.name = "test"
            self.age = 10
        }
    
        convenience init(name:String){
            self.age = 11 
            self.name = "haha"
            self.init(name: name, age: 10)
        }
    
        init(name: String, age: Int){
            self.name = name
            self.age = age
        }
    }

    上边的代码会发生编译错误,因为convenience初始化函数中在self被designated initializer初始化之前就使用了self。从这一点上看,convenience initializer并不是一个真正的初始化函数,只是能提供初始化功能的一般函数。

    在高级篇介绍的继承体系中,会有更复杂的初始化规则。不过如果你违反了这些规则,编译器都会提示的很清楚。只要理解这些规则的目的都是确保对象被完全初始化即可。

    属性(对struct和class都适用)

    在类的属性全部被初始化完毕之前,不能使用self。

    class Moi {
        let first = "Matt"
        let last = "Neuburg"
        let whole = self.first + " " + self.last // compile error
    
    }

    对于静态属性的使用,在非静态函数中应该使用类名.属性,在静态函数中可以使用self.属性或者类名.属性

    class Greeting {
        static let friendly = "hello there"
        static let hostile = "go away"
    
        static var ambivalent : String {
            return self.friendly + " but " + self.hostile
        }
    }

    下标(对struct和class都适用)

    下标是一种调用实例方法的方式。一般在通过整数参数或者String类型的key获取元素的时候使用下标。

    struct Digit {    
        var number : Int    
    
        init(_ n:Int) {
            self.number = n    
        }   
    
        subscript(ix:Int) -> Int {
            get {             
                let s = String(self.number)  
                return Int(String(s[s.index(s.startIndex, offsetBy:ix)]))!        
            }   
        }
    
    }

    上述代码定义了一个通过位数取数字的下标方法,只读。

    var d = Digit(1234)
    let aDigit = d[1] // 2

    嵌套定义

    class Dog {   
        struct Noise {        
            static var noise = "woof"    
        }    
    
        func bark() {        
            print(Dog.Noise.noise)  
        }
    }
    

    注意:struct不能直接或者间接嵌套自己的类型。

    Struct

    struct大部分特性都和class一致,可以看做是没有继承特性的值类型的class。

    一些不同:
    - 改变struct属性的方法需要标记为mutating,在enum章节中会有例子。
    - 默认的初始化函数可以提供逐一赋值功能(memberwise),只要能保证所有属性都初始化。

    struct Digit {
        var number = 42
        var number2
    }
    
    var d = Digit(number: 3, number2: 34)
    var f = Digit() //compile error
    
    • struct 和enum的类方法或者类属性使用static关键字,class可以使用static或者class,static = final class。

    enum

    enum Filter {    
        case albums    
        case playlists    
        case podcasts    
        case books
    }
    
    let type = Filter.albums

    在能根据上下文推断出enum的类型的时候,可以简写成:

    let type : Filter = .albums

    RawValue

    可以给enum指定一个存储类型,存储类型只能是数字或者String

    enum PepBoy : Int { 
        case manny    
        case moe    
        case jack
    }
    
    enum Filter : String {
        case albums
        case playlists
        case podcasts
        case books
    }

    PepBoy中默认从0开始,Filter中默认值就是case的名字。

    要获取enum中相应case的值,使用rawValue属性

    let type = Filter.albums
    print(type.rawValue) // albums

    可以通过rawValue初始化enum

    let type = Filter(rawValue:"Albums")

    Swift中的enum可以有初始化方法

    enum Filter : String {
        case albums = "Albums"
        case playlists = "Playlists"
        case podcasts = "Podcasts"
        case books = "Audiobooks"
        static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
        init(_ ix:Int) {
            self = Filter.cases[ix]
        }
    }

    上边的代码就可以通过一个Int来初始化一个存储类型是String的enum。

    enum可以有实例方法和类方法

    enum Shape {
        case rectangle
        case ellipse
        case diamond
        func addShape (to p: CGMutablePath, in r: CGRect) -> () {
            switch self {
            case .rectangle:
                p.addRect(r)
            case .ellipse:
                p.addEllipse(in:r)
            case .diamond:
                p.move(to: CGPoint(x:r.minX, y:r.midY))
                p.addLine(to: CGPoint(x: r.midX, y: r.minY))
                p.addLine(to: CGPoint(x: r.maxX, y: r.midY))
                p.addLine(to: CGPoint(x: r.midX, y: r.maxY))
                p.closeSubpath()
            }
        }
    }

    上边的代码能根据这个enum实际的值,来创建一个图形。

    如果一个enum的实例方法能够修改这个enum的值,那需要将方法声明为mutating

    enum Filter : String {
        case albums = "Albums"
        case playlists = "Playlists"
        case podcasts = "Podcasts"
        case books = "Audiobooks"
        static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
        mutating func advance() {
            var ix = Filter.cases.index(of:self)!
            ix = (ix + 1) % 4
            self = Filter.cases[ix]
        }
    }

    原理是这样的,enum是一个值类型,值类型是不可变的,要改变enum的值,只有再创建一个enum。这个动作在Swift中是需要开发人员显示指定的。这一条也适用于struct。

    Associated Value

    在Swift中enum还可以作为C语言中的Union使用。

    enum MyError {
        case number(Int)
        case message(String)
        case fatal
    }
    • 在MyError中不声明任何存储类型
    • 在每个case后边用tuple定义类型

    MyErrorj就是一个可能保存Int或者String的数据类型。

    let num = 4
    let err : MyError = .number(num)

    因为Associated Value是动态赋值的,所以Associated Value类型的enum不能使用enum比较。

    if err == MyError.fatal { // compile error

    因为Swift不知道如何比较,两个实例的fatal可能关联了不同的值,那么到底是相同还是不相同?

    集合数据类型

    Array

    • Array只能保存一种数据类型,是指声明为同一种的数据类型,不是实际类型。
    • 如果想保存混合类型的数据,使用[Any],Any是为了和OC交互定义的数据类型。
    • 保存不同类型的Array属于不同的数据类型。
    • Array是值类型,是struct。

    保存Int类型的Array有两种写法:

    let arr1 = Array<Int>()
    let arr2 = [Int]()

    可以使用Range:

    let arr3 = Array(1...3)

    Array有很多初始化函数,比如还可以接受一个集合类型,创建出一个Array

    let arr4 = Array("hey".characters)

    有一个初始化函数需要注意:init(repeating:count),如果参数是引用类型,那么Array中的所有元素将指向同一个元素。

    class Person {
        var name = "123"
    }
    
    var p = Person()
    let arr5 = Array(repeatElement(p, count: 3)) 
    //[{name "123"}, {name "123"}, {name "123"}]
    
    arr5[1].name = "555"  
    //[{name "555"}, {name "555"}, {name "555"}]
    

    Array作为一个整体可以类型转换:

    let dog1 : Dog = NoisyDog()
    let dog2 : Dog = NoisyDog()
    let arr = [dog1, dog2]
    let arr2 = arr as! [NoisyDog]

    NoisyDog 是 Dog的子类, arr是[Dog]类型,可以时间用as!或者as?转换为[NoisyDog]类型。

    两个Array相等的条件是Array中的每一个元素相等(注意并没有要求两个Array的类型是一样的)。和其他语言类似,可以自己提供比较函数。

    let nd1 = NoisyDog()
    let d1 = nd1 as Dog
    let nd2 = NoisyDog()
    let d2 = nd2 as Dog
    if [d1,d2] == [nd1,nd2] { // they are equal!

    Array的下标是支持切片的(slicing),切片仅仅是原来Array的一个映像,底层还是引用的是原来的Array

    let arr = ["manny", "moe", "jack"]
    let slice = arr[1...2] // ["moe", "jack"]
    print(slice[1]) // moe

    slice是arr的从1到2闭区间的切片,下标也是从1开始,到2结束。==如果引用了下标0,则会产生运行时错误==。如果改变了切片中的元素(前提是可以改变),则原来的数组也会受到影响。

    但是,Array不支持负数下标。

    Array有一些常用的属性:

    let arr = ["manny", "moe", "jack"]
    
    arr.count
    arr.isEmpty
    arr.first
    arr.last
    arr.startIndex
    arr.endIndex
    //...

    判断元素是否存在:

    let arr = [1,2,3]
    let ok = arr.contains(2) // true
    let ok2 = arr.contains {$0 > 3} // false
    let arr = [1,2,3]
    let ok = arr.starts(with:[1,2]) // true
    let ok2 = arr.starts(with:[1,-2]) {abs($0) == abs($1)} // true

    改变Array元素:

    var arr = [1,2,3]
    arr.append(4)
    arr.append(contentsOf:[5,6])
    arr.append(contentsOf:7...8) // arr is now [1,2,3,4,5,6,7,8]
    var arr = ["manny", "moe", "jack"]
    arr.insert("333", at: 1) //["manny", "333", "moe", "jack"]
    arr.remove(at: 1) //arr is ["manny", "moe", "jack"]
    let arr = [[1,2], [3,4], [5,6]]
    let joined = Array(arr.joined(separator:[10,11]))
    // [1, 2, 10, 11, 3, 4, 10, 11, 5, 6]
    let arr = [1,2,3,4,5,6]
    let arr2 = arr.split {$0 % 2 == 0} // split at evens: [[1], [3], [5]]

    遍历Array元素

    let pepboys = ["Manny", "Moe", "Jack"]
    for pepboy in pepboys {
        print(pepboy) // prints Manny, then Moe, then Jack
    }
    let pepboys = ["Manny", "Moe", "Jack"]
    pepboys.forEach {print($0)} // prints Manny, then Moe, then Jack
    let pepboys = ["Manny", "Moe", "Jack"]
    for (ix,pepboy) in pepboys.enumerated() {
        print("Pep boy \(ix) is \(pepboy)") // Pep boy 0 is Manny, etc.
    }
    // or:
    pepboys.enumerated().forEach {print("Pep boy \($0.0) is \($0.1)")}
    let pepboys = ["Manny", "Jack", "Moe"]
    let arr1 = pepboys.filter{$0.hasPrefix("M")} // ["Manny", "Moe"]
    let arr2 = pepboys.prefix{$0.hasPrefix("M")} // ["Manny"]
    let arr3 = pepboys.drop{$0.hasPrefix("M")} // ["Jack", "Moe"]

    Array和OC的关系

    如果一个NSArray没有任何额外信息则转化为[Any],NSArray中的对象都是class类型。把一个Array转化为NSArray没有额外的工作要做。

    let arr = [UIBarButtonItem(), UIBarButtonItem()]
    self.navigationItem.leftBarButtonItems = arr

    在Array上调用NSArray的方法需要转换:

    let arr = ["Manny", "Moe", "Jack"]
    let s = (arr as NSArray).componentsJoined(by:", ")
    // s is "Manny, Moe, Jack"

    不能把一个Array转化成一个NSMutableArray。如果需要调用NSMutabelArray的方法,使用NSMutableArray的构造函数创建一个。

    var arr = ["Manny", "Moe", "Jack"]
    let arr2 = NSMutableArray(array:arr)
    arr2.remove("Moe")
    arr = arr2 as! [String]

    在Xcode7以后,有些OC的API提供了额外的类型信息,比如:

    + (NSArray<NSString *> *)fontNamesForFamilyName:(NSString *)familyName;

    这时候返回的值就能直接转换为String。

    Dictionary

    Dictionary的语法:

    var d : [String:String] = [:]
    var d = [String:String]()
    var d = ["CA": "California", "NY": "New York"]

    两个Array,一个保存Key,一个保存Value,初始化一个Dictionary

    let abbrevs = ["CA", "NY"] 
    let names = ["California", "New York"]
    
    let tuples = zip(abbrevs, names) 
    let d = Dictionary(uniqueKeysWithValues: tuples)

    如果两个Array长度不同,zip自动忽略额外的部分,保证成对。

    从Dictionary中取出来的值是Opational的,因为如果不存在的话会返回nil。可以使用有默认值的方式获取

    let d = ["CA": "California", "NY": "New York"] 
    let state = d["MD", default:"N/A"] // state is a String (not an Optional)

    使用了default关键字返回的就是String而不是String?

    Dictionary的遍历:

    遍历key:

    var d = ["CA": "California", "NY": "New York"] 
    
    for s in d.keys { 
        print(s) // NY, then CA 
    
    }

    遍历key和value:

    var d = ["CA": "California", "NY": "New York"] 
    
    for (abbrev, state) in d { 
        print("\(abbrev) stands for \(state)") 
    
    }

    可以将Dictionary变成一个Tuple的Array:

    var d = ["CA": "California", "NY": "New York"] 
    let arr = Array(d) 
    // [(key: "NY", value: "New York"), (key: "CA", value: "California")]
    

    和NSDictionary的关系:

    NSDictionary对应[AnyHashable: Any],NSDictionary向Swift转换:

    let prog = n.userInfo?["progress"] as? Double 
    
    if prog != nil { 
        self.progress = prog!
    
    }

    Swift中使用Cocoa接口:

    UINavigationBar.appearance().titleTextAttributes = [
        .font: UIFont(name: "ChalkboardSE-Bold", size: 20)!, 
        .foregroundColor: UIColor.darkText, 
        .shadow.: {
            let shad = NSShadow()
            shad.shadowOffset = CGSize(width:1.5,height:1.5)
            return shad 
    
        }()
    ]

    Set

    let set : Set<Int> = [1, 2, 3, 4, 5]

    在Swift中Set没有字面变量,但是可以用Array构建。

    在Array中去重:

    let arr = [1,2,1,3,2,4,3,5] 
    let set = Set(arr) 
    let arr2 = Array(set) // [5, 2, 3, 1, 4], perhaps
    

    insert 和 update,假设Dog的比较函数是name相等。

    var set : Set = [Dog(name:"Fido", license:1)] 
    let d = Dog(name:"Fido", license:2) 
    set.insert(d) // [Dog(name: "Fido", license: 1)] 
    set.update(with:d) // [Dog(name: "Fido", license: 2)]
    

    当已经存在的时候,insert不会改变set,update更新set。

    两个set可以使用==比较,相等的条件是每一个元素相等。

    求两个Set的交集:

    intersection(_:)formIntersection(_:)

    求两个Set的并集:

    union(_:)formUnion(_:)

    求两个Set的异或:

    symmetricDierence(_:), formSymmetricDierence(_:)

    求两个Set的差集:

    subtracting(_:), subtract(_:)

    还有几个集合的函数,判断是不是子集,判断有没有相交等。

    Optional Set

    Optional Set是和NS_OPTIONS对应的。

    typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    
        UIViewAnimationOptionLayoutSubviews = 1 << 0,
        UIViewAnimationOptionAllowUserInteraction = 1 << 1, 
        UIViewAnimationOptionBeginFromCurrentState = 1 << 2, 
        UIViewAnimationOptionRepeat = 1 << 3, 
        UIViewAnimationOptionAutoreverse = 1 << 4, 
        // ...
    
    };

    对应Swift中:

    UIViewAnimationOptions.layoutSubviews 
    UIViewAnimationOptions.allowUserInteraction 
    UIViewAnimationOptions.beginFromCurrentState 
    UIViewAnimationOptions.repeat 
    UIViewAnimationOptions.autoreverse
    

    UIViewAnimationOptions被定义为一个Set,这样就可以模拟bitmask了。

    var opts = UIViewAnimationOptions.autoreverse 
    opts.insert(.repeat)

    也可以使用运算符操作:

    let val = UIViewAnimationOptions.autoreverse.rawValue | UIViewAnimationOptions.repeat.rawValue 
    let opts = UIViewAnimationOptions(rawValue: val)

    控制结构

    if 语句

    if condition {
        statements
    } else if condition {
        statements
    } else {
        statements
    }

    条件语句不需要用括号括起来。

    条件绑定(conditional binding),这个在Optional变量的使用中非常常见

    if let prog = n.userInfo?["progress"] as? Double {
        self.progress = prog
    }

    等号后边是一个Optional Chain,可能返回nil,或者一个Double?,如果Optional Chain返回的是nil,则条件不成立,不会执行大括号内内容。如果Optional Chain不是nil,则==自动拆箱==,然后把拆箱后的值赋给prog,注意,==prog是Double而不是Double?==,prog的作用域在条件语句内部

    Switch 语句

    不用写break,自动break

    switch i {
    case 1:
        print("You have 1 thingy!")
    case 2:
        print("You have 2 thingies!")
    default:
        print("You have \(i) thingies!")
    }

    但是Switch的case语句必须覆盖所有情况,否则会发生编译错误。case语句也不能为空,至少要写一句break

    可以在case中定义变量

    switch i {
    case 1:
        print("You have 1 thingy!")
    case let n:
        print("You have \(n) thingies!")
    }

    如果i不是1,就将i赋值给n(好像并没有什么卵用)

    可以使用Range 匹配:

    switch i {
    case 1:
        print("You have 1 thingy!")
    case 2...10:
        print("You have \(i) thingies!")
    default:
        print("You have more thingies than I can count!")
    }

    Switch的另外一种用法:

    func position(for bar: UIBarPositioning) -> UIBarPosition {
        switch true {
        case bar === self.navbar:  return .topAttached
        case bar === self.toolbar: return .bottom
        default:                   return .any
        }
    }

    在switch中先指定结果(只能是true或者false),然后在case中判断表达式的结果是否和switch中相同。

    case语句中还可以加filter:

    switch i {
    case let j where j < 0:
        print("i is negative")
    case let j where j > 0:
        print("i is positive")
    case 0:
        print("i is 0")
    default:break
    }

    上述代码等价于:

    switch i {
    case ..<0:
        print("i is negative")
    case 1...:
        print("i is positive")
    case 0:
        print("i is 0")
    default:break
    }

    还可以判断对象类型:

    switch d {
    case is NoisyDog:
        print("You have a noisy dog!")
    case _:
        print("You have a dog.")
    }
    switch d {
    case let nd as NoisyDog:
        nd.beQuiet()
    case let d:
        d.bark()
    }

    注意:第二段代码中是as而不是as?,如果d是NoisyDog,nd才会被赋值,如果不是就不走这一个分支了。

    Switch还可以比较tuple:

    switch (d["size"], d["desc"]) {
    case let (size as Int, desc as String):
        print("You have size \(size) and it is \(desc)")
    default:break
    }

    如果switch的type是enum,那么还可以有很多花样:

    enum MyError {
        case number(Int)
        case message(String)
        case fatal
    }
    switch err {
    case .number(let theNumber):
        print("It is a number: \(theNumber)")
    case let .message(theMessage):
        print("It is a message: \(theMessage)")
    case .fatal:
        print("It is fatal")
    }
    switch err {
    case .number(1...):
        print("It's a positive error number")
    case .number(..<0):
        print("It's a negative error number")
    case .number(0):
        print("It's a zero error number")
    default:break
    }

    因为Optional本身是一个enum,所以可以这样写:

    switch i {
    case .none: break
    case .some(1):
        print("You have 1 thingy!")
    case .some(let n):
        print("You have \(n) thingies!")
    }

    fallthrough关键字:

    switch pep {
    case "Manny": fallthrough
    case "Moe": fallthrough
    case "Jack":
        print("\(pep) is a Pep boy")
    default:
        print("I don't know who \(pep) is")
    }

    if case

    if case let .number(n) = err {
        print("The error number is \(n)")
    }

    这是一个switch语句的简写,直接将err enum的Associated value取出来。

    条件赋值:

    let title : String = {
        switch type {
        case .albums:
            return "Albums"
        case .playlists:
            return "Playlists"
        case .podcasts:
            return "Podcasts"
        case .books:
            return "Books"
        }
    }()

    ??

    func tableView(_ tv: UITableView, numberOfRowsInSection sec: Int) -> Int {
        return self.titles?.count ?? 0
    }

    self.titles是[String]?类型,如果不是nil,拆箱,获取count属性;否则,返回0

    这段代码是什么意思?

    let someNumber = i1 as? Int ?? i2 as? Int ?? 0

    while

    两种方式:

    while condition {
        statements
    }
    
    repeat {
        statements
    } while condition
    

    for循环

    for…in

    for i in 1...5 {
        print(i) // 1, 2, 3, 4, 5
    }

    可以加filter:

    for i in 0...10 where i % 2 == 0 {
        print(i) // 0, 2, 4, 6, 8, 10
    }

    可以使用case简写:

    let arr : [MyError] = [
        .message("ouch"), .message("yipes"), .number(10), .number(-1), .fatal
    ]
    
    for case let .number(i) in arr {
        print(i) // 10, -1
    }

    stride

    for i in stride(from: 10, through: 0, by: -2) {
        print(i) // 10, 8, 6, 4, 2, 0
    }

    从10开始到0,步幅为-2,迭代。

    sequence

    sequence是一个函数,有两个参数,一个是初始值,一个是生成函数。sequence返回的是一个生成器,只有用的时候才会计算并返回下一个值。

    sequence用法:

    let seq = sequence(first:1) {$0 >= 10 ? nil : $0 + 1}
    for i in seq {
        print(i) // 1,2,3,4,5,6,7,8,9,10
    }

    或者:

    let seq = sequence(first:1) {$0 + 1}
    for i in seq.prefix(5) {
        print(i) // 1,2,3,4,5
    }

    jumping

    几个跳转的关键字:
    fallthrough,是switch case中执行下一个case的意思。
    continue,循环中,结束当前循环,从判断条件开始进行下一个循环。
    break,在循环中,跳出当前循环,在switch…case中,跳出switch语句。

    Swift中的循环可以带label,这样嵌套循环中的break和continue可以指定跳出哪一个循环。

    outer: for i in 1...5 {
        for j in 1...5 {
            print("\(i), \(j);")
            break outer
        }
    }
    // 1, 1;

    Error

    Swift采用throw…catch的方式来管理错误。

    error定义:

    enum MyFirstError : Error {
        case firstMinorMistake
        case firstMajorMistake
        case firstFatalMistake
    }
    enum MySecondError : Error {
        case secondMinorMistake(i:Int)
        case secondMajorMistake(s:String)
        case secondFatalMistake
    }

    使用:

    do {
        // throw can happen here
    } catch MyFirstError.firstMinorMistake {
        // catches MyFirstError.firstMinorMistake
    } catch let err as MyFirstError {
        // catches all other cases of MyFirstError
    } catch MySecondError.secondMinorMistake(let i) where i < 0 {
        // catches e.g. MySecondError.secondMinorMistake(i:-3)
    } catch {
        // catches everything else
    }

    抛出错误的函数需要在参数后边上throws关键字

    enum NotLongEnough : Error {
        case iSaidLongIMeantLong
    }
    func giveMeALongString(_ s:String) throws {
        if s.characters.count < 5 {
            throw NotLongEnough.iSaidLongIMeantLong
        }
        print("thanks for the string")
    }

    throws也是函数签名的一部分,giveMeALongString的函数签名就是(String) throws -> ()

    含有throws的函数必须使用try调用,try语句必须在do…catch中,或者是另外一个throws的函数中。

    try!,这个try!的意思是这个函数虽然被标记为throws,但是我知道它肯定不会throw错误,try!是不需要do…catch或者throws函数中使用的。但是如果真的throw的错误,程序就会Crash。

    同样有一个try?的作用在try和try!之间。try?可以在任何地方调用,但是会吞掉error。如果函数返回一个Optional,则返回nil。

    rethrows关键字

    一个参数中有接受throws函数的函数,如果自己本身不会throw error,则可以标记为rethrows。标记了rethrows关键字的函数,可以接受throw函数或者非throw的函数,如果调用者传入的参数是非throw的函数,那么可以不使用try来调用这个函数。擦!

    func receiveThrower(_ f:(String) throws -> ()) rethrows {
        try f("ok?")
    }
    func callReceiveThrower() { // no throws needed
        receiveThrower { s in // no try needed
            print("thanks for the string!")
        }
    }

    Swift和OC的错误处理转换:

    在OC中NSString有一个初始化函数:

    - (instancetype)initWithContentsOfFile:(NSString *)path
                                  encoding:(NSStringEncoding)enc
                                     error:(NSError **)error;

    需要传入一个NSError的地址,如果初始化失败,则返回nil,错误信息在NSError中。

    在Swift中这个初始化函数变成了throw的:

    init(contentsOfFile path: String, encoding enc: String.Encoding) throws

    所以OC中的传入NSError地址的函数,全都被Swift中的throw函数替代。

    do {
        let f = // path to some file, maybe
        let s = try String(contentsOfFile: f)
        // ... if successful, do something with s ...
    } catch CocoaError.fileReadNoSuchFile {
        print("no such file")
    } catch {
        print(error)
    }

    由Swift的Error转换陈NSError的时候,domain属性不变,code是enum的case的index

    do … break

    给do语句块加一个label,在语句块内部使用break label的时候就能跳出语句块。

    out: do {
        // ...
        if somethingBadHappened {
            break out
        }
        // we won't get here if somethingBadHappened
    }

    defer

    defer的语句在离开当前的大括号之前一定会执行。比如一个释放资源的代码,在函数的任何一个退出分支都需要写,很容易遗忘。使用defer语句可以避免这个麻烦。

    func doSomethingTimeConsuming() {
        defer {
            UIApplication.shared.endIgnoringInteractionEvents()
        }
        UIApplication.shared.beginIgnoringInteractionEvents()
        // ... do stuff ...
        if somethingHappened {
            return
        }
        // ... do more stuff ...
    }

    defer语句要写在尽可能靠前的位置,如果在return之后,那么defer语句是不会执行的。

    abort

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    就是直接让程序挂掉,应该是一种调试手段。

    assert还是可以使用的。

    guard

    guard是为了解决if false return 这样的嵌套问题的。是一个简写。

    guard let s = optionalString else {return}
    // s is now a String (not an Optional)

    相当于

    let s = optionalString
    if s == nil {
        return
    }

    guard可以和try?合起来使用:

    let f = // path to some file, maybe
    guard let s = try? String(contentsOfFile: f) else {return}
    // s is now a String (not an Optional

    和case合起来使用:

    guard case let .number(n) = err else {return}
    // n is now the extracted number

    和表达式一起使用:

    guard howMany() > 10 else {return}
    展开全文
  • Swift 3.0 -字符串

    2016-09-30 15:54:12
    // main.swift // Swift-字符串 // // Created by yidong on 16/9/28. // Copyright © 2016年 东哥. All rights reserved. // import Foundation //1.0 /*  字符串/字符 的定义  */ //字符串变量 var str1 ...
    //
    //  main.swift
    //  Swift-字符串
    //
    //  Created by yidong on 16/9/28.
    //  Copyright © 2016年 东哥. All rights reserved.
    //

    import Foundation
    //1.0
    /*
     字符串/字符 的定义
     */

    //字符串变量
    var str1 = "hello"

    //字符串常量
    let str2 = "swift3.0"

    //声明为nil,
    var str3:String?

    //空字符串
    let str4 = String()

    //空字符串 提倡用这样的字面量语法声明,类型可不指定,swift自动识别
    let str5 = ""
    //字符
    var char1:Character = "m"
    var p_str1 = ""

    //2.0
    /*
     字符串/字符 的拼接
     */
        
        p_str1 = str1+str2
        p_str1 = String(format:"%@~%@",str1,str2)
        print(p_str1);//hello~swift3.0
        
        
        
        p_str1 = String(format:"%@~%@-%d",str1,str2,456)
        
        print(p_str1);//hello~swift3.0-456
        
        //这种拼接方式方便地组合多种类型
        
        p_str1 = "\(str1)\(str2)\(456)"
        
        print(p_str1);//helloswift3.0456
        
        p_str1 = "\(str1)\(str2)\(char1)"
        
        print(p_str1);//helloswift3.0m
        
        //在后面添加
        p_str1.append(char1)
        print(p_str1);//helloswift3.0mm
        
        
        p_str1 += str2
        
        print(p_str1);//helloswift3.0mmswift3.0
        
        //与数组的组合
        var strArray = ["hello", "swift", "3.0"]
        
        p_str1 = strArray.joined(separator: "-")//数组通过指定字符拼接
        
        print("数组通过指定字符拼接\(p_str1)");
        
        
        strArray = p_str1.components(separatedBy: "-")//拆分为数组
        
        print("拆分为数组\(strArray)");
        
        print("p_str1字符串为:\(p_str1)");
        
        //枚举字符
        for ch in p_str1.characters{
            
            print(ch)
            
            switch ch {
            case "0":
                print("有")
            default:
                break
            }
        }
        

    //3.0
    /*
     获取字符串中指定索引的字符
     */

     //字符串长度
    print("p_str1长度为:\(p_str1.characters.count)");

    //首字母
    char1 = p_str1[p_str1.startIndex]

    print("首字母\(char1)");

    //末字母
    char1 = p_str1[p_str1.index(before: p_str1.endIndex)]

    print("末字母\(char1)")

    //第二个字母
    char1 = p_str1[p_str1.index(after: p_str1.startIndex)]

    print("第二个字母\(char1)")

    //索引4的字母
    char1 = p_str1[p_str1.index(p_str1.startIndex, offsetBy: 4)]

    print("索引4的字母\(char1)")



    //4.0
    /*
     获取字符串子串
     */

    let i = p_str1.index(p_str1.startIndex, offsetBy: 4)


    let j = p_str1.index(p_str1.startIndex, offsetBy: 8)

    //print(i,j);

    var subStr = p_str1.substring(to: i)
    print("截取到4\(subStr)")

    subStr = p_str1.substring(from: i)
    print("从索引4开始截取\(subStr)")

    subStr = p_str1.substring(with: i..<j)
    print("从索引4开始到8(不截取索引为8的字符)结束截取\(subStr)")

    //通过扩展来简化一下
    //  extension String {
    //    
    //    subscript (range: Range<Int>) -> String {
    //        get {
    //            let startIndex = self.index(self.startIndex, offsetBy: range.lowerBound)
    //            let endIndex = self.index(self.startIndex, offsetBy: range.upperBound)
    //            return self[Range(startIndex..<endIndex)]
    //        }
    //        
    //        set {
    //            let startIndex = self.index(self.startIndex, offsetBy: range.lowerBound)
    //            let endIndex = self.index(self.startIndex, offsetBy: range.upperBound)
    //            let strRange = Range(startIndex..<endIndex)
    //            self.replaceSubrange(strRange, with: newValue)
    //        }
    //    }
    //}

    //subStr = p_str1[0..<14]


    //通过指定字符串截取子串
    let range1 = p_str1.range(of: "o-")

    let range2 = p_str1.range(of: ".")

    subStr = p_str1.substring(from: (range1?.upperBound)!)

    print("从o-开始截取字符串:\(subStr)")

    subStr = p_str1.substring(with: (range1?.upperBound)!..<(range2?.lowerBound)!)

    print("从o-开始到.结束截取字符串:\(subStr)")

    //5.0
    /*
     插入、删除指定字符串
     */
    subStr.insert("!", at: subStr.startIndex)

    print(subStr);

    subStr.insert("!", at: subStr.endIndex)

    print(subStr);

    subStr.insert(contentsOf:"YY".characters, at: subStr.index(after: subStr.startIndex))

    print(subStr);

    subStr.insert(contentsOf:"MM".characters, at: subStr.index(before: subStr.endIndex))

    print(subStr)

    let x = p_str1.index(p_str1.startIndex, offsetBy: 4)

    //print(x)

    subStr.remove(at: x)
    print("删除索引为4的字符后得到的字符串\(subStr)")

    subStr.remove(at: subStr.startIndex)
    print("删除索引为0的字符后得到的字符串\(subStr)")

    subStr.remove(at: subStr.index(after: subStr.startIndex))

    print("删除索引为0的字符后得到的字符串\(subStr)")


    subStr.remove(at: subStr.index(before: subStr.endIndex))
    print("删除为最后一个索引的字符后得到的字符串\(subStr)")


    let ran1 = subStr.index(subStr.endIndex, offsetBy: -3)..<subStr.endIndex

    subStr.removeSubrange(ran1)

    print("删除从索引为倒数第3之后所有的字符串后得到的字符串\(subStr)")

    let ran2 = subStr.index(subStr.startIndex, offsetBy: 4)..<subStr.endIndex

    subStr.removeSubrange(ran2)

    print("删除从索引为4之后所有的字符串后得到的字符串\(subStr)")

    subStr = "http://baidu.com"
    let ran3 = subStr.range(of: ":")

    let ran4 = subStr.range(of: ".")

    subStr.removeSubrange((ran3?.upperBound)!..<(ran4?.lowerBound)!)
    print("删除从:开始到.之间的字符串后得到的字符串\(subStr)");

    //替换
    subStr.replaceSubrange(ran3!, with: "wert")

    print("ran3被wert替换之后的字符串\(subStr)");


    //是否有前、后缀
    p_str1.hasPrefix("3.0")

    p_str1.hasSuffix("3.0")
    //是否为空
    p_str1.isEmpty
    str3?.isEmpty
    str4.isEmpty
    str5.isEmpty

    //6.0
    /*
     其他
     */
    //--- 大小写转换 -----------
    subStr = "ashdfasdjf"

    print("subStr.capitalized(首字母大写)方法:\(subStr.capitalized)")


    print("subStr.uppercased()大写方法:\(subStr.uppercased())")


    print("subStr.lowercased()小写方法:\(subStr.lowercased())")

    //--- 字符串比较 -----------
    subStr = "2.6.2"
    p_str1 = "2.6.1"
    str1 = "2.7"
    str3 = "2.7.0"
    subStr > p_str1
    subStr > str1
    str1 == str3
    str1 > str3!
    str1 < str3!

    //--- 字符串转换 -----------
    Int(str1)
    Double(str1)
    Double(str3!)
    Bool(str1)
    str1 = "true"
    Bool(str1)

    展开全文
  • swift4把字符串中间空格去掉 也不知道这帮人怎么想的,设计的字符串处理麻烦死了,我先不说效率到底怎么样,就从写法上到现在没理解swift4处理字符串怎么搞成这样子 下面是查了好半天才查到一种方式,太麻烦的...
  • Swift 4 中的字符串

    2018-08-15 10:30:03
    《高级 Swift 编程》新版本已根据 Swift 4 的新特性修订补充,新版现已上市。 所有的现代编程语言都有对 Unicode 编码字符串的支持,但这通常只意味着它们的原生字符串类型可以存储 Unicode...
  • 不知道怎么解析的小伙伴可以看我另一篇贴子 《 swift4--解析json》   GET请求: // // ViewController.swift // URLSesstionTest // import UIKit class ViewController: UIViewController { // 要传递的...
  • Swift 包管理器教程

    2017-05-05 09:00:09
    原文:An Introduction to the Swift Package Manager 作者: Mikael Konutgan 译者:kmyhy Swift 包管理器的正式发布是随着 Swift3.0 一起发布的,它是一个用于构建能够运行在 macOS 和 Linux 上的 Swift 库和 ...
  • Swift的集合类型中,有许多十分便捷的函数。相比于Objective-C,这些高阶函数会引起你的极度舒适。因为在Swift的许多函数中引入了闭包元素,这就直接造就了它的灵活性,简直就是极致的便捷。
  • func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // 第一次运行获取到DeviceToken时间会比较长! // 将deviceToken转换成字符串,以便...
  • let timeText = "2019-10-10 00:00:00,2019-10-10 00:00:00|2019-10-10 00:00:00,2019-10-10 00:00:00" let mapBlock: ([String]) -> (String) = { (list) -> String in var startTimeString = list.firs...
  • 升级到 Swift3.0 之后,新版本的 Alamofire 只支持 iOS 9.0 以上的系统,如果要适配 iOS 8,需要自己封装 URLSession,下面是笔者的方案: 这里使用的是 Swift 自己的原生类型 URLSession,而不是NSURLSession。 ...
  • Swift 中的关键字详解
  • swift3.0的一些知识

    2016-12-26 11:28:15
    import UIKit //----------------- 字符串/字符 的定义 ----------------- //字符串变量 var str1 = "hello" //字符串常量 let str2 = "swift3.0" //声明为nil, ...let str4 = String
  • Swift 是苹果遵循 Apache 开源授权协议开源的一门编程语言 Swift 3 源代码不兼容旧版本,主要是因为 SE-0005 和 SE-0006 的改进,这些改进不仅影响 Standard Library APIs 命名,还会完全改变 Objective-C APIs ...
  • I'm looking for a way to replace characters in a Swift String . 我正在寻找一种替换Swift String字符的方法。 Examp
  • swift5.2 方法 将 [String] 转换成 String let class3_2: [String] = ["LiMing", "LiHua", "XiaoWang", "Uzi"] let studentsName = class3_2.joined(separator: "") 判断是否包含所求字符串 if studentsName....
  • http://www.swift51.com/swift4.0/chapter2/22_Protocols.html 本页包含内容: 协议语法 属性要求 方法要求(Method Requirements) Mutating 方法要求 构造器要求 协议作为类型 委托(代理)模式 通过扩展添加协议...
1 2 3 4 5 ... 20
收藏数 436
精华内容 174
热门标签
关键字:

4 joined swift