zoukankan      html  css  js  c++  java
  • 从0开始学Swift笔记整理(五)

    这是跟在上一篇博文后续内容:

    ——Core Foundation框架

    Core Foundation框架是苹果公司提供一套概念来源于Foundation框架,编程接口面向C语言风格的API。虽然在Swift中调用这种C语言风格的API比较麻烦,但是在OS X和iOS开发过程中,有时候使用Core Foundation框架的API是非常方便的,例如在与C语言混合编码的时候。
    Core Foundation框架与Foundation框架紧密相关,他们具有与相同的接口,但是不同。Core Foundation框架是基于C语言风格的,而Foundation框架是基于Objective-C语言风格的。在OS X和iOS程序代码中经常会有多种语言风格的代码混合在一起的情况,这使得我们开发变得更加麻烦。
    数据类型映射
    Core Foundation框架提供了一些不透明的数据类型,这些数据类型封装了一些数据和操作,他们也可以称为“类”,他们都继承于CFType类,CFType是所用Core Foundation框架类型的根类。这些数据类型在Foundation框架中都有相应的数据类型与之对应,这些数据类型也有一些与Swift原生数据类型有对应关系。

    看看Swift原生类型与Core Foundation类型之间的转换示例:

    import CoreFoundation
    import Foundation
    var cfstr1: CFString = "Hello,World"    //创建CFString字符串
    var str: String = cfstr1 as String     //将CFString字符串转换为Swift原生字符串Stringvar cfstr2: CFString = str            //将Swift原生字符串String转换为CFString字符串

    这个转换过程中Core Foundation类型转换为Swift原生类型是需要强制类型转换的。

    ——Core Foundation框架之内存管理

    在Swift原生数据类型、Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,虽然是大部分是可以零开销桥接,零开销并不意味着内存什么都不用管。Swift类型内存管理是采用ARC,Foundation类型和Core Foundation类型内存管理都是采用MRC或ARC,Core Foundation类型内存管理是基于C语言风格的,它有一个对象所有权的概念。
    Objective-C的MRC内存管理
    Core Foundation的内存管理与Objective-C的MRC内存管理密不可分,先介绍一下Objective-C的MRC内存管理。
    所有Objective-C类都继承NSObject类,每个NSObject对象都有一个内部计数器,这个计数器跟踪对象的引用次数,被称为“引用计数”(Reference Count,简称RC)。当对象被创建时候,引用计数为1。为了保证对象的存在,可以调用retain方法保持对象,retain方法会使其引用计数加1,如果不需要这个对象可以调用release或autorelease方法,release或autorelease方法使其引用计数减1。当对象的引用计数为0的时候,系统运行环境才会释放对象内存。
    引用计数示例如图所示,首先在第①步调用者A中创建了一个NSObject对象,这时该对象引用计数为1。在第②步调用者B中想使用这个NSObject对象,于是使用NSObject对象引用,但是为了防止使用过程中NSObject对象被释放,可以调用retain方法使引用计数加1,这时引用计数为2。在第③步调用者A中调用release或autorelease方法,使引用计数减1,这时引用计数为1。在第④步调用者C中调用release或autorelease方法,只是获得NSObject对象引用,并没有调用retain、release或autorelease方法,因此没有引起引用计数的变化。在第⑤步调用者B中调用release或autorelease方法使引用计数减1,这时引用计数为0。这个时候NSObject对象就内存就可以释放了。

    来总结一下:

    1. 谁创建或拷贝对象,他也一定要负责调用NSObject对象release或autorelease方法,使引用计数减1,如图中调用者A在第①步,负责创建了NSObject对象,那么调用者A也必须是负责使引用计数减1,见第④步。
    2. 谁调用retain方法使引用计数加1,它也一定要负责调用NSObject对象release或autorelease方法,使引用计数减1,如图中调用者B在第②步,调用者B调用NSObject对象retain方法使引用计数加1,那么调用者B也必须是负责使引用计数减1,见第⑤步。
      对象所有权
      一个对象可以有一个或多个所有者,从所有者的角度看是对这个对象具有了“所有权”,从上图中看,调用者A和调用者B是所有者,他们可能是一段程序,可能是一个对象。他们对NSObject对象具有所有权,不再使用时候他们应该负责放弃对象所有权,当对象没有所有者时,引用计数为0,它才可以被释放。
      如上图如果按照对象所有权解释:调用者A创建或拷贝NSObject对象,这时调用者A就具有了NSObject对象的所有权,见第①步。调用者B调用NSObject对象retain方法,就获得了也NSObject对象的所有权,见第②步。调用者A调用NSObject对象release方法,放弃NSObject对象的所有权,见第③步。调用者C只是使用NSObject对象没有获得NSObject对象的所有权,见第④步。调用者B调用NSObject对象release方法,放弃NSObject对象的所有权,见第⑤步,但是调用者B使用这个NSObject对象过程中,由于其他调用者放弃所有权,导致NSObject对象被释放,那么调用者B中程序就会发生运行期错误。

    ——Core Foundation框架之内存托管对象与非托管对象

    Swift中调用Core Foundation函数获得对象时候,对象分为:内存托管对象和内存非托管对象。

    ·内存托管对象
    内存托管对象就是由编译器帮助管理内存,我们不需要调用CFRetain函数获得对象所有权,也不需要调用CFRelease函数放弃对象所有权。
    获得这些内存托管对象的方法,是采用了CF_RETURNS_RETAINED或CF_RETURNS_NOT_RETAINED注释声明

    func CFStringCreateWithCString(_ alloc: CFAllocator!, 
    _ cStr: UnsafePointer<Int8>,
    _ encoding: CFStringEncoding) -> CFString!    //内存托管对象
    func CFHostCreateCopy(_ alloc: CFAllocator?,
    _ host: CFHost) -> Unmanaged<CFHost>        //内存非托管对象

    ·内存非托管对象
    内存非托管对象就是内存需要程序员自己管理。这是由于在获得对象的方法中没有使用CF_RETURNS_RETAINED或CF_RETURNS_NOT_RETAINED注释声明,编译器无法帮助管理内存。在具体使用时候我们可以上一节的方法判断是否为非内存托管对象。
    内存非托管对象使用起来有些麻烦,要根据获得所有权方法,进行相应的处理。

    1. 如果一个函数名中包含Create或Copy,则调用者获得这个对象的同时也获得对象所有权,返回值Unmanaged<T>需要调用takeRetainedValue()方法获得对象。调用者不再使用对象时候,Swift代码中需要调用CFRelease函数放弃对象所有权,这是因为Swift是ARC内存管理的。
    2. 如果一个函数名中包含Get,则调用者获得这个对象的同时不会获得对象所有权,返回值Unmanaged<T>需要调用takeUnretainedValue()方法获得对象。

    ——Cocoa Touch设计模式及应用之单例模式

    什么是设计模式。设计模式是在特定场景下对特定问题的解决方案,这些解决方案是经过反复论证和测试总结出来的。实际上,除了软件设计,设计模式也被广泛应用于其他领域,比如UI设计和建筑设计等。
    下面来介绍Cocoa Touch框架中的设计模式中的单例模式。
    单例模式
    单例模式的作用是解决“应用中只有一个实例”的一类问题。在Cocoa Touch框架中,有UIApplication、NSUserDefaults和NSNotificationCenter等单例类。另外,NSFileManager和NSBundle类虽然属于Cocoa框架的内容,但也可以在Cocoa Touch框架中使用(Cocoa框架中的单例类有NSFileManager、NSWorkspace和NSApplication等)。
    问题提出
    在一个应用程序的生命周期中,有时候只需要某个类的一个实例。例如:当iOS应用程序启动时,应用的状态由UIApplication类的一个实例维护,这个实例代表了整个“应用程序对象”,它只能是一个实例,其作用是共享应用程序中的一些资源、控制应用程序的访问,以及保持应用程序的状态等。
    解决方案
    单例模式的实现有很多方案,苹果公司在《Using Swift with Cocoa and Objective-C》官方文档中给出了一种单例模式的实现。最简单形式代码如下:

    class Singleton {

        static let sharedInstance = Singleton()

    }

    上述代码采用static的类属性实现单例模式,这种类属性只被延迟加载执行一次,即便是在多线程情况下也只是执行一次,并且保证是线程安全的。
    如果需要进行一些初始化,可以使用如下带有闭包形式代码:

    class Singleton {

        static let sharedInstance: Singleton = {

            let instance = Singleton()

            // 初始化处理

            return instance

        }()

    }

    ——Cocoa Touch设计模式及应用之目标与动作

    目标(Target)与动作(Action)是iOS和OS X应用开发的中事件处理机制。

    要实现目标与动作的连接有两种方式:Interface Builder连线实现和编程实现。

    1. Interface Builder连线实现
      Interface Builder连线实现就是故事板或Xib文件中,通过连线而现实。
    2. 编程实现
      编程实现是通过UIControl类addTarget(_:action:forControlEvents:)方法实现的,主要代码如下:

    class ViewController: UIViewController {

     

        override func viewDidLoad() {

        super.viewDidLoad()

     

        self.view.backgroundColor = UIColor.whiteColor()

     

        let screen = UIScreen.mainScreen().bounds;

        let labelWidth:CGFloat = 90

        let labelHeight:CGFloat = 20

        let labelTopView:CGFloat = 150

        let label = UILabel(frame: CGRectMake((screen.size.width

                 -labelWidth)/2 , labelTopView, labelWidth, labelHeight))

     

        label.text = "Label"

        //字体左右剧中

        label.textAlignment = .Center

        self.view.addSubview(label)

     

        let button = UIButton(type: UIButtonType.System)// 创建UIButton对象

        button.setTitle("OK", forState: UIControlState.Normal)   

     

        let buttonWidth:CGFloat = 60

        let buttonHeight:CGFloat = 20

        let buttonTopView:CGFloat = 240

     

        button.frame = CGRectMake((screen.size.width

             - buttonWidth)/2 , buttonTopView, buttonWidth, buttonHeight)

     

        button.addTarget(self, action: "onClick:",

                         forControlEvents: UIControlEvents.TouchUpInside)

     

        self.view.addSubview(button)   

            }

     

        func onClick(sender: AnyObject) {   

            NSLog("OK Button onClick.")

        }   

            ...

    }

    上述代码中创建并设置UIButton对象,其中创建UIButton对象,参数type是设置按钮的样式,UIButton样式:

    • Custom。自定义类型。如果不喜欢圆角按钮,可以使用该类型。
    • System。系统默认属性,表示该按钮没有边框,在iOS 7之前按钮默认为圆角矩形。
    • Detail Disclosure。细节展示按钮,主要用于表视图中的细节展示。
    • Info Light和Info Dark。这两个是信息按钮,样式上与细节展示按钮一样,表示有一些信息需要展示,或有可以设置的内容。
    • Add Contact。添加联系人按钮
    • 代码调用addTarget(_:action:forControlEvents:)方法,方法第一个参数是target,即事件处理对象,本例中是self;方法第二个参数是action,即事件处理对象中的方法,
      代码中是"onClick:",方法第三个参数是事件,TouchUpInside事件是按钮的触摸点击事件。
      如果调用如下无参数方法:

    func onClick() {
    }

    调用代码如下:

    button.addTarget(self, action: "onClick",

                    Ê    forControlEvents: UIControlEvents.TouchUpInside)

    区别在于action参数"onClick"方法名不同,action参数方法名的冒号暗示了方法名应该具有几个参数。如果要调用的方法是如下3个参数形式:

    func onClick(sender: AnyObject, forEvent event: UIEvent) {

    }

    那么调用代码如下:

    button.addTarget(self, action: "onClick:forEvent:",

                    Ê    forControlEvents: UIControlEvents.TouchUpInside)

    其中"onClick:forEvent:"是调用方法名,onClick表示方法名也是,forEvent表示第二个参数的外部参数名。

    ——Cocoa Touch设计模式及应用之选择器

    实现目标与动作关联使用UIControl类addTarget(_:action:forControlEvents:)方法,示例代码如下:

    button.addTarget(self, action: "onClick:",

                 forControlEvents: UIControlEvents.TouchUpInside)

    其中的action参数"onClick:"事实上就是选择器(Selector)。

    通过选择器调用方法,关键是方法名字,它有一定规律的。穷其根本是源自于Objective-C多重参数方法命名规律。方法名的冒号暗示了方法名应该具有几个参数,下面我们看几个示例:

        //选择器为"onClick:"

        func onClick(sender: AnyObject) {

             NSLog("onClick:")

        }

     

        //选择器为"onClick:forEvent:"

        func onClick(sender: AnyObject, forEvent event: UIEvent) {   

              NSLog("onClick:forEvent:")

        }

     

        //选择器为"onClickWithExtSender:forEvent:"

        func onClick(extSender sender: AnyObject, forEvent event: UIEvent) {

              NSLog("onClickWithExtSender:forEvent:")

        }

    出于数据封装的需要,我们会在方法前面加private,使其变为私有方法,代码如下。

        private func onClick(sender: AnyObject) {

              NSLog("onClick:")

        }

    但是这样方法在调用时候会出现如下错误:
    unrecognized selector sent to instance 0x7f7f81499b10'

    这个错误的意思是没有找到选择器所指定的方法,也就是没有找到onClick:方法。正确的做法是在方法前面添加@objc属性注释,这说明选择器是在objc runtime运行环境下调用的。

        //选择器为"onClick:"

        @objc private func onClick(sender: AnyObject) {

               NSLog("onClick:")

        }

    ——Cocoa Touch设计模式及应用之通知机制

    通知(Notification)机制是基于观察者(Observer)模式也叫发布/订阅(Publish/Subscribe)模式,是 MVC( 模型-视图-控制器)模式的重要组成部分。

    在软件系统中,一个对象状态改变也会连带影响其他很多对象的状态发生改变。能够实现这一需求的设计方案有很多,但能够做到复用性强且对象之间匿名通信的,观察者模式是其中最为适合的一个。

    通知机制可以实现“一对多”的对象之间的通信。如图所示,在通知机制中对某个通知感兴趣的所有对象都可以成为接收者。首先,这些对象需要向通知中心(NSNotificationCenter)发出addObserver消息进行注册通知,在投送对象通过postNotificationName消息投送通知给通知中心,通知中心就会把通知广播给注册过的接收者。所有的接收者都不知道通知是谁投送的,更不关心它的细节。投送对象与接收者是一对多的关系。接收者如果对通知不再关注,会给通知中心发出removeObserver消息注销通知,以后不再接收通知。

    ——Cocoa Touch设计模式及应用之MVC模式

    MVC(Model-View-Controller,模型-视图-控制器)模式是相当古老的设计模式之一,它最早出现在Smalltalk语言中。现在,很多计算机语言和架构都采用了MVC模式。

    MVC模式概述
    MVC模式是一种复合设计模式,由 “观察者”(Observer)模式、“策略”(Strategy)模式和“合成”(Composite)模式等组成。MVC模式由3个部分组成,如图所示,这3个部分的作用如下所示。
    · 模型。保存应用数据的状态,回应视图对状态的查询,处理应用业务逻辑,完成应用的功能,将状态的变化通知视图。
    · 视图。为用户展示信息并提供接口。用户通过视图向控制器发出动作请求,然后再向模型发出查询状态的申请,而模型状态的变化会通知给视图。
    · 控制器。接收用户请求,根据请求更新模型。另外,控制器还会更新所选择的视图作为对用户请求的回应。控制器是视图和模型的媒介,可以降低视图与模型的耦合度,使视图和模型的权责更加清晰,从而提高开发效率。

     

    对应于哲学中的“内容”与“形式”, 在MVC模型中,模式是“内容”,它存储了视图所需要的数据,视图是“形式”,是外部表现方式,而控制器是它们的媒介。
    Cocoa Touch中的MVC模式
    上面我们讨论的是通用的MVC模式,而Cocoa和Cocoa Touch框架中的MVC模式与传统的MVC模式略有不同,前者的模型与视图不能进行任何通信,所有的通信都是通过控制器完成的,如图所示。

     

    在Cocoa Touch框架的UIKit框架中,UIViewController是所有控制器的根类,如UITableViewController、UITabBarController和UINavigationController。UIView是视图和控件的根类。

    ——Cocoa Touch设计模式及应用之响应者链与触摸事件

    应用与用户进行交互,依赖于各种各样的事件。事件响应者对象是可以响应事件并对其进行处理的对象,响应者链是由一系列链接在一起的响应者组成的。响应者链在事件处理中是非常重要的,响应者链可以把用
    户事件路由给正确的对象。

    响应者对象与响应链
    UIResponder是所有响应者对象的基类,它不仅为事件处理,而且也为常见的响应者行为定义编程接口。UIApplication、UIView(及其子类,包括UIWindow)和UIViewController(及其子类)都直接或间接地继承自UIResponder类。

     

    第一响应者是应用程序中当前负责接收触摸事件的响应者对象(通常是一个UIView对象)。UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。
    响应者链是一系列链接在一起的响应者对象,它允许响应者对象将处理事件的责任传递给其他更高级别的对象。随着应用程序寻找能够处理事件的对象,事件就在响应者链中向上传递。响应者链由一系列“下一个响应者”组成。
    1.第一响应者将事件传递给它的视图控制器(如果有的话),然后是它的父视图。
    2.类似地,视图层次中的每个后续视图都首先传递给它的视图控制器(如果有的话),然后是它的父视图。
    3.最上层的容器视图将事件传递给UIWindow对象。
    4.UIWindow对象将事件传递给UIApplication单例对象。

    触摸事件
    触摸(UITouch)对象表示屏幕上的一个触摸事件,访问触摸是通过UIEvent对象传递给事件响应者对象的。触摸对象有时间和空间两方面。
    1.时间方面
    时间方面信息称为阶段(phase),表示触摸是否刚刚开始、是否正在移动或处于静止状态,以及何时结束,也就是手指何时从屏幕抬起。
    在给定的触摸阶段中,如果发生新的触摸动作或已有的触摸动作发生变化,则应用程序就会发送这些消息。

    • 当一个或多个手指触碰屏幕时,发送touchesBegan:withEvent:消息。
    • 当一个或多个手指在屏幕上移动时,发送touchesMoved:withEvent:消息。
    • 当一个或多个手指离开屏幕时,发送touchesEnded:withEvent:消息。

    2.空间方面
    触摸点对象还包括当前在视图或窗口中的位置信息,以及之前的位置信息(如果有的话)。下面的方法是可以获得触摸点所在窗口或视图中的位置。

        func locationInView(_ view: UIView?) -> CGPoint

    获得前一个触摸点所在窗口或视图中的位置信息:

    func previousLocationInView(_ view: UIView?) -> CGPoint

    ——Swift与Objective-C混合编程之语言

    在Swift语言出现之前,开发iOS或OS X应用主要使用Objective-C语言,此外还可以使用C和C++语言,但是UI部分只能使用Objective-C语言。

    选择语言
    Swift语言出现后,苹果公司给程序员提供了更多的选择,让这两种语言并存。既然是并存,我们就有4种方式可以选择:

    • 采用纯Swift的改革派方式;
    • 采用纯Objective-C的保守派方式;
    • 采用Swift调用Objective-C的左倾改良派方式;
    • 采用Objective-C调用Swift的右倾改良派方式。

    文件扩展名

    在Xcode等工具开发iOS或OS X应用可以编写多种形式的源文件,原本就可以使用Objective-C、C和C++语言,Swift语言出现后源文件的形式更加多样。可能的文件扩展名说明:

     

    ——Swift与Objective-C混合编程之Swift与Objective-C API映射

    Swift与Objective-C API映射

    在混合编程过程中Swift与Objective-C调用是双向的,由于不同语言对于相同API的表述是不同的,他们之间是有某种映射规律的,这种API映射规律主要体现在构造函数和方法两个方面。

    1、构造函数映射
    在Swift与Objective-C语言进行混合编程时,首先涉及到调用构造函数实例化对象问题,不同语言下构造函数表述形式不同,如图是苹果公司官方API文档,描述了NSString类的一个构造函数。

     

    Swift构造函数除了第一个参数外,其它参数的外部名就是选择器对应部分名。规律的其它细节图中已经解释的很清楚了,这个规律反之亦然,这里不再赘述。
    2、方法名映射

    在Swift与Objective-C语言进行混合编程时,不同语言下方法名表述形式也是不同的,如图是苹果公司官方API文档,描述了NSString类的rangeOfString:options:range:方法。

    选择器第一个部分rangeOfString作为方法名,一般情况下Swift方法第一个参数的外部参数名是要省略的,“_”符号表示省略。之后的选择器各部分名(如:options和range),作为外部参数名。除了参数名对应为,参数类型也要对应下来。

    Swift 2.0之后方法可以声明抛出错误,这些能抛出错误的方法,不同语言下方法名表述形式如图下图所示,是writeToFile:atomically:encoding:error:苹果公司官方API文档。

    比较两种不同语言,我们会发现error参数在Swift语言中不再使用,而是在方法后添加了throws关键字。
    这种映射规律不仅仅只适用于苹果公司官方提供的Objective-C类,也适用于自己编写的Objective-C类。

    ——Swift与C/C++混合编程之数据类型映射

    如果引入必要的头文件,在Objective-C语言中可以使用C数据类型。而在Swift语言中是不能直接使用C数据类型,苹果公司为Swift语言提供与C语言相对应数据类型。这些类型主要包括:C语言基本数据类型和指针类型。

    如表所述是Swift数据类型与C语言基本数据类型对应关系表。

    Swift语言中的这些数据类型与Swift原生的数据类型一样,本质上都是结构体类型。

    如表所述是Swift数据类型与C语言指针数据类型对应关系表。

    从表可见针对C语言多样的指针形式,Swift主要通过提供了三种不安全的泛型指针类型:UnsafePointer<T>、UnsafeMutablePointer<T>和AutoreleasingUnsafeMutablePointer<T>。T是泛型占位符,表示不同的数据类型。另外,还有COpaquePointer类型是Swift中无法表示的C指针类型。
    下面我们分别介绍一下。

      1. UnsafePointer<T>
        UnsafePointer<T>是一个比较常用的常量指针类型,这种指针对象需要程序员自己手动管理内存,即需要自己申请和释放内存。它一般是由其他的指针创建。它的主要的构造函数有:
        · init( other: COpaquePointer)。通过COpaquePointer类型指针创建。
        · init<U>(
        from: UnsafeMutablePointer<U>)。通过 UnsafeMutablePointer类型指针创建。
        · init<U>(_ from: UnsafePointer<U>)。通过UnsafePointer类型指针创建。
        UnsafePointer<T>主要的属性:
        · memory。只读属性,它能够访问指针指向的内容。
        UnsafePointer<T>主要的方法:
        · successor() -> UnsafePointer<T>。获得指针指向的下一个内存地址的内容。
        · predecessor() -> UnsafePointer<T>。获得指针指向的上一个内存地址的内容。
      2. UnsafeMutablePointer<T>
        UnsafeMutablePointer<T>是一个比较常用的可变指针类型,这种指针对象需要程序员自己手动管理内存,自己负责申请和释放内存。可变指针可以由其他的指针创建,也可以可变指针通过alloc(:)方法申请内存空间,再调用initialize(:)方法初始化指针指向数值。当指针对象释放时候需要调用destroy()方法销毁指针指向对象,它是initialize(:)方法的反向操作,他们两个方法在代码中应该成对出现的。最后还要调用dealloc(:)方法释放指针指向的内存空间,它是alloc(_:)方法的反向操作,这两个方法在代码中也应该成对出现。
      3. AutoreleasingUnsafeMutablePointer<T>
        AutoreleasingUnsafeMutablePointer<T>被称为自动释放指针,在方法或函数中声明为该类型的参数,是输入输出类型的,在调用方法或函数过程中,参数先首先被拷贝到一个无所有权的缓冲区,在方法或函数内使用的这个缓冲区,当方法或函数返回时,缓冲区数据重新写回到参数。



        这是我在学Swift整理的基础笔记,希望给更多刚学IOS开发者带来帮助,在这里博主非常感谢大家的支持!
        到这里就基本完成了Swift语言基础笔记整理了。  如果大家觉得此博文对您有帮助,那就动动你的小手指点个赞!!   哈哈,感谢各位博友的支持!!!
  • 相关阅读:
    python3线程介绍01(如何启动和调用线程)
    CentOS7 设置静态 ip
    png2ico
    Thunderbird 配置 QQ mail
    memcached 开机启动 (Ubuntu)
    CentOS7 docker 安装的 container-selinux 问题及解决
    YAML 的基本语法
    docker 的脚本化安装和使用
    解决Windows下 “setup.py build” 时出现错误 ” error: Unable to find vcvarsall.bat”
    Electric Fence
  • 原文地址:https://www.cnblogs.com/ZRJ-boke/p/6103995.html
Copyright © 2011-2022 走看看