• Demo主要介绍Swift的网络部分,代码已更新到swift4 –网络部分 请求均采用 Alamofire 请求封装方式分为: * 1.Moya(一个star很多的Alamofire的上层封装,为本demo推荐方式。我在使用过程中最终发现moya是极其...

    封装了moya,链式,类AFN式请求

    SwiftHttpRequest Github地址

    简书地址

    Demo说明

    Demo主要介绍Swift的网络部分,代码已更新到swift4

    –网络部分

    请求均采用 Alamofire

    请求封装方式分为:

    • 1.Moya(一个star很多的Alamofire的上层封装,为本demo推荐方式。我在使用过程中最终发现moya是极其优美的网络请求方式)
    • 2.链式请求(如果你刚刚从OC转到swift,可能还不适应moya的方式,那么可以用这种请求方式来过渡)
    • 3.仿AFN式请求(这应该是OC中常见的封装方式,但是真的不再适合swift这个优美的语言啦)

    –Progress及信息处理

    加载动画及弹出框采用 MBProgressHUD

    –数据解析

    在swift4之前,我一直用的是HandyJSON(下面有介绍)。在swift4之后我把model的解析转到到官方的Codable。

    本来这个demo只是我转swift时用来学习网络的,但是发现对一些同学很有帮助,所以重新整理了一下代码,将代码由swift3升级到swift4,并且抛弃了HandyJSON,因为swift语言的特性,要学就学最新的,所以demo中不再提供其他josn解析方式的示例

    –缓存

    缓存部分没有接入数据库,而是直接用了write to file,并将缓存封装到网络请求方法中

    本demo内容可直接用于项目开发,我在项目中大量使用,感觉还不错哈哈哈


    三方库介绍

    Alamofire:Swift中著名的网络请求库

    Moya:著名的Alamofire封装,让网络请求看起来更加的优美,更有利于阅读与迭代

    MBProgressHUD: 进度条,弹出框,OC写的库

    Kingfisher: 加载网络图片,类似SDWebImage

    已弃用

    HandyJSON 是阿里巴巴开源的model的映射库。使用方式类似OC中的MJExtention

    ObjectMapper:json解析库,需要手动写映射关系

    具体可以看在Swift语言中处理JSON - 转换JSON和Model

    接口说明

    本demo使用接口为多米音乐接口
    http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=关键字&pi=页码&pz=每页音乐数
    请求数据参数:kw=像我这样的人&pi=1&pz=1

    返回实例:

    {
    "album_offset": 0,
    "albums": [
    {
    "artists": [
    {
    "id": 61799986,
    "name": "Mc名决",
    "portrait": null,
    "valid": false
    }
    ],
    "available": true,
    "company": "",
    "cover": "http://pic.cdn.duomi.com/imageproxy2/dimgm/scaleImage?url=http://img.kxting.cn//p1/08/16/72494779.jpg&w=150&h=150&s=100&c=0&o=0&m=",
    "id": 2742662,
    "name": "像我这样的人",
    "num_tracks": 4,
    "release_date": "2017-08-22",
    "type": "EP/单曲"
    }
    ],
    "artist_offset": 0,
    "artists": null,
    "dm_error": 0,
    "error_msg": "操作成功",
    "recommend": 0,
    "total_albums": 1,
    "total_artists": 0,
    "total_tracks": 6,
    "track_offset": 0,
    "tracks": [
    {
    "album": {
    "cover": "/p1/12/17/72493295.jpg",
    "id": 2741390,
    "name": "裙娣"
    },
    "artists": [
    {
    "id": 61792091,
    "name": "DJ马哥",
    "num_albums": 35,
    "num_tracks": 233,
    "portrait": "",
    "valid": false
    }
    ],
    "availability": "1110",
    "dlyric": "",
    "id": 28136457,
    "medias": [
    {
    "bitrate": 320,
    "p2purl": "1A4DF5035CE09DB8DF0500000060CFABAC000000A9.mp3"
    }
    ],
    "mv": 0,
    "slyric": "",
    "title": "像我这样的人",
    "isdown": "1",
    "isplay": "1"
    }
    ]
    }
    

    End


    作者语:

    希望能达到抛砖引玉的效果

    也给新学习swift的朋友一个简单的网络处理的方式

    大家互相帮助,互相学习

    如果对你有帮助还请给个Star,谢谢

    版本:

    2.0 更新到swift4,重新整理代码

    1.2 新增moya的demo

    1.1 新增链式请求的封装

    • 链式请求可以只组合需要的函数,本身默认为常用方式,简化常用的链式调用
    • 对于非默认值的请求可以自定义进行设置
    • 方便添加自定义行为,利于扩展

    1.0 类OC中的AFN封装

    • 利于OC转swift的同学学习
    • 其中对返回值做了JSON和String两种解析,String是为了方便实用HandyJSON,不使用HandyJSON可以自行删除部分代码
    展开全文
  • 注:本文是笔者向一位大佬请教后凭自己理解整理的,如有不妥欢迎指正,如有疑问请留言,我会及时回答。 ...),在闭包的回调函数中我们要实现页面跳转,需要写以下代码: // 闭包回调 cell.b...

    注:本文是笔者向一位大佬请教后凭自己理解整理的,如有不妥欢迎指正。

    要说循环引用,先举一个例子,我们有时需要在tableviewCell中添加一个按钮,点击可以跳转到下个页面,和cell本身的点击事件并不相同,这时我们需要用到闭包,在闭包的回调函数中我们要实现页面跳转,需要写以下代码:

    // 闭包回调
    cell.backClosure = {(str:String)-> Void in
        let vc = newViewController()
        self.navigationController?.pushViewController(vc, animated: true)
    }

    这样其实我们就已经进入了一个误区,造成了循环引用,导致内存无法释放。

    cell最后持有了self,而它本身又在被self持有,我拿我自己?显然不合理

    既然知道了问题所在,那么自然就有解决的办法,我们可以按如下修改代码:

    weak var weakSelf = self //避免循环引用
    // 闭包回调
    cell.backClosure = {(str:String)-> Void in
        let vc = newViewController()
        weakSelf?.navigationController?.pushViewController(vc, animated: true)
    }

     这就相当于为self做了一个副本,但self的副本(wakeself)并没有持有和self一样的东西(tableView),我不能拿我自己,但我可以拿我自己的副本。cell用wakeself跳到下个页面,self不再需要做任何事,wakeself结束了它的任务,内存自然也就释放了。

    swift中,有两个很关键的函数,init(在初始化时执行)和deinit(在被释放时执行),所以要想检测到底有没有被释放,我们可以在deinit函数中打印日志来观察。

    deinit {
        print("=== 释放 === \(self.classForCoder) ===")
    }

     

    展开全文
  • 在学习Swift 3的过程中整理了一些笔记,如果想看其他相关文章可前往《Swift 3必看》系列目录 在之前,一个函数的参数的闭包的捕捉策略默认是escaping,如果是一个非逃逸闭包需要显示的添加声明@noescape。感兴趣的...

    在学习Swift 3的过程中整理了一些笔记,如果想看其他相关文章可前往《Swift 3必看》系列目录

    在之前,一个函数的参数的闭包的捕捉策略默认是escaping,如果是一个非逃逸闭包需要显示的添加声明@noescape。感兴趣的可以看我以前写过一篇介绍:Swift中被忽略的@noescape。简单的介绍就是如果这个闭包是在这个函数结束前内被调用,就是非逃逸的即noescape。如果这个闭包是在函数执行完后才被调用,调用的地方超过了这函数的范围,所以叫逃逸闭包。

    举个例子就是我们常用的masonry或者snapkit的添加约束的方法就是非逃逸的。因为这闭包马上就执行了。

      public func snp_makeConstraints(file: String = #file, line: UInt = #line, @noescape closure: (make: ConstraintMaker) -> Void) -> Void {
            ConstraintMaker.makeConstraints(view: self, file: file, line: line, closure: closure)
        }复制代码

    网络请求请求结束后的回调的闭包则是逃逸的,因为发起请求后过了一段时间后这个闭包才执行。比如这个Alamofire里的处理返回json的completionHandler闭包,就是逃逸的。

        public func responseJSON(
            queue queue: dispatch_queue_t? = nil,
            options: NSJSONReadingOptions = .AllowFragments,
            completionHandler: Response<AnyObject, NSError> -> Void)
            -> Self
        {
            return response(
                queue: queue,
                responseSerializer: Request.JSONResponseSerializer(options: options),
                completionHandler: completionHandler
            )
        }复制代码

    就像我之前写的那篇标题,很多人在写闭包参数的时候总是忽略去判断这个闭包是否是逃逸的。这对闭包的内存管理优化不太友好,都被当做了逃逸闭包处理。所以在3中做出了一个对调的改变:所有的闭包都默认为非逃逸闭包,不再需要@noescape;如果是逃逸闭包,就用@escaping表示。比如下面的一段代码,callBack在函数执行完后1秒才执行,所以是逃逸闭包。

    func startRequest(callBack: ()->Void ) {
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 1) { 
            callBack()
        }
    }复制代码

    这样就需要显示的声明@escaping才能编译通过。

    相关链接:
    SE-0103:Make non-escaping closures the default

    展开全文
  • 我这里用的是WKWebView,首先要实现WKNavigationDelegate协议 1、由H5页跳转微信客户端--WKNavigationDelegate方法 该方法是决定H5页面是否允许跳转的 -(void)webView:(WKWebView*)webView decidePolicyForNav.....

    转载自:https://www.jianshu.com/p/30ca8b2c1235

    我这里用的是WKWebView,首先要实现WKNavigationDelegate协议

    1、由H5页跳转微信客户端--WKNavigationDelegate方法

    该方法是决定H5页面是否允许跳转的

    -(void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler{

        WKNavigationActionPolicy  actionPolicy = WKNavigationActionPolicyAllow;//允许

        NSString*urlString = [[navigationAction.request URL] absoluteString];

        urlString = [urlString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

        NSLog(@"跳转:>>>???>>%@",urlString);

        //打开微信

        if([urlString containsString:@"weixin://wap/pay?"]) {

            actionPolicy =WKNavigationActionPolicyCancel;//不允许跳转

            NSURL*url = [NSURL URLWithString:urlString];

            if([[UIApplication sharedApplication] respondsToSelector:@selector(openURL:options:completionHandler:)]) {

                [[UIApplication sharedApplication] openURL:url options:@{UIApplicationOpenURLOptionUniversalLinksOnly: @NO} completionHandler:^(BOOL success) {

                }];

            }else{

                [[UIApplication sharedApplication]openURL:webView.URL];

            }

        }

        decisionHandler(actionPolicy); //这句是必须加上的,不然会异常

    }

     

    2、从微信客户端跳回APP

    很多小伙伴儿都遇到回跳的问题,点击“完成”或“取消”跳到了Safari却跳不回APP。

    1、配置 URL Types   添加schemes:www.xxxx.com

     

    2、配置 Referer

    [request setValue:@"www.xxxx.com://" forHTTPHeaderField:@"Referer"];

    3、服务器回调的URL中一定要写成  www.xxxx.com:// , 如果一定要拼接,可以让后台区分一下,如果是iOS 前面一定要写成 www.xxxx.com:// ,不然跳不回APP。

    注意:www.xxxx.com 此域名一定是H5授权的域名,如果是二级域名,可以写成:aaa.xxxx.com

    3、支付回来页面不刷新或产生空白页

    一般支付完不管完成与否都要调一下回调地址来刷新支付页面的,但问题是iOS的回调地址中一定要写成 www.xxxx.com:// 这样的格式才能跳回来,但是调回来后没有执行回调地址,页面又刷新不了,或者跳回来后是个空白页,这又产生了一个新的问题。当点击微信支付页上的“完成”或“取消”返回APP时,会调用 AppDelegate 中的 openURL: 方法

    -(BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary *)options{ 

        // url.scheme 就是 www.xxxx.com ,你只需要在这做一个判断,如果是 www.xxxx.com 你就给外部发一个通知,那外部接收到这个通知,刷新界面就行了。

    那么问题又来了,刷新方法是什么呢??

    刷新地址也就是支付完的回调地址,后台可以把回调地址拼接在 www.xxxx.com:// 后面,例如:www.xxxx.com://http://www.baidu.com 。 那么 url 就是 www.xxxx.com:// http://www.baidu.com ,你向外部发通知时把 url 作为参数传出去即可 

    if ([url.scheme containsString:@"www.xxxx.com"]) {                                                                                          

    [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshWXH5Pay" object:nil userInfo:@{@"url":url}];                  

     }                                                                                                                                                                                                                      

    }

    //这是外部接收、处理通知的方法                                                                                                                                                                -(void)refreshOrderData:(NSNotification*)notifi{                                                                                                                   

    NSDictionary*dic = notifi.userInfo;                                                                                                                                             

    NSString*url = [NSString stringWithFormat:@"%@",dic[@"url"]];                                                                                          

    NSString*urlStr = [url substringFromIndex:14];  //截取 www.xxxx.com:// http://www.baidu.com    后面的部分                                                                                                    

    [self.webView loadRequest:[self getRequest:urlStraction:nil]];

    }



    作者:861488970
    链接:https://www.jianshu.com/p/30ca8b2c1235
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

     

    展开全文
  • 在最近的一个项目中再一次用到了第方支付,对,就是支付宝,之前的项目其实已经实现过相应的功能,那是还是在ios8的系统下,这不在iOS9下就遇到了一个问题,不回调啊,反正要梳理支付宝的嵌入,那就先说说具体的...

    又有一段时间没有经营自己的博客了,这一段有点忙啊!

    在最近的一个项目中再一次用到了第三方支付,对,就是支付宝,之前的项目其实已经实现过相应的功能,那是还是在ios8的系统下,这不在iOS9下就遇到了一个问题,不回调啊,反正要梳理支付宝的嵌入,那就先说说具体的流程吧。

    我使用的支付宝SDK2.0标准版,下载相应的SDK时,除了会附带支付宝的Demo之外,还有一份文档“移动支付接口SDK2.0标准版接入和使用规则.pdf”,我们大概分3步来总结一下:

    第一步:商户签约和秘钥配置;

    第二步:SDK集成及xcode相关配置;

    第三部:参数设置及相关调试;

    接下来我们一步一步来说:

    第一步:商户签约和秘要配置;

    对于公司的业务一般是由公司申请企业支付宝账号,同时进行签约和相应的业务申请,这是公司该做的,作为开发者给我们的就是一组支付宝账号和密码,我们登录进去可以查看签约管理,示意图如下(注意一下我们进入之后,查看的是支付宝-商家服务,而不是支付宝-开放平台,商家服务可以理解为你为公司的支付宝签约的服务配置信息,而开放平台是给自己作为开发者用的,在开放平台中只能看到在商户服务中配置的信息)

    点击查看 商户服务->签约管理->查看更多签约信息,之后则需要输入支付宝支付密码才能看见PID和Key等内容(支付宝付费账号是单独设置的,和登录密码不一定相同,试了3遍会被锁定的,最好事先确定一下),之后我们会看到这样一个界面:


    这个界面就是我们获取PID和设置秘钥的地方;

    接下来就是这个秘钥怎么配的问题了,如上图中所标注的,我们需要添加RSA加密的公钥,作为支付宝的公钥;

    在我们下载的SDKDemo中有一个openssl的文件夹,其中包含了windows下的生成秘钥的工具,我是在Mac电脑上操作的,直接在终端上敲命令就行,在根目录下就会生成一组.pem格式的秘钥,一个是私钥,一个是公钥,再用命令生成PKCS8格式的私钥,会直接显示在终端上,保存到一个记事本里保存好,那现在我们手里就有了3个秘钥:

    rsa_private_key.pem;

    rsa_public_key.pem;

    PKCS8编码之后的私钥(字符串);

    其中,PKCS8编码之后的私钥会在app中作为参数privateKey传入,RSA加密的公钥就是前面我们说的要添加作为支付宝公钥的;

    好了,第一步就完成了;


    第二步:SDK集成及xcode相关配置:

    对于继承的流程,文档上有很详尽的描述,一般我的做法就是把Demo中给的这几个文件拖进应用:

    这个order类也是从Demo中拷出来的;

    在导入SDK之后,还需要在Header Search Paths增加头文件路径,引入支持的系统库,Info-UIL Types中增加URL Schemes,同时还要进行ios9的适配,在Info plist文件中增加LSApplicationQueriesSchemes数组属性,增加两个值:alipay和alipayshare...反正就是正常做就行了;

    这里完成了上述的步骤之后,run一下,发现竟然跑不了,报错了(是不是很郁闷!)

    Undefined symbols for architecture armv7:
      "_OBJC_CLASS_$_UTDevice", referenced from:
          objc-class-ref in AlipaySDK
    ld: symbol(s) not found for architecture armv7
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    具体解决过程就不提了,在链接的系统库中增加CoreMotion.framework就好了。

    这下可以编译通过了,第二步也完成了;


    第三部:参数设置及相关调试:

    按照Demo中的请求调用支付宝支付,我们需要若干参数:

    partner:合作身份者ID,以2088开头,也就是前面我们提到的PID;

    seller:商家的支付宝收款账号;

    privateKey:还记得那个PKCS8格式的秘钥吗,对,就是他;

    order.tradeNO:这个订单内容编码是最终要传给服务端的,其中根据不同的业务需求可能会传订单Id,用户Id,优惠券Id等信息,支付宝回调服务端之后,进行入库;

    order.productName:可以作为产品业务名;

    order.productionDescription:可以作为产品业务描述;

    order.amount:这是最重要的“钱”,千万别算错了,会出大问题的,别问我是怎么知道的^_^;

    order.notifyURL:这个是支付宝回调服务端的地址,找服务端要就行了;

    appScheme:前面配置的URL Schemes中的字符串,写在这就行了;

    这样参数就基本齐全了,很开心对吧,接下来就要处理回调了,需要在两个地方处理回调:

    一个是进行支付时的callback的block回调,这个是调用SDK时的回调(没安装应用的);

    另一个是appdelegate中的这个方法中:

    - (BOOL)application:(UIApplication *)application
                openURL:(NSURL *)url
      sourceApplication:(NSString *)sourceApplication
             annotation:(id)annotation
    添加如下代码:

    /**
             9000 订单支付成功 8000 正在处理中 4000 订单支付失败 6001 用户中途取消 6002 网络连接出错
             */
            //*支付宝
            //如果极简 SDK 不可用,会跳转支付宝钱包进行支付,需要将支付宝钱包的支付结果回传给 SDK
            if ([url.host isEqualToString:@"safepay"]) {
                
                [[AlipaySDK defaultService]processAuth_V2Result:url standbyCallback:^(NSDictionary *resultDic) {
                    [[NSNotificationCenter defaultCenter]postNotificationName:k_Noti_transeAlipayCallBackResault object:resultDic];
                }];
                [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
                    NSLog(@"result = %@",resultDic);
                    [[NSNotificationCenter defaultCenter]postNotificationName:k_Noti_transeAlipayCallBackResault object:resultDic];
                }];
            }
            if ([url.host isEqualToString:@"platformapi"]){//支付宝钱包快登授权返回 authCode
                [[AlipaySDK defaultService] processAuthResult:url standbyCallback:^(NSDictionary *resultDic) {
                    NSLog(@"result = %@",resultDic);
                    [[NSNotificationCenter defaultCenter]postNotificationName:k_Noti_transeAlipayCallBackResault object:resultDic];
                }];
            }
    
    这个是跳转到支付宝应用返回当前应用的回调处理,三个判断对应三种不同情况:其实正常的情况下只有第二个会走,第三个从url.host就可以看出来,是处理登录授权的(其他人都有这个判断,姑且放着吧),至于第一个判断我记得是如果应用被干掉,也会有回调,但是鉴于我对回调的处理方式是通过通知发回发起支付的界面,所以这个判断实际上也没什么意义;

    在具体界面处理具体通知,根据不同的状态码处理就行了;

    第三步也完成了,按照之前排练的进行付费,之后就等着能华丽丽的收场,结果...你错了,竟然不回调,对,就是不回调,瞬间好心情全没了^_^;

    终端上只是孤零零的输出了“retryHandleOpenURL”

    3步都完成了,就差回调的处理了,各种搜索,最后在简书上有个人处理了这个问题(毕竟人家是作者,把链接附在这了http://www.jianshu.com/p/3ce4561ae7be

    解决的方法就是在iOS9下,这个方法才是可靠的

    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options 
    把上边的处理代码考到这个方法中一份就可以了^_^


    感谢俊哥的帮助,使这篇博客成为了可能,谢谢!也希望对大家有帮助。



    展开全文
  • import UIKit extension UIButton { /// 快速创建按钮(全) /// /// - Parameters: /// - imageName: 图片 /// - titleColor: 字体颜色 /// - titleFont: 字体大小 /// - backgroundColor: ...
  • 说明 首先声明,今日头条是我经常...项目中有的地方代码写的不是很简洁,毕竟自己能力有限,对 Swift 使用不是很熟练,还请各位朋友不喜勿喷。下面有项目的完整源码,喜欢的朋友可以下载下来,如果您感觉我写的代码对
  • 一个特别好用的照片选择器,和系统Photos框架的一些知识
  • 但并不是每次都想使用这些第方的服务的, 这里作者整理了微信, QQ, 新浪微博原生第方的接入:[Swift]原生第方接入: 微信篇--集成/登录/分享/支付[Swift]原生第方接入: QQ篇--集成/登录/分享[Swift]原生...
  • 在很多App中,经常存在一需求就是,界面上下滚动时用户的头像也会跟着滚动,而用户头像在视图向上滚动一定范围时停留并在导航栏的位置,这里我实现了一个视图,基本样式如下:headerZoom.gif基本用法如下:1、单纯...
  • Swift3之闭包

    2017-06-16 12:10:20
    闭包有三种形式: 全局函数 嵌套函数 闭包表达式 有名字但不能捕获任何值。 有名字,也能捕获封闭函数内的值。 无名闭包,使用轻量级语法,可以根据上下文环境捕获值。 捕获值 闭包可以在其定义的上下文中捕获...
  • 在日常开发过程中我们为了使这个项目,页面精简 美观易懂,往往采用组件式开发,会对一个复杂的完整页面分割成一个个UI view ,UItableviewcell 等控件类,或者我们会在某些网络请求回调 和异步操作跳转页面。...
  • ios swift开发中有几方式传值,看到简书上一篇不错的文章。 链接:http://www.jianshu.com/p/3e1173652996一.通过segue进行传值二.通过delegate进行传值.通过Notification进行传值四.通过回调函数进行传值需求...
  • Swift 3.0 集成极光推送

    2017-05-24 13:37:56
    1.前言推送证书配置什么的都不多讲了,极光推送的开发文档里都有详细的介绍极光推送文档,因为官方的文档是OC版本的,我这里主要是讲解一下怎么用Swift进行集成。 本篇文章也可移步简书阅览,效果更好哦!2.配置现在...
  • 在拥有匿名函数、闭包这些特性的编程语言中,我们通常可以使用回调函数来做一个异步任务完成或失败时的处理。但当我们的业务逻辑逐渐复杂时,就会产生回调嵌套,整个事件流将十分混乱。相信大家对 Node.js 的回调...
  • 前言我们在开发的过程中,大家应该都会遇到已进入某个页面,就要...实战我所有的网络请求是基于AFNetworking的封装,然后我给所以的网络请求方法加了一个请求结束的回调,我以头部获取广告数组为例,代码如下:#pragma
  • 这个栏目将持续更新–请iOS的小伙伴关注! 1、多线程的应用 2、GCD实现多个请求都完成之后返回结果 3、A、B两个int数组,得到A数组中B数组不包含的元素 4、事件传递链,页面上一个按钮,按钮和它的superView有...
  • Swift 各种闭包各种使用 && 设置参数,函数传值
  • 更新:所有代码已经更新到Swift4.1,请移步github下载 ======================================================= iOS开发已经做了快4年了,听说Swift也已经有两年多,但是一直都只是把学习停留在表面。无意中听说了...
1 2 3 4 5 ... 20
收藏数 569
精华内容 227