苹果原生post请求swift_flutter android 原生,苹果原生 - CSDN
  • 网络请求用的比较多的是Get和Post请求,最为学习记录,先介绍Get请求.后续更新Post请求. 本文介绍,在IOS开发中,苹果原生的NSURLSession框架和第三方开源的Alamofire1:调用系统浏览器打开网页let baidu = ...

    网络请求用的比较多的是Get和Post请求,最为学习记录,先介绍Get请求.后续更新Post请求.
    本文介绍,在IOS开发中,苹果原生的NSURLSession框架和第三方开源的Alamofire

    这里写图片描述


    1:调用系统浏览器打开网页

    let baidu = "http://www.baidu.com"
    //MARK:构建一个NSURL,使用String
    var bdUrl: NSURL {
        return NSURL(string: baidu)! 
    }
    //MARK: 获取UIApplication对象
    var application: UIApplication {
        return UIApplication.sharedApplication()
    }
    
    @IBAction func openBaidu() {
        //检查是否可以打开这个URL
        let can = application.canOpenURL(bdUrl)
        if can {
            //记住:一定要在子线程中调用,否则会阻塞主线程.
            NSOperationQueue().addOperationWithBlock {
                //返回结果,表示是否请求成功
                let result = self.application.openURL(self.bdUrl)//打开URL
            }
        }
    }

    关于多线程: http://blog.csdn.net/angcyo/article/details/52280819

    2:使用NSURLSession获取NSURL对应的数据

    @IBAction func openWidthSession() {
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { data, response, error in
            print("\(String(data: data!, encoding:NSUTF8StringEncoding))")//数据
            print("\(response)")
            print("\(error)")
        }.resume() //调用resume,执行任务.
    }

    关于NSURLSession: http://www.cnblogs.com/biosli/p/iOS_Network_URL_Session.html

    3:使用Alamofire请求数据
    需要: import Alamofire

    @IBAction func openWidthAlamofire() {
        // 1:
        Alamofire.request(.GET, url)
            .validate()
            .response { request, response, data, error in
                print("\(request?.URLString)")
                print("\(response)")
                print("\(String(data: data!, encoding: NSUTF8StringEncoding))")//数据
                print("\(error)")
        }
    
        // 2:
        Alamofire.request(.GET, bdUrl).responseString { response in
            print("\(response.result.value)")//数据
        }
    }

    Alamofire的用法: https://github.com/Alamofire/Alamofire#usage

    4:使用AlamofireImage装载网络图片
    需要: import AlamofireImage

    @IBAction func pngImage() {
        let url = "https://httpbin.org/image/png"
        // 1:
        Alamofire.request(.GET, url)
            .responseImage { response in
                print(response.request)
                print(response.response)
                if let image = response.result.value {
                    print("image downloaded: \(image)")
                    self.imageView.image = image //图片
                }
        }
        // 2:
        imageView.af_setImageWithURL(NSURL(string: url)!)//更简单的方式
    }

    AlamofireImage的用法: https://github.com/Alamofire/AlamofireImage#usage

    注意:
    如果你选择的IOS版本>=9.0, 那么在网络请求的时候,可能会出现这个错误:

    The resource could not be loaded because the App Transport Security policy require

    你只需要在项目中的plist文件中添加:

    在Info.plist中添加NSAppTransportSecurity类型Dictionary。
    在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES

    这里写图片描述

    源码: https://github.com/angcyo/HttpDemo

    后续补充更多…


    至此: 文章就结束了,如有疑问: QQ群 Android:274306954 Swift:399799363 欢迎您的加入.

    展开全文
  • 小萌之前的OC内购,中间也经历了不少的困难,详情请看苹果内购审核那些被拒的原因,不过之前是用OC封装的,现在小萌的主要语言是Swift,闲暇之余做了Swift5.X内购StoreKit原生的封装,特别齐全,可以直接使用哦Swift...

    内购是移动端一值在讨论的话题,每次上架内购功能要做好被拒的准备,小萌在2年前也做过内购,是OC版本,回想那段往事一把血泪呀,被拒了无数次才把内购功能送上AppStore

    小萌之前的OC内购,中间也经历了不少的困难,详情请看苹果内购审核那些被拒的原因,不过之前是用OC封装的,现在小萌的主要语言是Swift,闲暇之余做了Swift5.X内购StoreKit原生的封装,特别齐全,可以直接使用哦Swift版StoreKit的内购Demo

    语言: Swift5

    Xcode: 11.4.1

    环境: Mac CataLina 10.15.4

    注意⚠️: 不建议大家升级,不能传送大文件,小萌升级之后上传AppStore就没有成功过

    一、内购的流程

    网上有很多,也是各种各样,不过小萌对下面的这张图很感兴趣,好好的研究了一下这张图,看完之后茅塞顿开呀
    内购流程图
    总结内购流程:

    1、获取内购列表(从App内读取或从自己服务器读取(推荐服务器获取))

    2、App Store请求可用的内购列表(其实就是判断从服务器获取的产品列表是否可用)

    3、向用户展示内购列表

    4、用户选择了要购买的产品,发个购买请求,收到购买完成的回调(购买完成后会把钱打给申请内购的银行卡内)

    5、购买流程结束后, 向服务器发起验证凭证以及支付结果的请求

    6、自己的服务器将支付结果信息返回给前端并发放虚拟产品

    7、服务端的工作比较简单,分4步:

            7.1、接收ios端发过来的购买凭证。
            7.2、判断凭证是否已经存在或验证过,然后存储该凭证。
            7.3、将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
            7.4、如果需要,修改用户相应的会员权限。
            7.5、考虑到网络异常情况,服务器的验证应该是一个可恢复的队列,如果网络失败了,应该进行重试。
    

    简单来说就是将该购买凭证用Base64编码,然后POST给苹果的验证服务器,苹果将验证结果以JSON形式返回。

    二、审核流程

    网上特别多,小萌在这里就不分享了,小萌主要分享一下,上架的时候注意事项

    三、创建产品流程

    小萌在创建产品的之后提交AppStore上,因为创建的产品不符合要求,也被拒绝过一次,所以在这里分享一下内购产品创建需要注意的地方
    第一步:填写参考名称内购产品的ID
    在这里插入图片描述
    第二步:选择定价,保持一致
    在这里插入图片描述
    第三步:填写说明描述说明可以随意填,但是一定要有意义,描述一定是消耗多少人民币获得多少自己定义的币
    在这里插入图片描述
    第四步:App Store推广 无特殊需要不用传图片
    在这里插入图片描述
    第五步:截屏一定包含内购信息,否则会审核不通过,审核备注里面自己的测试账号和密码写进去,方便测试人员测试哦
    在这里插入图片描述

    四、注意事项

    注意⚠️:

    1、别提示是否登录,如果没有登录,点击直接跳到登录界面即可,一定不要询问哦

    2、别提示是否购买,直接购买即可,美国就是这么直接呀,不买不要点击嘛,增加审核通过率

    3、充值说明一定要有,并且声明仅限iOS系统使用,其他系统无权使用

    4、创建项目的时候App Store 推广那里的图片是可选的,不必要选哦,如下图⬇️所示
    审核产品选择图片,不用上传
    5、还有其他需要注意的地方,详情看苹果内购审核那些被拒的原因,一次又一次被拒的血泪!

    五、苹果的二次验证

    第一次:交易完成后在移动端验证,返回正确的数据,验证完毕

    第二次:小萌交给了后台去做验证,传了Base64位编码数据给后台

    六、DemoSwift版StoreKit的内购Demo

    小萌已经做了Swift的StoreKit原生的封装,下载直接可以使用

    主要代码:

    //交易完成在这里完成苹果验证
        func completeTransaction(transaction:SKPaymentTransaction){
    //        let environmentStr = String(data: transaction, encoding: .utf8)
             // 验证凭据,获取到苹果返回的交易凭据
             // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
            let receiptURL = Bundle.main.appStoreReceiptURL
            
             // 从沙盒中获取到购买凭据
            let receiptData = NSData(contentsOf: receiptURL!)
             
            // 发送网络POST请求,对购买凭据进行验证
            var urls : URL?
            let receiptURLString = receiptURL?.absoluteString
            if receiptURLString?.contains("sandbox") == true { //判断是沙盒路径还是正式环境
                urls = URL(string: ITMS_SANDBOX_VERIFY_RECEIPT_URL)
            }else{
                urls = URL(string: VERIFY_RECEIPT_URL)
            }
             
             // 国内访问苹果服务器比较慢,timeoutInterval需要长一点
            var requset = URLRequest(url: urls!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
            requset.httpMethod = "POST"
            
             // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
             // 传输的是BASE64编码的字符串
             /**
                 BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
                 BASE64是可以编码和解码的
             */
            
             let encodeStr = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithLineFeed)
             
             let payload = NSString(string: "{\"receipt-data\" : \"" + encodeStr! + "\"}")
    //         print(payload)
            let payloadData = payload.data(using: String.Encoding.utf8.rawValue)
            
            requset.httpBody = payloadData;
            weak var weakSelf = self //避免循环引用
            let result = URLSession.shared.dataTask(with: requset) { (data, response, error) in
                if data == nil{
                    print("验证失败")
                    return
                }
                
                if response == nil {
                    print("验证失败")
                    return
                }
                
                let dict = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
                if dict != nil {
    //                print(dict!)
                }
                
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: ProductPurchasedNotification), object: nil)
                weakSelf?.finishTransaction(transaction: transaction)
     
                
            }
            
            result.resume()
             
        }
    

    这是验证流程,具体封装代码可以下载Swift版StoreKit的内购Demo,直接使用项目中的StoreObserver.swift文件即可,单例模式设计,很简单哦

    展开全文
  • 日常的开发当中,网络请求是不可或缺的。而在网络访问请求中,经常会遇到有中文空格字符的情况,直接用这些字符串去访问是无法正常访问,需要我们做进一步的处理。 一般处理 let urlString = "http://10.0.3.86/中文...

    日常的开发当中,网络请求是不可或缺的。而在网络访问请求中,经常会遇到有中文空格字符的情况,直接用这些字符串去访问是无法正常访问,需要我们做进一步的处理。

    一般处理

    let urlString = "http://10.0.3.86/中文/main.html#/help"
    复制代码

    比如以上的url,想使用webview进行访问或者是原生发起http请求,都需要进行转码处理。 有人会问,这有什么难的?拿起键盘就是干

    OC:
    NSString* encodedString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    Swift:
    let encodedString = urlString.addingPercentEscapes(using: .utf8)
    
    复制代码

    一敲代码,emmmmm~~ Xcode发警告了,该方法已经过期,用下面的方法替代,于是紧接着:

    OC:
    NSString* encodedString = [urtString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    
    Swift:
    let encodedString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
    复制代码

    OK,转码处理一下,再访问转码之后的url。 what?还是不能正常访问? 我们来看看转码之后的url是什么:

    原来是因为最后的#被转码成了%23 前端开发的小伙伴说这个#不能动,只能我们不转码。不转码那么夹杂中文字符怎么办呢?—— 修改参数所以我们的需求变化成:除了url里面的#不动,其他该转码的都转码

    NSMutableCharacterSet *set  = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [set addCharactersInString:@"#"];
    NSString *encodedString     = [urlSring stringByAddingPercentEncodingWithAllowedCharacters:set];
    复制代码

    如上手动修改转码参数,OK 可以了。 Swift如法炮制

    let charSet = CharacterSet.urlQueryAllowed as! NSMutableCharacterSet
    charSet.addCharacters(in: "#")
    let encodingString = urlStr.addingPercentEncoding(withAllowedCharacters: charSet as CharacterSet)
    复制代码

    虽然语言不一样,但是思路一样。emmmm...你会惊人的发现,根本不管用!!!

    原因分析

    问题是出在let charSet = CharacterSet.urlQueryAllowed as! NSMutableCharacterSet这一行,在swift语言中,Foundation框架中的很多class都重新用struct重写了,比如NSString和String,NSUrl和URL,如果要使用类似于OC一些特性,有时候需要as来强转成对应的NS开头的类。强转的过程中,CharacterSet应该转成NSCharacterSet,而不应该是NSMutableCharacterSet,也就是说子类指针指向了父类对象,父类里面没有子类的方法,所以执行charSet.addCharacters(in: "#")的时候,无法正确添加。

    Swift正确的写法

    顺着原因一路分析,应该这么写:

    方法一:
    let charSet = CharacterSet.urlQueryAllowed as NSCharacterSet
    let mutSet = charSet.mutableCopy() as! NSMutableCharacterSet
    mutSet.addCharacters(in: "#")
    let encodingURL = urlStr.addingPercentEncoding(withAllowedCharacters: mutSet as CharacterSet)
    复制代码

    当然还有其他写法:

    方法二:
    let charSet = NSMutableCharacterSet()
    charSet.formUnion(with: CharacterSet.urlQueryAllowed)
    charSet.addCharacters(in: "#")
    let encodingURL = urlStr.addingPercentEncoding(withAllowedCharacters: charSet as CharacterSet)
    复制代码

    方法一和二本质是一样的,其实沿用的OC的思想,先构造一个可变对象,再加入自定义的字符。如果要像OC这么搞,那么苹果设计swift的意义何在?换句话说,swift用结构体写重写这个类一定考虑到这个问题,那就应该有相应的处理方法。

    查阅官方文档吧,少年!

    果不其然,找到一个方法,用来插入字符。 所以还有第三种写法:

    方法三:
    var charSet = CharacterSet.urlQueryAllowed
    charSet.insert(charactersIn: "#")
    let encodingURL = urlStr.addingPercentEncoding(withAllowedCharacters: charSet )
    复制代码

    我们来看一下最终结果

    OK,符合需求!

    转载于:https://juejin.im/post/5a68417051882573351a7aa1

    展开全文
  • 1. iOS网络请求的概述 当前我们开发的应用,可以说几乎每个应用都要用到网络请求,简直可以称之为“无网络不应用” 。而在众多网络请求中, AFNetorking作为一个第三方的框架,可以说是应用最为广泛的,而且我们也...

    1. iOS网络请求的概述

           当前我们开发的应用,可以说几乎每个应用都要用到网络请求,简直可以称之为“无网络不应用”  。而在众多网络请求中, AFNetorking作为一个第三方的框架,可以说是应用最为广泛的,而且我们也可以基于AFNetorking进行一些适用于项目的二次封装,以便更好的达到自己的需求。

      2. AFNetworking的应用

          2.2.1  AFNetworking

           AFNetowking是一款颇受欢迎的第三方框架,所谓第三方,就是说这个网络请求不是苹果公司出品的。AFNetorking从发布之日起,就得到了众多iOS开发者的青睐。迄今,已经发展到了3.0版本,为了契合iOS新版本的升级,AFNetorking在3.0版本中删除了所有基于NSURLConnection API的支持。如果你之前使用过基于AFNetorking 2.0X版本中的NSURLConnection API,虽然还能用,但是还是建议升级到基于NSURLSession API的AFNetorking 3.0X版本。

            AFNetorking 3.0X正式支持iOS7, Mac OS X的10.9、watchOS2、tvOS9和Xcode7。AFNetorking 3.0版本中,NSURLConnection的API已经被弃用。

           为了便于深入理解AFNetworking的来龙去迈,还是从AFNetorking 1.0说起吧。
    AFNetorking 1.0 是建立在NSURLConnection的基础API之上,AFNetorking 2.0开始使用NSURLConnection的基础API和部分NSURLSession基础之上的API。现有的AFNetorking 3.0版本已经完全基于NSURLSession的API,这样一来,不仅降低了维护代码的工作量,同时也支持NSURLSession提供的任何额外的功能。

            在Xcode7中, NSURLConnection API已经正式被苹果公司弃用,尽管基于NSURLConnection的API仍然可以正常运行,但不会再添加什么新功能了。苹果公司已经明确表示,今后所有与网络有关的功能,都是基于NSURLSession的API的基础上的。

           任何新的技术出现之前,原有的技术仍会持续一段时间,AFNetorking也不例外。AFNetworking  2.0X将继续获得关键的安全补丁,仍然可以放心使用,但不会增加新的功能了。

           下面的类已从AFNetorking 3.0中弃用。

             AFURLConnectionOperation

            AFHTTPRequestOperation

           AFHTTPRequestOperationManager

        AFNetworking 2.0X是这样请求数据的。

          AFHTTPOperationManager  *manager = [AFHTTPOperationManager manager];

          manager.responseSerializer = [AFHTTPResponseSerializer serializer];

          [manager GET:@"请求的url" parameters:nil success:^(AFHTTPRequestOperation *operation ,id responseObject){

    NSLog(@"成功");

    }

    failure:^(AFHTTPRequestionOperation *operation, NSError *error){

    NSLog(@"失败");

    }];

    在AFNetowrking 3.0中,HTTP网络请求返回的不再是AFHTTPRequestOperation,而是NSURLSessionDataTask,对于AFNetworking 3.0来说,网络请求代码示例如下:

      AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

        [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

    NSLog(@"成功")};

    failure:^(NSURLSessionDataTask *task, NSError *error){

    NSLog(@"失败")};

    ];

    2.2.2  AFNetworking 框架使用方法

            在工程中引入AFNetworking框架是一件颇为简单的事,这就得再用一个第三方管理工具--Cocoapods。在iOS项目开发中,稍微像回事的工程,动不动就得引进十来个第三方的。为了有效管理这些第三方,才会出现了“管理第三方的第三方”--Cocoapods。关于Cocoapods的安装和使用,这里不做过多描述,详细资料可参阅关于Cocoapods使用的文章。

          Cocoapods是iOS最常用的第三方类库管理工具,绝大部分有名的开源类库都支持Cocoapods.正因为AFNetworking支持Cocoapods,才使得应用起来非常简单。如果你已经安装好了Cocoapods,只需要以下几步就能轻松引入AFNetworking.

      (1) 创建podfile文件。 打开MAC电脑上的终端窗口,在指定的工程路径下创建podfile文件,命令是:
           touch podfile

           创建成功后,会自动生成一个podfile文件,它是一个文本文件,可自行编辑。需要注意的是,为确保特殊字符的兼容性,最好在Xcode中打开这个podfile文件进行编辑;如果用textEditor,会出现字符不识别现象,这个特殊的字符就是单引号的处理。

    (2)编辑podfile,引入AFNetworking的脚本,如下所示。

             platform:ios,'8.0'

             pod 'AFNetworking'

             保存这个podfile文件并退出。

     (3) 下载AFNetworking。在MAC终端窗口,输入命令:

           pod install

       这个时候,等待安装完成吧。这过程需要几分钟的时间,就看网络是否给力了,如果出现异常,也不必惊慌,初学者在遇到错误时,第一反应就是从网上搜索答案,结果出现错误越来越多。殊不知,换个好一点的网络,所有的错误瞬间消失。

     (4)验证AFNetworking是否可以调用。在Cocoapods创建成功后,所有的工程会自动生成一个对应的xcworkspace工程(工作空间)。后续使用时,一定要打开这个以xcworkspace为后缀的工程文件。如果仍然打开xcodeproj文件,编译时会报错,原因就是缺少了AFNetworking这个文件造成的。

             验证AFNetworking能否正常工作的方法很简单,在工程文件中,添加以下代码。

          #import <AFNetworking.h>

      只需要引入一个AFNetworking API,编译一下,如果正常,说明成功;反之,失败;

    2.2.3  影响网络请求的几个条件

            对于一个app来说,网络请求是必不可少的,而且会在多处用到。稍微复杂些的app,总觉得网络请求的模块很乱,既然说网络请求没有技术难点,那么,这种“乱”的感觉又是从何而来的呢?要解决这个问题,首先得了解下iOS网络请求的过程。

            确定网络请求的URL,URL的全称是 Uniform Resource Location(统一资源定位符),通过一个URL,能偶找到互联网上唯一的一个资源。网址就是资源,我们所需要的数据存在服务器端,App根据网址(NSURL)向后台发送请求(NSURLRequest)。

           1. 网络请求的方式

            APP与后台服务器之间的数据交换,是通过网络请求的方式来实现的。网络请求最常用的方法有两种:GET请求和POST请求。需要说明的是,网络请求并不是iOS所独有的,它是整个互联网通信共有的技术,可见其应用的广泛。对于iOS开发者来说,需要对GET和POST有一些基本的认识。

         2. GET请求和POST请求的区别

             GET请求的参数和POST请求的参数的区别

             1. GET请求的接口会包含参数部分,参数会作为网址的一部分,服务器地址与参数之间通过字符“?”来间隔;POST请求会将服务器地址与参数分开,请求接口中只有服务器地址,而参数会作为请求的一部分,提交后台服务器。

            2. GET请求参数会出现在接口中,不安全;而POST请求相对安全。

            3. 虽然GET请求和POST请求都可以用来请求和提交数据,但通常情况下GET多用于从后台请求数据,而POST多用于向后台提交数据。 

              关于GET和POST的选择,可参考以下几点:

            (1)如果要传递大量数据,如文件上传,只能用POST请求。

            (2) GET的安全性要比POST差些,如果包含机密或敏感信息,建议用POST。

            (3)如果是仅仅是获取数据(数据查询,建议用GET。

            (4)如果是增加、修改、删除数据,建议使用POST。

         3.发送请求

            iOS向服务器端发送请求,建立客户端与服务器端的连接(NSURLConnection或NSURLSessionTask),连接的方式有两种:同步与异步。

       (1)同步连接:当建立同步连接时,请求发送出去以后,等着后台返回数据。只要后台还没有返回数据,那么其他的操作都不能进行。对于代码来说,只要同步请求尚未结束,它下面的代码就不会执行。

       (2)异步连接:请求发送出去以后,不用等待,即便后台的数据还没有返回,但仍然可以进行其他操作。在代码中的表现是就是,发送了请求以后,即使数据未返回,它下面的代码也可以继续执行。异步实现的方式有两种:一种是通过代理(Delegate),另一种是通过Block回调。

      4. 获取服务器的返回数据

        服务器在得到客户端的请求之后,不管成功还是失败,都会返回相应的数据。如果网络连接问题,会给出连接超时的信息。      

       服务器的返回数据存放在NSURLRequest中,NSURLRequest包括响应头和响应体,我们就是从这个NSURLRequest中提取所需要的数据的。

      在清楚了网络的请求机制之后,我们再来审视一下之前的这段代码示例:

      AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

        [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

       NSLog(@"成功")};

       failure:^(NSURLSessionDataTask *task, NSError *error){

       NSLog(@"失败")};

       ];

      上面这段网络请求的代码,共包括以下几个参数:

      1. 请求参数

      2. 请求的URL

      3. 请求的参数(Parameters)

      4. 成功返回的Block(success)

      5. 失败返回的Block(failure)。

      仅从这段代码来看,AFNetworking的应用再简单不过了。问题在于,实际项目中,在很多地方都会用到网络请求。在调试过程中,URL也需要不断地变化,比如,测试环境下的URL与生产环境的URL并不相同;网络请求的方法和接口也是多种多样的。对URL和请求方法,如果缺乏统一的管理,就会带来巨大的维护工作量。

    2.2.4  善用URL宏定义

        在实际项目中,经常可以看到类似这样的代码。

      #define  kOrderServerUrl  @"http://192.168.10.172:8899/proj/app/order"

     #define   kMineServerUrl  @"http://192.168.10.172:8899/proj/app/mine"

     #define   kStoreServerUrl  @"http://192.168.10.172:8899/proj/app/store"

          乍一看,这段代码没有什么缺陷。在宏定义文件中,通过#define声明几个URL,又怎么了呢?

    或许你已经发现,同样的代码,同样的IP地址,在多个地方重复出现,恐怕不妥吧!每当看到这类代码时,在我耳边时常想起“面向对象的编程思想”的警示。作为一名程序员,我们应该追求代码更加简洁,优雅。

            这里仅仅是给出一个代码片段,才不过三个URL而已。在实际项目中,时常多大几十个URL。通篇的相同的IP地址,不仅仅是带来审美的疲劳,更大的问题是:在开发阶段,这个IP地址需要经常变动,至少需要区分一下测试环境和生产环境。我们期望的是,在改动IP时,只需要改动一个地方,改动一次;而不是到处搜索(Search),到处替换 (Replace)。

            其实,很简单,只需要再添加一个宏定义。如果留意的话,你会发现,一个好的框架必然会用到大量的宏定义。宏定义的妙处,不在于语法上的替换,而是原本复杂的代码经过宏定义后,瞬间简洁了很多,改进后的代码如下:

           //如果请求地址和端口发生变化,只需要这里修改

          #define SeverDomain @"http://192.168.10.10:8899/"

          #define  kOrderServerUrl    SeverDomain@"proj/app/order"

          #define   kMineServerUrl    SeverDomain@"proj/app/mine"

          #define   kStoreServerUrl    SeverDomain@"proj/app/store"

         改进后,冗余的代码去掉了,如果请求地址和端口发生了变化,只需要在这里修改一次。这个看上去不起眼的改动,不仅减少了代码量,而且也避免了因改动多处而造成的低级错误。

       2.2.5 URL接口统一管理

            关于URL的应用,在实际项目中,也会看到类似下面的这段代码。

            AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

            [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

             NSLog(@"成功")};

            failure:^(NSURLSessionDataTask *task, NSError *error){

            NSLog(@"失败")};

            ];

          从代码所实现的功能来看,无可厚非;但从网络请求的维护性和潜在的问题来看,这段代码是不可取的,这是因为:

           1.  URL是网络请求与后台交互的接口,这个接口定义至关重要,应该放到一个.h文件中统一管理。

           2. URL应该由宏定义来声明,而不是用字符串拼接的方式来构建。二者最大的区别就是,在调用宏定义时,Xcode会自动跟谁,如果拼写有误,编译会报错;而Xcode不会检查字符串拼写是否有误,即使拼写有误,编译器也不会报错。

           改进后的代码,示例如下,在.h文件中,通过宏定义声明URL,如下图所示。

          #define SeverDomain @"http://192.168.10.10:8899/"

          #define  kOrderServerUrl    SeverDomain@"proj/app/order"
         当然,字符串的拼接也可以调用方法 stringByAppendingString:来实现。换一种表示方法,如下所示。

         #define  kOrderServerUrl   [SeverDomain stringByAppendingString:@"proj/app/order"]

         在.m文件中,当出发网络请求时,添加以下代码:

        AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

            [session GET:kOrderServerUrl parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

             NSLog(@"成功")};

            failure:^(NSURLSessionDataTask *task, NSError *error){

            NSLog(@"失败")};

            ];

           通过以上几行代码的改进,你会发现,代码瞬间规整了许多,可能出现的低级错误从根源上得以灭绝。当团队多人协作共同完成一个App时,如果每个人都自觉遵循一个约定的编程风格,编码的世界该是有多么美好。    

         2.2.6  AFNetworking的二次封装

           网络请求之所以看起来复杂,主要是受到内外两方面因素的影响。从内部因素来讲,iOS自身的网络请求机制也在护短的改进,从早期的NSURLConnection到今天的NSURLSession;从外部因素来讲,以AFNetworking为代表的第三方网络库也在不断的升级。第三方的网络库再强大,也离不开iOS网络框架的支撑,所以,一旦iOS自身网络框架发生变化,第三方的网络库必然随之而变。

           既然AFNetworking已经很好用了,为何还要对它进行再封装呢?答案很简单,正是因为网络库经常变化,才做二次封装。

           对AFNetworking进行二次封装后,使用起来会更加方便,实用性更强。封装的原则是,对经常变化的部分(如网络库)进行封装,把封装之后的API提供给自己的工程使用。即使以后网络库更新了,我们也只需要更新这个封装好的网络库即可。   

           如果直接使用iOS原生的NSURLConnection或NSURLSession,也同样可以实现App的网络请求,但是只不过代码量无形中会增加很多,代码量一大,维护起来就很苦难。

          对于使用AFNetworking的开发者来说,如果是直接调用AFNetworking的API,这样不是很理想,无法做到整个工程的统一配置,最好的方式就是对网络层再封装一层,全工程不允许直接使用AFNetworking的API,必须调用我们自己封装的API,如此一来,任何网络配置都可以在这一层配置好,使用者无法知道里面实现的细节,只好调用就可以了。

           1. 对GET和POST请求的理解

           2. 对无线网络的判断

           3. 请求超时的时间配置

           4. 网络请求出现异常时,给出提示信息

           对于AFNetworking的二次封装,乍一听很在理,其实过度封装,也会带来局限性。很多情况下,这种封装是针对业务需要的,大可不必刻意地为封装而封装。

         2.3   AFNetworking的序列化问题

            谈到App与后台的接口调试,尤其是在初期阶段,着实让人头痛。在约定接口时,经常说的一句话就是,前端与后台都要通过HTTP通信。这句话看似简单,其实到了调试阶段则会变得复杂,这里给出一个精彩遇到的场景:就拿请求验证码来说,App通过POST方式将手机号发给后台,格式如下:

               请求参数={

              cellnumber = 186116198xx;

          }

          后台返回的数据显示,“手机号为空,请重新输入!”

          iOS通过输出log信息看出,分明已经提交给后台了,而且其他工程的代码也是这么做的啊,为什么偏偏对整个后台就报错了呢?

          与后台调试接口,难就难在后台也不会轻易“认错”。后台的调试方法是,直接通过浏览器地址栏输入请求的URL和请求的参数来验证后台是不是有问题。就拿比较简单的验证码来说,在浏览器地址栏输入"http://192.168.10.10:8899/getCode?cellnumber=186116198xxx"。短信验证码还真的收到了,这样说来,后台也没有问题啊。那么究竟是哪里出了问题呢?

           经过分析发现,这是因为iOS数据请求的序列化格式问题。后台可接收的虽然是HTTP协议格式,但具体到哪类数据格式又有区分了。这个时候,要重点查看以下的代码。

         使用常规的AFNetworking访问网络,首先需要创建AFHTTPSessionManager的实例对象,代码如下:

         AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

         所有的网络请求,均有manager发起。需要注意的是,默认提交的数据请求格式是二进制的,后台返回的数据格式是JSON,代码如下。

         //iOS请求数据编码为二进制格式

         manager.requestSerializer = [AFHTTPRequestSerializer serializer];

       //后台数据返回数据编码是JSON格式

        manager.responseSerializer = [AFJSONResponseSerializer serializer];

          所谓默认的格式,意思是说,上面这两行代码可写可不写。不写这两行代码,默认的就是这两种格式。如果不是这两种格式,就得写代码了。比如,如果数据请求的编码是JSON的,需要将请求格式设置为:

          manager.requestSerializer = [AFJSONRequestSerializer serializer];

         如果后台返回的数据编码不是JSON,而是二进制格式,这时需要将数据响应格式设置为:

         manager.responseSerializer = [AFHTTPResponseSerializer serializer];

     2.3.1    AFNetworking请求格式

           通过对requestSerializer的设置来区分AFNetworking数据请求的序列化格式,网络请求的序列化编码有以下三种:

           (1) AFHTTPRequestSerializer: 普通的HTTP编码格式,也可以理解为二进制格式,类似于"cellnumber=186116198xxx&token=123456",这种格式就是可以在浏览器上直接访问的格式。

          (2) AFJSONRequestSerializer: 是JSON编码格式,请求格式类似于"{"cellnumber":"186116198xxx","token":"123456"}"。

         (3)  AFPropertyListRequestSerializer: 属于plist格式,这种格式很少用,也可以理解为一种特殊的XML格式,解析起来相对容易。

           在AFNetworking开源库的AFURLRequestSerializer.h文件中,可以看出AFHTTPRequestSerializer与AFJSONRequestSerializer的关系:

           'AFJSONRequestSerializer' is a subclass of 'AFHTTPRequestSerializer' that encodes parameters as JSON using 'NSJSONSerializer',setting the 'Content-Type' of the encoded request to 'application/json'。

          意思是说,AFJSONRequestSerializer是AFHTTPRequestSerializer的子类,AFJSONRequestSerializer可以通过JSON数据格式请求后台,同时,将Content-Type的编码设为application/json类型,  设置方式如下。

          manager.responseSerializer.acceptableContentType = [NSSet setWithObjects:@"text/html",@"text/plain",@"application/json",nil];   

     2.3.2  AFNetworking响应格式

           与网络请求格式相对应的是后台的数据响应格式。AFNetworking给出了以下几种格式。

           1.  AFHTTPResponseSerializer:二进制格式

           2.  AFJSONResponseSerializer: JSON格式

           3.  AFXMLParserResponseSerializer: XML格式,只能返回XMLParser,还需要自己通过代理方法解析。

           4.  AFPropertyListResponseSerializer: Plist格式。

           5.  AFImageResponseSerializer: Image格式。

           6. AFCompoundResponseSerializer:组合形式。

          2.4  异步请求数据并刷新UI界面

            在处理iOS网络请求时,掌握多线程编程是必须的。应该说,只要是网络请求,都会遇到多线程的问题,不仅仅是iOS要求这样,其他的比如Android、Java开发,都会遇到大量的后台运行、多线程池、异步消息队列等问题,这些都要运用多线程技术来实现。虽然多线程技术看起来很高深,其实需要我们自己编写多线程的地方并不多,当我们调用iOS SDK发起一个网络请求时,系统都会默认地自动开辟一个线程去处理。从而给人的感觉就是,整个iOS APP基本上就是Main主线程中执行的。

           App中所有的触发动作,都是用户在页面上操作控件完成的,用户触摸控件,触发新的时间,后台处理完成后,更新UI界面。只要是UI控件的更新,都是在主线程处理完成的。

           开辟一个子线程,最经典的莫过于文件的下载,如离线地图的下载。下载地图时,每个省市的数据包都是独立的,需要单独下载。我们可以同时下载多个省市的数据包,也可以一个接一个的下载。只要单击某个省市的下载按钮,系统就会启动一个新的子线程来请求网络数据。为了显示下载速度,还需要边下载边更新主线程的UI,iOS提供了GCD机制来完成多线程的下载。

          GCD(Grand Central Dispatch)是Apple开发的一个多核编程的解决方法,这里给出了一个GCD应用的简单示例。

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

            NSError *error;

            NSString *data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];

            if (data != nil) {

                dispatch_async(dispatch_get_main_queue(), ^{

                    NSLog(@"根据后台返回的数据,更新UI");

                });

            }

            else{

                NSLog(@"error when download:%@",error);

            }

        });

           这段代码中的一个重要的方法是dispatch_async。在处理耗时的操作时,比如,下载超大的文件,为避免界面冻屏(Freeze),我们会另外开辟一个子线程请求网络数据,待下载完成后,再通知主线程更新UI界面。这时候,用GCD来实现这个流程的操作比传统的NSThread、NSOperation方法都要简单,代码框架结构如下:

       

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            //处理耗时的任务,如下载

            dispatch_async(dispatch_get_main_queue(), ^{

                //后台任务完成,更新界面

            });

        })

      2.5  远程文件下载

         2.5.1 基于AFNetworking的文件下载

           每当谈到文件下载时,总会有一种望而生畏的感觉,同时下载多个文件怎么办?下载中网络出现异常情况,网络恢复后还能自动接着下载吗?对于好几个GB的超大文件能顺利下载吗?的确,网络下载可以做的很全面,也可以做的很简单,这取决于产品的业务需求。对于一个APP来说,下载都是为了业务服务的,而不是一个强大的下载工具。只要在一定的网络条件下,能满足业务需求就可以了。

            文件下载的实现方法,总体来讲有两种,一种是基于AFNetworking,另一种是基于原生的iOS SDK。前面讲到,AFNetworking也是基于iOS SDK封装的第三方库。iOS网络请求经历了NSURLConnection和NSURLSession两个阶段,于此相对应,AFNetworking 2.0与3.0版本均实现了文件的下载。我们与时俱进,对于AFNetworking 2.0不再赘述。通过AFNetworking 3.0实现文件的下载代码示意如下。   

            #import "ViewController.h"

           #import 'AFNetworking.h'

          @interface ViewController()

         {

            //下载操作

            NSURLSessionDownloadTask *_downloadTask;

        }

       @end

       @implementation ViewController

    -(void)downFileFromServer

     {

             NSURL *URL = [NSURL URLWithString:@“http://www.baiidu.com/img/bdlogo.png”];

                 NSURLSesstionConfiguration *configuration = [NSURLSesstionConfiguration defaultSesstionConfiguration];    

               //AFNetworking 3.0+基于URLSession封装

             AFURLSessionManager *manager = [AFURLSessionManager alloc]initWithSessionConfiguration:configuration];

           //请求    

           NSURLRequest *request = [NSURLRequest requestWithURL:URL];

          //下载操作

         _downloadTask = [manager downloadTaskWithRequest:request  progress:^(NSProgress *_Nonnull downloadProgress){

             //downloadProgress 的两个属性

             //@property int64_t totalUnitCount;需要下载文件的总大小

             //@property int64_t completedUnitCount;当前已经下载的大小

              //给Progress添加监听 KVC

              NSLog(@“下载的进度 = %f”,1.0*downloadProgress.completedUnitCount/downloadprogress.totalUnitCount)

             //切换到主线程刷新UI,通过progressView显示下载速度

            dispatch_async(dispatch_get_main_queue(),^{

                self.progressView.progress =  1.0*downloadProgress.completedUnitCount/downloadprogress.totalUnitCount;

            });

        destination:^NSURL *_Nonnull(NSURL *_Nonnull targetPath,NSURLResponse *_Nonnull response){

            //Block的返回值,需要返回一个URL,返回的这个URL就是文件下载后所在的路径

            NSString *cachesPath = [NSSearchPathForDirectoriesInDomain(NSCacheDirectories,NSUserDomainMask,YES) lastObject];

            NSSreing *path = [cachesPath StringByAppendingPathComponent:response.suggestedFilename];        

            return [NSURL fileURLWithPath:path];

        }

        completionHander:^(NSURLResponse *_Nonnull response,NSURL *_Nonable file Path,NSError *_Nonnull error){

            //filePath就是文件下载的路径:如果是zip文件,需要在这里解压缩

            NSString *imgFilePath = [filePath path];//将NSURL转换成NSString

            UIImage *img = [UIImage imageWithContentsOfFile:imgFilePath];        

            self.imageView.image = img;

        }];

            };

          以上这段代码实现了文件的下载和存储。这里需要注意NSURL的使用,在请求网络时,必须调用NSURL的URLWithString,这样才可以请求到网络的URL,示意代码如下:
          NSURL *URL = [NSURL URLWithString:@“http://www.baiidu.com/img/bdlogo.png”];

          而在访问本地文件时,必须调用NSURL的fileURLWithPath方法,示意代码如下:
           [NSURL fileURLWithPath:path];

         开始下载的方法为:

        [_downloadTask resume];

        暂停下载的方法为:

       [_downloadTask suspend];

        2.5.2 基于NSURLSesstion的文件下载

             关于iOS的文件下载,早期是基于NSURLConnection实现的,原本这种机制已经足够强大:正如人们常说的,没有最好只有更好。在iOS7,Apple官方推出了另外一种文件下载模式,这就是NSURLSession。有了这个神器,即使应用程序进入后台,也依然可以下载文件。

            与NSURLConnectio的使用类似,我们也要通过实现NSURLConnectio的代理方式来完成下载的任务。这个代理就是NSURLSessionDownloadDelegate,我们需要实现它的以下四个代理方法。

           - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                               didWriteData:(int64_t)bytesWritten

                                          totalBytesWritten:(int64_t)totalBytesWritten

                                  totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

            -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompletedWithError:(NSError *)error;

             - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                          didResumeAtOffset:(int64_t)fileOffset

                                         expectedTotalBytes:(int64_t)expectedTotalBytes;

           - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                  didFinishDownloadingToURL:(NSURL *)location;

             当使用NSURLConnection时,每次网络请求都会创建一个链接;而使用NSURLSession时,每次网络请求都会创建一个 Session(会话),所以在用到NSURLSession时,首先要创建一个Configuration(配置),让这个Configuration运行在后台;这个Configuration的ID应该是唯一的,最好是应用程序的bundle identifier,如com.yourCompany.appName;然后基于Configuration创建一个Session,代码如下:

              NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"myUniqueAppID"];

                _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];

              创建了Configuration后,就可以初始化Session。在初始化Session时,设置好session的Delegete,并把delegateQueue设为mainQueue

              我们的目标是下载网络上的文件,一旦可以获取到网络的URL,就可以通过Session创建一个下载的任务,所以我们呢downloadTask是必不可少的,代码如下:

          //一个带有URL的请求

          NSURLSessionDownloadTask *task = [_session downloadTaskWithRequest:request];

         [task resume];

         当downloadTask开始下载文件时,它会把文件先下载到一个临时目录文件下,即使没有设置这个存储路径也没有关系,系统会自动创建,不用担心。

        在文件下载的过程中,它会告知下载的进度,已经下载了多少,我们再通过主进程的调用,通过下载的进度条提示给用户,代码如下:

              - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                               didWriteData:(int64_t)bytesWritten

                                          totalBytesWritten:(int64_t)totalBytesWritten

                                  totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

           CGFloat percentDone = (double)totalBytesWritten/ (double)totalBytesExpectedToWrite//通知下载的进度

    }

          一旦文件下载完毕,就会调用以下Delegate方法。

          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                  didFinishDownloadingToURL:(NSURL *)location{

            //文件下载完成后,将下载的临时文件存储到永久性的目录中,并删除临时文件。如果是压缩文件,解压的过程也是在这里完成的。
    }

           以上就是基于NSURLSession实现文件下载的流程。总的来说,实现文件的下载没有想象的那么难。这一切,正是基于NSURLSession为我们提供强的后盾。

          2.5.3  网路安全访问设置

           从iOS9开始,苹果新增了App Transport Security(ATS),之前的网络请求是HTTP协议,现在都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用HTTPS协议了。这个改动的直接影响是,Xcode7默认的HTTPS泄题,如果仍然想进行HTTP请求,运行时就会出现如下错误。

            App Transport Security blocked a cleartext HTTP(http://) resource load  since it is insecure. Temporary  exceptions can be configured via your app's Info.plist file.

           系统告诉我们不能直接使用HTTP进行请求,需要在Info.plist文件中新增一段用于控制ATS的配置,方法如下。

           1. 在Info.plist中添加NSAppTransportSecurity类型Dictionary。

           2. 在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES。

           经过以上两步的设置后,Info.plist配置如下图所示

          这种基于通行界面的设置,看起来并不简单,经常出现错位的情况。如果不习惯这种操作,还可以采用代码编辑方式;在左侧工程导航栏,找到Info.plist文件,右键单击Open As-Source Code。添加NSAppTransportSecurity项,代码如下:

         

                 2.6 小结

                在iOS网络请求的第三方框架中,AFNetworking一枝独秀。可以说,只要跟网络有关的,没有AFNetworking解决不了的。有了AFNetworking,原本看似神秘的网络操作,瞬间变得如此的简洁易用,以至于每一个iOS开发者都可以做到驾轻就熟。

    展开全文
  • 本文转载自:https://juejin.im/post/5b1cb5805188257d507be5d4所有权归原文所有 WWDC 2018 Session 401 What's New in Swift? 这个 Session 分为两个部分,前半部分会简单介绍一下 Swift 开源相关的...

    本文转载自:https://juejin.im/post/5b1cb5805188257d507be5d4
    所有权归原文所有

     

    WWDC 2018 Session 401 What's New in Swift?

    这个 Session 分为两个部分,前半部分会简单介绍一下 Swift 开源相关的事情,后半部分我们深入了解一下 Swift 4.2 带来了哪些更新。

    社区的发展

    首先我们来看一下 Swift 的一些统计数据,Swift 自开源之后,总共有 600 个代码贡献者,合并了超过 18k pull request。

    社区主导的持续集成

    Swift 想要成为一门跨平台的泛用语言,大概一个月之前,Swift 团队拓展了原有的公开集成平台,叫做 Community-Hosted Continuous Integration,如果大家想要把 Swift 带到其它平台上,就可以在这上面去接入你们自己的硬件机器,Swift 会定期在这些硬件上跑一遍集成测试,这样大家就可以非常及时地了解到最新的 Swift 是否能在你们的平台上正常运行。

    目前已经接入了 Fedora 27 / Ubuntu on PowerPC / Debian 9.1 on Raspberry Pi 等等:

     

     

    Swift 论坛

    同时,Swift 的团队付出了很大的精力在维护 Swift 的社区上,两个月前 Swift 社区正式从邮件列表转向论坛,让大家可以更容易贡献自己的力量,例如说三月份的这一份提案:

     

    SE-0200

     

    大家只要简单回答这些问题,参与讨论即可,如果你对于这方面的理解不深,不想贸然发言的话,其实只要大概阅读过社区成员们的发言,对这件事情有了解,那也是一种参与,以后也许这个提案出来了你还可以写篇文章跟大家讲讲当时讨论的内容和要点。

    如果你在维护一个 Swift 相关的计划,你可以考虑在论坛上申请一个板块,让社区的人也可以关注到你的计划并且参与到其中来。

     

     

    Swift 的文档现在改为由 swift.org 来维护,网址是 docs.swift.org

    Chris Lattner

    Chris Lattner 大神离开苹果的时候,有很多人在讨论这会不会对 Swift 的发展有不好的影响,但过去一年,实际上 Chris 也还是尽自己的力量在推动 Swift 发展,去谷歌甚至可以说是在那里做 Swift 的布道师。

    Chris 进了谷歌之后,谷歌 fork 了一个 Swift 的仓库,作为谷歌里开发者 Commit 的中转站,过去一年修复了很多 Swift 在 Linux 上的运行问题,让 Swift 在 Linux 上的运行更加稳定。谷歌还写了一个 Swift Formatter,现在正在开发阶段。

    并且促成了 Swift 与 Tensorflow 的合作,开发了 Swift for Tensorflow,主要是因为 Python 已经渐渐无法满足 Tensorflow 的使用,上百万次的学习循环让性能表现变得异常重要,需要一门语言去跟 Tensorflow 有更紧密的交互,大家可能觉得其它语言也都可以使用 Tensorflow,没有什么特别,实际上其它语言都只是开发了 Tensorflow 的 API,而 Swift 代码则会被直接编译成 Tensorflow Graph,具有更强的性能,甚至 Tensorflow 团队还为 Swift 开发了专门的语法,让 Swift 变成 Tensorflow 里的一等公民。加入了与 Python 的交互之后,让 Swift 在机器学习领域得到了更加好的生态。

    Chris 在过去一年,拉谷歌入局一起维护 Swift,加强 Swift 在 Linux 上的表现,还给 Swift 开辟了一个机器学习的领域,并且在 Swift 社区持续活跃贡献着自己的才华,现在我想大家完全可以不必担心说 Chris 的离开会对 Swift 产生什么不好的影响。

     

     

    并且 Swift 的开发团队和社区里也有很多做出了巨大贡献的大神,例如这一次 Session 的主讲之一 Slava,核心团队负责人的 Ted,Doug Gregor,Xiaodi Wu 等等,他们也一样把自己的精力和才华贡献给了 Swift。

    What is Swift 4.2?

    接下来我们要了解一下 Swift 4.2,那么 Swift 4.2 是什么?它在整个开发周期中是一个什么样的角色?

    Swift 每半年就会有一次 Major Release,Swift 4.2 就是继 4.0 和 4.1 之后的一次 Major Release,官方团队一直致力于提升开发体验,这是 Swift 4.2 的开发目标:

    • 更快的编译速度
    • 增加功能提升代码编写效率
    • 让 SDK 提供 Swift 更好的支持
    • 提升 ABI 的兼容性

     

     

    Swift 5 会在 2019 年前期正式发布,ABI 最终会在这一个版本里稳定下来,并且 Swift 的运行时也会内嵌到操作系统里,到时候 App 的启动速度会有进一步的提升,并且打包出来的程序也会变得更小。

    如果大家对于 ABI 稳定的计划感兴趣的话,可以关注一下这一份进度表 ABI Dashboard

    编译器的改进

    代码兼容性

    跟 Xcode 9 一样,Xcode 10 里也只会搭载一个 Swift 编译器,并且提供两种兼容模式,同时兼容之前的两个 Swift 版本,这三种模式都可以使用新的 API,新的语言功能。

     

     

    并且不只是 Swift 的语法层面的兼容,开发组三种模式也同时覆盖 SDK 的兼容,也就是说只要你的代码在 Xcode 8,Swift 3 的环境下能跑,那么在 Xcode 10 里使用兼容模式也肯定可以跑起来。

    但 Swift 4.2 确实提供了更多优秀的功能,为了接下来的开发,这会是最后一个支持 Swift 3 兼容模式的版本。

    更快的 Debug 编译速度

    接下来我们来讨论一下编译速度的提升,这是在 Macbook Pro 四核 i7 上测试现有 App 的结果:

     

     

    Wikipedia 是一个 Objective-C 和 Swift 混编的项目,可能更加贴近大家的实际项目,项目的编译速度实际上取决于很多方面,例如说项目的配置,图片文件的数量跟大小。

    1.6 倍的提升是整体的速度,如果我们只关注 Swift 的编译时间的话,实际上它总共提升了 3 倍,对于很大一部分项目来说,一次全量编译大概可以比以前快两倍。

    这些提升来自于哪里呢?由于 Swift 里并不需要导入头文件,但每一个文件由可以访问到模块里的其他文件里的内容,所以编译阶段会有大量的重复工作去进行 symbol 查找,这次编译器构建了一个编译 pipeline 去减少重复的跨文件执行。

    Compilation Mode vs. Optimization Level

     

     

    另外这一次,把“编译模式”从“优化级别”里剥离了出来,编译模式意味着我们如何编译我们的模块,目前总共有两种模式:

    • 增量化编译(Incremental):也就是以前的 Single File,逐个文件编译。
    • 模块化编译(Whole Module):整个模块一起编译。

    增量编译虽然全量编译一次会比模块化编译慢,但是之后修改一次文件就只需要再编译一次相关的文件即可,而不必整个模块都重新编译一次。

    整个模块一起编译的话会更加快,据说原理是把所有文件都合并为一个文件,然后再进行编译,以此减少跨文件的 symbol 查找。但一旦改动了其中一个文件,就需要重新再把整个模块编译一遍。

    增加了这个编译选项实际上还有一个很重要的意义,以前我们只有三种选项,可以达到下面三种效果:

     增量化编译模块化编译
    优化
    不优化

    优化是需要消耗时间的的,现在我们可以使用模块化并且不优化的选项,达到最快的编译速度,把这个选项应用到我们项目里不经常改动的那一部分代码里的话(例如 pod 的依赖库),就可以大大提高我们的编译速度。

    我把这个配置应用到项目里之后,实测编译速度从 113s 加快到了到了 64s,只要在 podfile 里加入这一段代码就可以了(在 Xcode 9.3 也可以正常使用):

    post_install do |installer|
      # 提高 pod 库编译速度
      installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
          config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule'
          if config.name == 'Debug'
            config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
          else
            config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize'
          end
        end
      end
    end
    

    Runtime 优化

    ARC

    Swift 使用 ARC 进行内存管理,ARC 是在 MRC 的基础上演进出来的,ARC 使用某种对象管理模型在编译时,在合适的位置自动为我们插入 retain 跟 release 代码。

    Swift 4.2 之前使用的模型是“持有(owned)”模型,调用方负责 retain,被调用方负责 release,换句话就是说被调用方持有了传进来的对象,如下图所示:

     

     

    但实际上这种模型会产生很多不必要的 retain 跟 release,现在 Swift 4.2 改为使用“担保(Guaranteed)”模型,由调用方去保证对象在函数调用的生命周期内不会被 release 掉,被调用方不再持有对象:

     

     

    采取了这种模型之后,不止可以有更好的性能表现,还会让编译出来的二进制文件变得更小。

    String

     

     

    当我们在 64bit 的平台上实例化一个 String 的时候,它的长度是 16 bytes,为了存储不等长的内容,它会在堆里申请一段空间去存储,而那 16 个 bytes 里会存储着一些相关信息,例如编码格式,这是权衡了性能和内存占用之后的出来的结果。

    但 16 bytes 的内存占用实际上还存在着优化空间,对于一些足够小的字符串,我们完全可以不必在堆里独立存储,而是放到这 16 个 bytes 里空余的部分,这样就可以让小字符串有更好的性能和更少的内存占用。

    具体原理跟 NSString 的 Tagged Pointer 一样,但能比 NSString 存放稍微更大一点的字符串。

    减小代码尺寸

     

     

    Swift 还增加了一个优化等级选项 "Optimize for Size",名如其意就是优化尺寸,编译器通过减少泛型特例化,减少函数内联等等手段,让最终编译出来的二进制文件变得更小

    现实中性能可能并非人们最关心的,而应用的大小会更加重要,使用了这个编译选项实测可以让二进制文件减小 10-30%,而性能通常会多消耗 5%。

    新的语法功能

    可遍历枚举

    以前我们为了遍历枚举值,可能会自己去实现一个 allCases 的属性:

    enum LogLevel {
        case warn
        case info
        
        static let allCases: [LogLevel] = [.warn, .info]
    }
    

    但我们在添加新的 case 的时候可能会忘了去更新 allCases,现在我们在 Swift 4.2 里可以使用 CaseIterable 协议,让编译器自动为我们创建 allCases

    enum LogLevel: CaseIterable {
        case warn
        case info
    }
    
    for level in LogLevel.allCases {
        print(level)
    }
    

    Conditional Conformance

    Conditional Conformance 表达了这样的一个语义:泛型类型在特定条件下会遵循一个特定的协议。例如,Array 只会在它的元素为 Equatable 的时候遵循 Equatable:

    extension Array: Equatable where Element: Equatable {
        func ==<T : Equatable>(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
    }
    

    这是一个非常强劲的功能,Swift 标准库里大量使用这个功能,Codable 也是通过这个功能去进行检查,帮助我们自动生成解析代码的.

    Hashable 的加强

    与 Codable 类似,Swift 4.2 为 EquatableHashable 引入了自动实现的功能:

    struct Stock: Hashable {
        var market: String
        var code: String
    }
    

    但这会带来一个问题,hashValue 该怎么实现?现有 hashValue 的 API 虽然简单,但却难以实现,你必须想出一种方法去把所有属性糅合起来然后产生一个哈希值,并且像 SetDictionary 这种围绕哈希表构建起来的序列,性能完全依赖于存储的元素的哈希实现,这是不合理的。

    在 Swift 4.2 里,改进了 Hashable 的 API,引入了一个新的 Hasher 类型来存储哈希算法,新的 Hashable 长这个样子:

    protocol Hashable {
        func hash(into hasher: inout Hasher)
    }
    

    现在我们不再需要在实现 Hashable 的时候就决定好具体的哈希算法,而是决定哪些属性去参与哈希的过程:

    extension Stock: Hashable {
        func hash(into hasher: inout Hasher) {
            market.hash(into: &hasher)
            code.hash(into: &hasher)
        }
    }
    

    这样 Dictionary 就不再依赖于存储元素的哈希实现,可以自己选择一个高效的哈希算法去构建 Hasher,然后调用 hash(into:) 方法去获得元素的哈希值。

    Swift 会在每次运行时 为 DictionarySet 提供一个随机的种子去产生随机数作为哈希的参数,所以 DictionarySet 都不会是一个有序的集合了,如果你的代码里依赖于它们的顺序的话,那就修复一下了。

    而如果你希望使用一个自定义的随机种子的话,可以使用环境变量 SWIFT_DETERMINISTIC_HASHING 去控制:

     

     

    更多细节可以查看 SE-0206 提案,不是很长,建议大家阅读一遍。

    随机数产生

    随机数的产生是一个很大的话题,通常它都需要系统去获取运行环境中的变量去做为随机种子,这也造就了不同平台上会有不同的随机数 API:

    #if os(iOS) || os(tvOS) || os(watchOS) || os(macOS)
        return Int(arc4random())
    #else
        return random()
    #endif
    

    但开发者不太应该去关系这些这么琐碎的事情,虽然 Swift 4.2 里最重要的是 ABI 兼容性的提升,但还是实现了一套随机数的 API:

    let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
    let randomFloat = Flow.random(in: 0 ..< 1)
    
    let greetings = ["hey", "hi", "hello", "hola"]
    print(greetings.randomElement()!)
    
    let randomlyOrderGreetings = greetings.shuffled()
    print(randomlyOrderedGreetings)
    

    我们现在可以简单地获取一个随机数,获取数组里的一个随机元素,或者是把数组打乱,在苹果的平台上或者是 Linux 上随机数的产生都是安全的。

    并且你还可以自己定义一个随机数产生器:

    struct CustomRandomNumberGenerator: RandomNumberGenerator { ... }
    
    var generator = CustomRandomNumberGenerator()
    
    let randomIntFrom0To10 = Int.random(in: 0 ..< 10, using: &generator)
    let randomFloat = Flow.random(in: 0 ..< 1, using: &generator)
    
    let greetings = ["hey", "hi", "hello", "hola"]
    print(greetings.randomElement(using: &generator)!)
    
    let randomlyOrderGreetings = greetings.shuffled(using: &generator)
    print(randomlyOrderedGreetings)
    

    检测目标运行平台

    以往我们自定义一些跨平台的代码的时候,都是这么判断的:

    #if os(iOS) || os(watchOS) || os(tvOS)
        import UIKit
        typealias Color = UIColor
    #else
        import AppKit
        typealias Color = NSColor
    #endif
    
    extension Color { ... }
    

    但实际上我们关心的并不是到底我们的代码能跑在什么平台上,而是它能导入什么库,所以 Swift 4.2 新增了一个判断库是否能导入的宏:

    #if canImport(UIKit)
        import UIKit
        typealias Color = UIColor
    #elseif canImport(AppKit)
        import AppKit
        typealias Color = NSColor
    #else
        #error("Unsupported platform")
    #endif
    

    并且 Swift 还新增了一套编译宏能够让我们在代码里手动抛出编译错误 #error("Error") 或者是编译警告 #warn("Warning")(以后不再需要 FIXME 这种东西了)。

    另外还增加了一套判断运行环境的宏,下面是我们判断是否为模拟器环境的代码:

    // Swift 4.2 以前
    #if (os(iOS) || os(watchOS) || os(tvOS) &&
        (cpu(i396) || cpu(x86_64))
        ...
    #endif
    
    // Swift 4.2
    #if hasTargetEnviroment(simulator)
        ...
    #endif
    

    废除 ImplicityUnwrappedOptional 类型

    ImplicityUnwrappedOptional 又被称为强制解包可选类型,它其实是一个非必要的工具,我们使用它最主要的目的是,减少显式的解包,例如说 UIViewController 的生命周期里, viewinit 的时候是一个空值,但是只要 viewDidLoad 之后就会一直存在,如果我们每次都使用都需要手动显式强制解包 view! 就会很繁琐,使用了 IUO 就可以节省这一部分解包代码。

    所以 ImplicityUnwrappedOptional 是与 Objective-C 的 API 交互时很有用的一个工具,所有未被标记上 nullability 的变量都会被作为 IUO 类型暴露给 Swift,它的出现同时也是为了暂时填补 Swift 里语言的未定义部分,去处理那些固定模式的代码。随着语言的发展,我们应该明确 IUO 的作用,并且用好的方式去取代它。

    SE-0054 提案就是为此而提出的,这个提案实际上在 Swift 3 里就实现了一部分了,在 Swift 4.2 里继续完善并且完整得实现了出来。

    以往我们标记 IUO 的时候,都是通过类型的形式去实现,在 Swift 4.2 之后,IUO 不再是一个类型,而是一个标记,编译器会通过给变量标记上 @_autounwrapped 去实现,所有被标记为 IUO 的变量都由编译器在编译时进行隐式强制解包:

    let x: Int! = 0 // x 被标记为 IUO,类型其实还是 Optional<Int>
    let y = x + 1   // 实际上编译时,编译器会转化为 x! + 1 去进行编译
    

    这就更加符合我们的原本的目的,因为我们需要标记的是变量的 nullability,而通过类型去标记的话实际上我们是在给一个标记上 IUO,而并非是变量

    当然,这样的改变也会给之前的代码带来一点小影响,因为我们标记的对象针对的是变量,而并非类型,所以以往作为类型存在的 IUO 就会变成非法的声明:

    let a: [Int!] = [] // 编译不通过
    

    内存独占访问权

    同一时间内,代码对于某一段内存空间的访问是具有独占性,听起来很难懂是吧,举个例子你就明白了,在遍历数组的同时对数组进行修改:

    var a = [1, 2, 3]
    
    for number in a {
        a.append(number) // 产生未定义的行为
    }
    

    Swift 通过内存独占访问权的模型,可以在编译时检测出这种错误,在 Swift 4.2 里得到加强,可以检测出更多非法内存访问的情况,并且提供了运行时的检查,在未来,内存独占访问权的检查会像数组越界一样默认开启:

     

     

    推荐资源

    推荐查看Ole Begemann 大神的出品的 What's new in Swift 4.2,带着大家用 Playground 亲身体会一下 Swift 里新的语法功能。

    结语

    Swift 5 是一个很重要的里程碑,ABI 的稳定意味着这一份设计需要支撑后面好几个大版本的功能需求,延期我觉得不算是一件坏事,大家别忘了,苹果是 Swift 的最大的使用者,这门语言会支撑苹果未来十几年的 SDK 开发和生态,所以他们才会在 ABI 稳定这件事情上更加谨慎小心,而且这也很符合今年苹果的方针,稳中求进。

    待 ABI 尘埃落定之后,Swift 的语法功能肯定还会有一波爆发,async/await,原生的正则表达式...,甚至苹果可能会开发 Swift Only 的 SDK,这些都让我更加期待 2019 年的 Swift 5。


    作者:四娘
    链接:https://juejin.im/post/5b1cb5805188257d507be5d4
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    转载于:https://www.cnblogs.com/yajunLi/p/9197723.html

    展开全文
  • title: "Swift 中枚举高级用法及实践" date: 2015-11-20 tags: [APPVENTURE] categories: [Swift 进阶] permalink: advanced-practical-enum-examples 原文链接=...
  • 资料1 ---教程类 官方文档中文翻译http://wiki.jikexueyuan.com/project/swift/Github上的地址点我 Using Swift with Cocoa and Objective-C ...Swift 开源及跨平台开发swift.org 斯坦福课程Stanford Un
  • 要使用系统分享,由于之前没接触过,上网发现UIActivityViewController可以实现原生分享。UIActivityViewController可以分享文本、图片、链接, NSString *textToShare = @"文本内容"; UIImage *imageToShare = ...
  • Mozilla 的 Rust、AppleSwift、Jetbrains 的 Kotlin,以及许多其它的语言都给开发者在速度、安全性、便利性、可移植性还有能力这些方面提供了新的选择。为什么现在正当时呢?一个大因素就是那些用来构建语言的新...
  • 1.iina A few of the Features: Based on mpv, which provides the best decoding capacity on macOS Designed for modern macOS (10.10+), aims to offer the best user experience All the features you nee...
  • POST请求post是向服务器提交数据的意思,提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器url中查看到,大小没有限制。 HEAD请求请求头信息,并不返回请求数据体,而只返回请求头信息,常用用于在...
  • ios中常用的第三方库

    2017-03-04 20:36:28
    下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件。SVPullToRefresh – 下拉刷新控件。MJRefresh – 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能。...
  • Swift-MVVM 简单演练(一)

    2019-06-11 16:06:19
    Swift-MVVM 简单演练(二) Swift-MVVM 简单演练(三) Swift-MVVM 简单演练(四) 前言 最近在学习swift和MVVM架构模式,目的只是将自己的学习笔记记录下来,方便自己日后查找,仅此而已!!! 如果有任何问题,欢迎和我...
  • 根据公司需求,这几天一直在研究iOS原生请求JSON的功能需求,当中遇到了不少的坑以及困扰,因此在这里详细的介绍取服务器数据; 实现获取服务器json数据 利用POST提交 利用GET提交 得到JSON数据并进行解析 显示到...
  • App架构设计经验谈

    2018-09-17 10:04:22
    现在,大部分App的接口都采用RESTful架构,RESTFul最重要的一个设计原则就是,客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大部分...
  • iOS的开发中的网络下载方式包括NSData(最原始,实际开发基本不会用),NSURLConnection(古老又过气的苹果原生网络框架),NSURLSession(现在流行的苹果网络框架),AFNetworking,SDWebImage以及基于AFNetworking...
  • WKWebView是苹果在iOS 8中引入的新组件,目的是给出一个新的高性能的WebView解决方案,摆脱过去 UIWebView的老、旧、笨重,特别是内存占用量巨大的问题,它使用Nitro JavaScript引擎,这意味着所有第三方浏览器运行...
  • Swift带来很多确实很棒的特性,使得很难再回到Objective-C。主要的特性是安全性,不过这也被看成是一种额外副作用。 带类型接口的强型别 Swift有强型别,这意味着除非你要求,不然Swift不会为你做类型之间的转换。...
  • 酷课堂iOS交流群问答精华整理 以下内容由@客服妹子爬楼整理,限于篇幅,无法就所有问题做详细说明,感兴趣的小伙伴,可以添加小课QQ:3068527144,申请加入交流小群。希望大家碰到的这些问题,对你有帮助/启发。...
1 2 3 4 5 ... 20
收藏数 493
精华内容 197
关键字:

苹果原生post请求swift