iOS开发之ReactiveCocoa下的MVVM

  最近工作比较忙,但还是出来更新博客了,今天给大家分享一些ReactiveCocoa以及MVVM的一些东西,干活还是比较足的。在之前发表过一篇博文,名字叫做《iOS开发之浅谈MVVM的架构设计与团队协作》,大体上讲的就是使用Block回调的方式实现MVVM的。在写上篇文章时也知道有ReactiveCocoa这个函数响应式编程的框架,并且有许多人用它来更好的实现MVVM。所以在上篇博客发表后,有些同行给评论建议看一下ReactiveCocoa的东西,所以就系统的看了一下ReactiveCocoa的东西。不过有一点要说明的就是,不使用ReactiveCocoa是可以实现MVVM的,并非使用MVVM模式你就必须的使用ReactiveCocoa的东西,你可以使用KVO,Block,Delegate,Navigation等手段,而ReactiveCocoa更优雅的实现了这个过程。ReactiveCocoa就是一个响应式编程的框架,它会使MVVM每层之间交互起来更为方便,所以长和MVVM联系在一起。

  一.函数响应式编程(Function Reactive Programming)

  关于函数响应式编程的东西,我想引用国外这个ReactiveCocoa教学视频(视频链接https:///65637501)中的一张PPT来简单的说一下什么是函数响应式编程。那就直接上图,下图是上方视频链接的截图,很形象的解释了什么是函数响应式编程。简单的说下方c = a + b 定义好后,当a的值变化后,c的值就会自动变化。不过a的值变化时会产生一个信号,这个信号会通知c根据a变化的值来变化自己的值。b的值变化同样也影响c的值。下图很好的表达了这个思想。在此就不做赘述了。

  二. ReactiveCocoa简介

  先简单的介绍一下什么是ReactiveCocoa框架,然后通过实例好好的去搞一搞这个框架,最后就是如何在项目中使用了。关于ReactiveCocoa的理解一些博客(见本篇博客中的链接分享)中把ReactiveCocoa比喻成管道,ReactiveCocoa中的Signal就是管道中的水流。使用ReactiveCocoa可以方便的在MVVM各层之间架起沟通的管道,便于每层之间的交互。现在在我们做的工程中已经在使用ReactiveCocoa框架了,用起来的感觉是非常爽的,好用!

  可以说ReactiveCocoa中核心是信号量机制,Signal在ReactiveCocoa中发挥着强大的不可代替的作用,可谓是ReactiveCocoa的灵魂所。Signal是可以携带一些对象和参数的,你可以获取该对象并且可以对该信号量携带的值进行map, filter等常用操作,操作后的值会和该信号量进行绑定。先简单的这么一说,后边的部分回详细的介绍如何让信号量发挥强大的作用。

  ReactiveCocoa中对Block的使用可谓是淋漓尽致,如果对Block使用不熟的朋友可以补一下Block的东西,然后在回头看一下ReactiveCocoa的东西。关于ReactiveCocoa更多的东西,请参考Github上的链接(https://github.com/ReactiveCocoa/ReactiveCocoa)。

  三. 在工程中引入ReactiveCocoa

    1.你可以使用Github上的加入方式如下所示,本人感觉比较麻烦,就没有使用,采用的第二种方法(CocoaPods)。

    2.上面的步骤难免有些麻烦,所以用CocoaPods更为便捷一些,Profile文件中的内容如下所示,我用的是2.5版本。3.0后就支持Swift了,因为我没有用Swift写东西,所以就用的是2.5版本,设置完Profile文件后,pod install即可。

    你可以pod search ReactiveCocoa看一下版本,选择你需要的版本即可。

  四.使用ReactiveCocoa

  下方会通过一些简单的实例来介绍一下信号量机制和一些常用的方法。

    1.引入相应的头文件

    在工程中引入下方的头文件(建议在Pch文件中引入)就可以使用我们的ReactiveCocoa框架了

1 #import
2 #import

    2. Sequence和Map

    Sequence:队列,是ReactiveCocoa中引入的一个类型,它类似于数组,我们可以暂且把Sequence看做绑定信号量的数组吧。在OC中的NSArray可以通过rac_sequence方法转换成ReactiveCocoa中的Sequence,然后就可以调用处理信号的一些方法了。

    参考以下实例代码:

      (1)把NSArray通过rac_sequence方法生成RAC中的Sequence

      (2)获取该Sequence对象的信号量

      (3)调用Signal的Map方法,使每个元素的首字母大写

      (4)通过subscribNext方法对其进行遍历输出

 1 //uppercaseString use map
 2 - (void)uppercaseString {
 3
 4     RACSequence *sequence = [@[@"you", @"are", @"beautiful"] rac_sequence];
 5
 6     RACSignal *signal =  sequence.signal;
 7
 8     RACSignal *capitalizedSignal = [signal map:^id(NSString * value) {
 9                                return [value capitalizedString];
10                             }];
11
12     [signal subscribeNext:^(NSString * x) {
13         NSLog(@"signal --- %@", x);
14     }];
15
16     [capitalizedSignal subscribeNext:^(NSString * x) {
17         NSLog(@"capitalizedSignal --- %@", x);
18     }];
19 }

    下方截图是上个这个方法中的运行结果,从运行结果不难看出,通过Signal相应的方法处理完后,处理的结果会与新返回的信号量所绑定。原信号量中的值保持不变。每次信号量调用相应的方法处理完数据后,都会返回一个新的信号量,而这个信号量是独立于原信号量的。

    

    由上面的介绍可知,上面方法中的一坨代码可以写成下方的一串。因为一个方法调用后会返回一个持有新结果的新的信号量,然后在这个信号量的基础上再次调用信号量其他的方法。Signal还有其他一些好用的方法,用法和map方法类似,在此就不一一赘述了,gitHub上有相应的实例文档。

1 - (void)uppercaseString {
2
3     [[[@[@"you", @"are", @"beautiful"] rac_sequence].signal
4      map:^id(NSString * value) {
5         return [value capitalizedString];
6     }] subscribeNext:^(id x) {
7         NSLog(@"capitalizedSignal --- %@", x);
8     }];
9 }

    3.信号量开关(Switch)

    上面把信号量比喻成水管,那么Switch就是水龙头呢。通过Switch我们可以控制那个信号量起作用,并且可以在信号量之间进行切换。也可以这么理解,把Switch看成另一段水管,Switch对接那个水管,就流那个水管的水,这样比喻应该更为贴切一些。下方是一个关于Switch的一个小实例。

      (1) 首先创建3个自定义信号量(3个水管),前两个水管是用来接通不同的水源的(google, baidu), 而最后一个信号量是用来对接不同水源水管的水管(signalOfSignal)。signalOfSignal接baidu水管上,他就流baidu水源的水,接google水管上就流google水源的水。

      (2) 把signalOfSignal信号量通过switchToLatest方法加工成开关信号量。

      (3) 紧接着是对通过开关数据进行处理。

      (4) 开关对接baidu信号量,然后baidu和google信号量同时往水管里灌入数据,那么起作用的是baidu信号量。

      (5) 开关对接google信号量,google和baidu信号量发送数据,则google信号量输出到signalOfSignal中

 1 //信号开关Switch
 2 - (void)signalSwitch {
 3     //创建3个自定义信号
 4     RACSubject *google = [RACSubject subject];
 5     RACSubject *baidu = [RACSubject subject];
 6     RACSubject *signalOfSignal = [RACSubject subject];
 7
 8     //获取开关信号
 9     RACSignal *switchSignal = [signalOfSignal switchToLatest];
10
11     //对通过开关的信号量进行操作
12     [[switchSignal  map:^id(NSString * value) {
13         return [@"https//www." stringByAppendingFormat:@"%@", value];
14     }] subscribeNext:^(NSString * x) {
15         NSLog(@"%@", x);
16     }];
17
18
19     //通过开关打开baidu
20     [signalOfSignal sendNext:baidu];
21     [baidu sendNext:@"baidu.com"];
22     [google sendNext:@"google.com"];
23
24     //通过开关打开google
25     [signalOfSignal sendNext:google];
26     [baidu sendNext:@"baidu.com/"];
27     [google sendNext:@"google.com/"];
28 }

    上面代码输出结果如下:

    4.信号量的合并

    信号量的合并说白了就是把两个水管中的水合成一个水管中的水。但这个合并有个限制,当两个水管中都有水的时候才合并。如果一个水管中有水,另一个水管中没有水,那么有水的水管会等到无水的水管中来水了,在与这个水管中的水按规则进行合并。下面这个实例就是把两个信号量进行合并。

    (1) 首先创建两个自定义的信号量letters和numbers

    (2) 吧两个信号量通过combineLatest函数进行合并,combineLatest说明要合并信号量中最后发送的值

    (3) reduce块中是合并规则:把numbers中的值拼接到letters信号量中的值后边。

    (4) 经过上面的步骤就是创建所需的相关信号量,也就是相当于架好运输的管道。接着我们就可以通过sendNext方法来往信号量中发送值了,也就是往管道中进行灌水。

 1 //组合信号
 2 - (void)combiningLatest{
 3     RACSubject *letters = [RACSubject subject];
 4     RACSubject *numbers = [RACSubject subject];
 5
 6     [[RACSignal
 7      combineLatest:@[letters, numbers]
 8      reduce:^(NSString *letter, NSString *number){
 9          return [letter stringByAppendingString:number];
10      }]
11      subscribeNext:^(NSString * x) {
12          NSLog(@"%@", x);
13      }];
14
15     //B1 C1 C2
16     [letters sendNext:@"A"];
17     [letters sendNext:@"B"];
18     [numbers sendNext:@"1"];
19     [letters sendNext:@"C"];
20     [numbers sendNext:@"2"];
21 }

  上面示例的运行输出结果如下:

    下面是自己画的原理图,思路应该还算是清晰。

    5.信号的合并(merge)

      信号合并就理解起来就比较简单了,merge信号量规则比较简单,就是把多个信号量,放入数组中通过merge函数来合并数组中的所有信号量为一个。类比一下,合并后,无论哪个水管中有水都会在merge产生的水管中流出来的。下方是merge信号量的代码:

      (1) 创建三个自定义信号量, 用于merge

      (2) 合并上面创建的3个信号量

      (3) 往信号里灌入数据

 1 //合并信号
 2 - (void)merge {
 3     RACSubject *letters = [RACSubject subject];
 4     RACSubject *numbers = [RACSubject subject];
 5     RACSubject *chinese = [RACSubject subject];
 6
 7     [[RACSignal
 8      merge:@[letters, numbers, chinese]]
 9      subscribeNext:^(id x) {
10         NSLog(@"merge:%@", x);
11     }];
12
13     [letters sendNext:@"AAA"];
14     [numbers sendNext:@"666"];
15     [chinese sendNext:@"你好!"];
16 }

    上面代码运行结果如下:

    上面示例的原理图如下:

  五. 在MVVM中引入RactiveCocoa

    学以致用,最后来个简单的实例,来感受一下如何在MVVM中使用RactiveCocoa。当然今天RAC的应用是非常简单的,但原理就是这样的。接下啦我们要使用RAC模拟一下登录功能,当然,网络请求也是模拟的,这不是重点。重点在于如何在MVVM各层之间使用RAC的信号量来更方便的在各个层之间进行响应式数据交互。下面这个实例的UI是非常简单的,并且实现起来也是灰常简单的,关键还是在于RAC的应用。

    1.搭建Demo所需UI,用户界面非常简单,公有两个用户界面,一个是登录页面(两个输入框,一个登录按钮),一个是登录后跳转的页面(一个展示用户名和密码的Label)。下方是使用Storyboard实现的用户登录页面。实现完后,个两个页面各自关联一个ViewContorller类。

    2.下方是整个小Demo的工程目录,因为我们今天的重点是如何在MVVM中使用RAC, 所以重点在于RAC的应用,对于MVVM的分层就简化一些。下方有VC层,在VC层中有两个视图控制器,一个是登录使用的视图控制器(ViewContorller)另一个是登录成功后的视图控制器(LoginSuccessViewController)。而ViewModel中则是负责登录的ViewModel业务逻辑层,该层中负责数据验证,网络请求,数据存储等一些与UI无关的业务逻辑。

    3.实现登录的ViewModel层

    因为ViewModel层是独立于UI层而存在的,所以可以在没有UI的情况下我们就可以去实现相应模块的ViewModel层。这正好减少了个个层次间的耦合性,同时也提高了可测试性,总体上改善了可维护性。好废话少说,接下来要实现登录的ViewModel层。

    (1) 登录ViewModel层对应的类的头文件中的内容如下所示(VCViewModel.h), 其实下方一些常用的信号量可以抽象出来放到ViewModel的父类中,这为了简化Demo没有做父类的抽象。下方就是VCViewModel中interface定义的公有属性和公有方法(Public)。userName和password(NSString类型) 用来绑定用户输入的用户名和密码。下方三个自定义信号量successObject, failureObject, errorObject 用来发送网络请求的数据。successObject负责处理网络请求成功且符合正常业务逻辑的事件, failureObject负责网络请求成功不符合正常业务逻辑的处理,errorObject负责网络异常处理。

 1 //
 2 //  VCViewModel.h
 3 //  ReactiveCocoaDemo
 4 //
 5 //  Created by Mr.LuDashi on 15/10/19.
 6 //  Copyright © 2015年 ZeluLi. All rights reserved.
 7 //
 8
 9 #import
10
11 @interface VCViewModel : NSObject
12 @property (nonatomic, strong) NSString *userName;
13 @property (nonatomic, strong) NSString *password;
14 @property (nonatomic, strong) RACSubject *successObject;
15 @property (nonatomic, strong) RACSubject *failureObject;
16 @property (nonatomic, strong) RACSubject *errorObject;
17
18 - (id) buttonIsValid;
19 - (void)login;
20 @end

    

   上面可能说的有些抽象,结合项目中的实例来解释一下什么时候发送successObject信号量,如何发送failureObject信号量,何时使用errorObject信号量。

   以某些理财App中购买理财产品的业务流程为例。在用户下单之前先去判断用户是否实名认证以及绑定银行卡,如果用户已经实名和绑定银行卡就走正常支付流程(用户就是想去下单购买),VM就往VC发送successObject信号,当前VC就会根据信号量的指示跳转到下单支付页面。  但是如果用户没有实名或者绑卡,那么VM就给VC发送failureObject信号,根据信号量中的参数来判断是走实名认证流程还是走绑定银行卡流程。 errorObject就比较简单了,网络异常,后台服务器抛出的异常等不需要iOS这边做业务逻辑处理的,就放在errorObject中负责错误信息的展示。

    文字说完了,如果有些小伙伴还不太明白,那看下面这张原理图吧。把三种信号量我们可以类比成十字路口的红绿灯。successObject就是绿灯,可以走正常流程。failureObject是黄灯,先等一下,完成该做的就可以走绿灯了。而errorObject就是一红灯,报错异常,终止业务流程并提升错误信息。有图有真相,到这儿如果还不理解我就没招了。

    在Public方法中- (id) buttonIsValid; 负责返回登录按钮是否可用的信号量。- (void)login;发起网络请求,调用登录网络接口。

  

    (2)代码的具体实现如下(VCViewModel.m中的代码),私有属性如下。userNameSignal用来存储用户名的信号量,passwordSignal是用来存储密码的信号量。reqestData则是用来存储返回数据的。

1 @interface VCViewModel ()
2 @property (nonatomic, strong) RACSignal *userNameSignal;
3 @property (nonatomic, strong) RACSignal *passwordSignal;
4 @property (nonatomic, strong) NSArray *requestData;
5 @end

    (3)VCViewModel的初始化方法如下,负责初始化属性。

 1 - (instancetype)init
 2 {
 3     self = [super init];
 4     if (self) {
 5         [self initialize];
 6     }
 7     return self;
 8 }
 9
10 - (void)initialize {
11     _userNameSignal = RACObserve(self, userName);
12     _passwordSignal = RACObserve(self, password);
13     _successObject = [RACSubject subject];
14     _failureObject = [RACSubject subject];
15     _errorObject = [RACSubject subject];
16 }

  

    (4) 发送登录按钮是否可用信号的方法如下,主要用到了信号量的合并。

//合并两个输入框信号,并返回按钮bool类型的值
- (id) buttonIsValid {

    RACSignal *isValid = [RACSignal
                          combineLatest:@[_userNameSignal, _passwordSignal]
                          reduce:^id(NSString *userName, NSString *password){
                              return @(userName.length >= 3 && password.length >= 3);
                          }];

    return isValid;
}

    (5) 模拟网络请求的发送,并发出网络请求成功的信号,具体代码如下

 1 - (void)login{
 2
 3     //网络请求进行登录
 4     _requestData = @[_userName, _password];
 5
 6     //成功发送成功的信号
 7     [_successObject sendNext:_requestData];
 8
 9     //业务逻辑失败和网络请求失败发送fail或者error信号并传参
10
11 }

  4. 上面是VM的实现,如果要进行单元测试的话,就对相应的VM类进行初始化,调用相应的函数进行单元测试即可。接着就是看如何在相应的VC模块中使用VM。

    (1) 在VC中实例化相应的VM类,并绑定相应的参数和实现接收不同信号的方法,具体代码如下:

 1 //关联ViewModel
 2 - (void)bindModel {
 3     _viewModel = [[VCViewModel alloc] init];
 4
 5
 6     RAC(self.viewModel, userName) = self.userNameTextField.rac_textSignal;
 7     RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal;
 8     RAC(self.loginButton, enabled) = [_viewModel buttonIsValid];
 9
10     @weakify(self);
11
12     //登录成功要处理的方法
13     [self.viewModel.successObject subscribeNext:^(NSArray * x) {
14         @strongify(self);
15         LoginSuccessViewController *vc = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"LoginSuccessViewController"];
16         vc.userName = x[0];
17         vc.password = x[1];
18         [self presentViewController:vc animated:YES completion:^{
19
20         }];
21     }];
22
23     //fail
24     [self.viewModel.failureObject subscribeNext:^(id x) {
25
26     }];
27
28     //error
29     [self.viewModel.errorObject subscribeNext:^(id x) {
30
31     }];
32
33 }

    (2) 点击登录按钮,调用VM中登录相应的网络请求方法即可

1 - (void)onClick {
2     //按钮点击事件
3     [[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
4      subscribeNext:^(id x) {
5          [_viewModel login];
6      }];
7 }

    到此为止,一个完整模拟登录模块的RAC下的MVVM就实现完毕。当然上面的Demo是非常简陋的,还有好多地方需要进化。不过麻雀虽小,道理你懂得。主要是通过上面的Demo来感受一下RAC中的信号量机制以及应用场景。

    5.上面代码写完,我们就可以运行看一下运行效果了,下方是运行后的效果,

  上述工程GitHub分享链接:https://github.com/lizelu/MVVMWithReactiveCocoa

  其他参考资料:

https://github.com/ReactiveCocoa/ReactiveViewModel

http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/

http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/

http://nshipster.cn/reactivecocoa/

http://limboy.me/ios/2013/06/19/frp-reactivecocoa.html

https:///65637501

http://southpeak.github.io/blog/2014/08/08/mvvmzhi-nan-yi-:flickrsou-suo-shi-li/

http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-[?]-:xin-hao/

http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-er-:twittersou-suo-shi-li/

ViewModel:

  Kicking off network or database requests

  Determining when information should be hidden or shown

  Date and number formatting

  Localization

ViewController:

  Layout

  Animations

  Device rotation

  View and window transitions

  Presenting loaded UI

分享:

时间: 2024-10-13 14:42:18

iOS开发之ReactiveCocoa下的MVVM的相关文章

iOS开发之ReactiveCocoa下的MVVM(干货分享)

最近工作比较忙,但还是出来更新博客了,今天给大家分享一些ReactiveCocoa以及MVVM的一些东西,干活还是比较足的.在之前发表过一篇博文,名字叫做<iOS开发之浅谈MVVM的架构设计与团队协作>,大体上讲的就是使用Block回调的方式实现MVVM的.在写上篇文章时也知道有ReactiveCocoa这个函数响应式编程的框架,并且有许多人用它来更好的实现MVVM.所以在上篇博客发表后,有些同行给评论建议看一下ReactiveCocoa的东西,所以就系统的看了一下ReactiveCocoa的

iOS 开发之 ReactiveCocoa(基础)

前言 前段时间在看Masonry这个全新的第三方的布局框架的时候,开始了解了链式编程.后来慢慢的又开始了解函数式编程和响应式编程.在这集中的编程思想下,开始接触和研究了ReactiveCocoa这个框架.在很多大神的blog中,都对这个框架有着高度的评价,各种秀自己如何灵活的使用ReactiveCocoa.今天主要就是这个框架的一些自己的学习笔记. 1.ReactiveCocoa简介 ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架. Reac

iOS 开发之 ReactiveCocoa(进阶)

Map : 映射 UITextField *textField =[[UITextField alloc]initWithFrame:CGRectMake(100, 100, 100, 40)]; textField.backgroundColor =[UIColor redColor]; [self.view addSubview:textField]; [[textField.rac_textSignal map:^id(NSString * value) { return @(value.

iOS开发之MVVM在项目中的应用

今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦~). 由于本人项目经验有限,关于架构设计方面的东西理解有限,我个人对MVVM的理解主要是借鉴于之前的用过的MVC的Web框架~在学校的时候用过ThinkPHP框架,和SSH框架,都是MVC的架构模式,今天MVVM与传统的MVC可谓是极为相似,也可以说是兄弟关系,也就是一家人了. 说到架构设计和团队

iOS 开发之Widget的开发及使用(下)

在iOS 开发之Widget的开发及使用(上)中都交代清楚了关于widget扩展的添加,以及布局部分的内容,下面说下关键部分:关于数据共享的操作. 在Apple发布iOS8.0以后,对App有提供一个新的扩展选择项,叫做App groups,选择宿主Target之后,在capabilities选项卡下面会找到这个选项: 那么这个选项主要是做何使用呢? iOS系统,每个开发应该都清楚,其内部程序都是遵循沙盒机制,App与App之间,是不能进行数据共享的,A 不能访问 B 的数据,同样 B 也不能访

IOS开发之XML解析以及下拉刷新上拉加载更多的分享

IOS开发之XML解析 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示XML文件版本, 内部文本使用的编码 <root> 表示根节点 <CityName>北京</CityName>  一个结点, CityName是结点名, 北京结点值 <Item key="1" value="A"></Item>  

iOS开发之WKWebView简单使用和常用使用场景

iOS开发之 WKWebVeiw使用 想用UIWebVeiw做的,但是突然想起来在iOS8中出了一个新的WKWebView,算是UIWebVeiw的升级版.本着对新事物的好奇,就上网查了一下,但是找了好多个都没说的多了详细,于是就问谷歌,找文档,看看使用方法,试用了一下,果然不错,记录下来,大家分享! WKWebView的特点: 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷

iOS开发之Auto Layout入门

随着iPhone6与iOS8的临近,适配的问题讲更加复杂,最近学习了一下Auto Layout的使用,与大家分享.  什么是Auto Layout? Auto Layout是iOS6发布后引入的一个全新的布局特性,其目的是弥补以往Autoresizing在布局方面的不足之处,以及未来面对更多尺寸适配时界面布局可以更好的适应. 为什么要用Auto Layout? Autolayout能解决不同屏幕(iPhone4,iPhone5,iPad...)之间的适配问题. 在iPhone4时代开发者只需要适

iOS开发之UILabel

UILabel是iOS开发中常用的一个组件,主要用来显示内容. UILabel的主要使用如下: ? 1 2 3 4 5 6 7 8 9 10 /*尺寸*/ CGRect labelRect = CGRectMake(100, 100, 80, 40); /*初始化*/ UILabel *titleLabel = [[UILabel alloc] initWithFrame:labelRect]; /*一些属性的设置*/ titleLabel.font = [UIFont systemFontOf