ReactiveCocoa 是一个框架,它允许你在你的iOS程序中使用函数响应式(FRP)技术。加上第一部分的讲解,你将会学会如何使用信号量(对事件发出数据流)如何替代标准的动作和事件处理逻辑。你也会学到如何转换、分离和组合这些信号量。

在这里,也就是第二部分里,你将会学到更多先进的ReactiveCocoa特性,包括:

1、另外两个事件类型:error和completed

2、Throttling(节流)

3、Threading

4、Continuations

5、更多。。。

是时候开始了。

Twitter Instant

这里我们要使用的贯穿整个教程的程序是叫做Twitter Instant的程序,该程序可以在你输入的时候实时更新搜索到的结果。

该应用包括一些基本的用户交互界面和一些平凡的代码,了解之后就可以开始了。在第一部分里面,你使用Cocoapods来把CocoaPods加载到你的工程里面,这里的工程里面就已经包含了Podfile文件,你只需要pod install一下即可。

然后重新打开工程即可。(这个时候打开TwitterInstant.xcworkspace):

1、TwitterInstant:这是你的程序逻辑

2、Pods:里面是包括的三方类库

运行一下程序,你会看到如下的页面:

花费一会时间让你自己熟悉一下整个工程。它就是一个简单的split viewController app.左边的是RWSearchFormViewController,右边的是:RWSearchResultsViewController。

自己说:原文简单介绍了一下该工程,就不在介绍看一下就可以了。

验证搜索文本

你第一件要做的事情就是去验证一下搜索文本,让它确保大于两个字符串。如果你看了第一篇文章,这个将会很简单。

在RWSearchFormViewController.m中添加方法如下:

1
2
3
- (BOOL)isValidSearchText:(NSString *)text {
  return text.length > 2;
}

这就简单的保证了搜索的字符串大于两个字符。写这个很简单的逻辑你可能会问:为什么要分开该方法到工程文件里面呢?

当前的逻辑很简单,但是如果后面这个会更复杂呢?在上面的例子中,你只需要修改一个地方。此外,上面的写法让你的代码更有表现力,它告诉你为什么要检查string的长度。我们应该遵守好的编码习惯,不是么?

然后,我们导入头文件:

1
#import

然后在导入该头文件的文件里面的viewDidLoad后面写上如下代码:

1
2
3
4
5
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?      [UIColor whiteColor] : [UIColor yellowColor];  }]
  subscribeNext:^(UIColor *color) {
    self.searchText.backgroundColor = color;  }];

想想这是做什么呢?上面的代码:

1、取走搜索文本框的信号量

2、把它转换一下:用背景色来预示内容是否可用。

3、然后设置backgroundColor属性在subscribeNext:的block里面。

Build然后运行我们就会发现当搜索有效的时候就会是白色,搜索字符串无效的时候就是黄色。

下面是图解,这个简单的反应传输看起来如下:

ran_textSignal发出包含当前文本框每次改变内容的next事件。map那个步骤转换text value,将其转换成了color,subscribeNext那一步将这个value提供给了textField的background。

当然了,你从第一个教程一定记得这些,对吧?如果你不记得的话,你也许想在这里停止阅读,至少读了整个测试工程。

在添加Twitter 搜索逻辑之前 ,这里有一些更有趣的话题。

Formatting of Pipelines

当你正在钻研格式化的ReactiveCocoa代码的时候,普遍接受的惯例就是:每一个操作在一个新行,和所有步骤垂直对齐的。

在下面的图片,你会看到更复杂的对齐方式,从上一个教程拿来的图片:

这样你会更容易看到该组成管道的操作。另外,在每个block中用最少的代码任何超过几行的都应该拆分出一个私有的方法。

不幸的是,Xcode真的不喜欢这种格式类型的代码,因此你可能需要找到自己调整。

Memory Management

思考一下你刚才加入到TwitterInstant的代码。你是否想过你刚才创建的管道式如何保留的呢?无疑地,是否是它没有赋值为一个变量或者属性他就不会有自己的引用计数,注定会消亡呢?

其中一个设计目标就是ReactiveCocoa允许这种类型的编程,这里管道可以匿名形式。所有你写过的响应式代码都应该看起来比较直观。

为了支持这种模型,ReactiveCocoa维持和保留自己全局的信号。如果它有一个或者多个subscribers(订阅者),信号就会活跃。如果所有的订阅者都移除掉了,信号就会被释放。想了解更多关于ReactiveCocoa管理进程,可以参看Memory Management 文档。

这就剩下了最后的问题:你如何从一个信号取消订阅?当一个completed或者error事件之后,订阅会自动的移除(一会就会学到)。手工的移除将会通过RACDisposable.

所有RACSignal的订阅方法都会返回一个RACDisposable实例,它允许你通过处置方法手动的移除订阅。下面是一个使用当前管道的快速的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RACSignal *backgroundColorSignal =
  [self.searchText.rac_textSignal
    map:^id(NSString *text) {
      return [self isValidSearchText:text] ?
        [UIColor whiteColor] : [UIColor yellowColor];
    }];
  
RACDisposable *subscription =
  [backgroundColorSignal
    subscribeNext:^(UIColor *color) {
      self.searchText.backgroundColor = color;
    }];
  
// at some point in the future ...
[subscription dispose];

你不会经常做这些,但是你必须知道可能性的存在。

Note:作为这些的一个推论,如果你创建了一个管道,但是你不给他订阅,这个管道将不会执行,这些包括任何侧面的影响,例如doNext:blocks。

Avoiding Retain Cycles

当ReactiveCocoa在场景背后做了好多聪明的事情—这就意味着你不必要担心太多关于信号量的内存管理——这里有一个很重要的内存喜爱那个管的问你你需要考虑。

如果你看到下面的响应式代码你仅仅加入:

1
2
3
4
5
6
7
8
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    self.searchText.backgroundColor = color;
  }];

subscribeNext:block使用self来获得一个textField的引用,Blocks在封闭返回内捕获并且持有了值。因此在self和这个信号量之间造成了强引用,造成了循环引用。这取决于对象的生命周期,如果他的生命周期是应用程序的生命周期,那这样是没关系的,但是在更复杂的应用中就不行了。

为了避免这种潜在的循环引用,苹果官方文档:Working With Blocks 建议捕捉一个弱引用self,当前的代码可以这样写:

1
2
3
4
5
6
7
8
9
10
__weak RWSearchFormViewController *bself = self; // Capture the weak reference
  
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    bself.searchText.backgroundColor = color;
  }];

在上面的代码中,bself就是self标记为__weak(使用它可以make一个弱引用)的引用,现在可以看到使用textField的时候使用bself代用的。这看起来并不是那么高雅。

ReactiveCocoa框架包含了一个小诀窍,你可以使用它代替上百年的代码。添加下面的引用:

1
#import "RACEXTScope.h"

然后代码修改后如下:

1
2
3
4
5
6
7
8
9
10
@weakify(self)
[[self.searchText.rac_textSignal
  map:^id(NSString *text) {
    return [self isValidSearchText:text] ?
      [UIColor whiteColor] : [UIColor yellowColor];
  }]
  subscribeNext:^(UIColor *color) {
    @strongify(self)
    self.searchText.backgroundColor = color;
  }];

@weakify和@strongify语句是在Extended Objective-C库的宏定义,他们也包含在ReactiveCocoa中。@weakify 宏定义允许你创建一个若饮用的影子变量,@strongify宏定义允许你创建一个前面使用@weakify传递的强引用变量。

Note:如果你对@weakify和@strongify感兴趣,可以进入RACEXTSCope.h中查看其实现。

最后一个提醒,当在Blocks使用实例变量的时候要小心,这样也会导致block捕获一个self的强引用。你可以打开编译警告去告诉你你的代码有这个问题。

好了,你从理论中幸存出来了,恭喜。现在你变得更加明智,准备移步到有趣的环节:添加一些真实的函数到你的工程里面。

Requesting Access to Twitter

为了在TwitterInstant 应用中去搜索Tweets,你将会用到社交框架(Social Framework)。为了访问Twitter你需要使用Accounts Framework。

在你添加代码之前,你需要到模拟器中输入你的账号:

设置好账号之后,然后你只需要在RWSearchFormViewController.m中导入以下文件即可:

1
#import #import

然后在引入的头文件下面写如下的代码:

1
2
3
4
5
typedef NS_ENUM(NSInteger, RWTwitterInstantError) {
    RWTwitterInstantErrorAccessDenied,
    RWTwitterInstantErrorNoTwitterAccounts,
    RWTwitterInstantErrorInvalidResponse}; 
static NSString * const RWTwitterInstantDomain = @"TwitterInstant";

你将会使用这些简单地鉴定错误。然后在interface和end之间声明两个属性:

1
2
@property (strong, nonatomic) ACAccountStore *accountStore;
@property (strong, nonatomic) ACAccountType *twitterAccountType;

ACAccountsStore类提供访问你当前设备有的social账号,ACAccountType类代表指定类型的账户。

然后在viewDidLoad里面加入以下代码:

1
2
self.accountStore = [[ACAccountStore alloc] init];
self.twitterAccountType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

这些代码创建了账户存储和Twitter账号标示。在.m中添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (RACSignal *)requestAccessToTwitterSignal {
 
  // 1 - define an error
  NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain
                                             code:RWTwitterInstantErrorAccessDenied
                                         userInfo:nil];
  // 2 - create the signal
  @weakify(self)
  return [RACSignal createSignal:^RACDisposable *(id subscriber) {
    // 3 - request access to twitter
    @strongify(self)
    [self.accountStore
       requestAccessToAccountsWithType:self.twitterAccountType
         options:nil
      completion:^(BOOL granted, NSError *error) {
          // 4 - handle the response
          if (!granted) {
            [subscriber sendError:accessError];
          else {
            [subscriber sendNext:nil];
            [subscriber sendCompleted];
          }
        }];
    return nil;
  }];
}

这个方法的作用是:

1、定义了如果用户拒绝访问的错误

2、根据第一个入门教程,类方法createSignal返回了一个RACSignal的实例。

3、通过账户存储请求访问Twitter。在这一点上,用户将看到一个提示,要求他们给予这个程序访问Twitter账户的弹框。

4、当用户同意或者拒绝访问,信号事件就会触发。如果用户同意访问,next事件将会紧随而来,然后是completed发送,如果用户拒绝访问,error事件会触发。

如果你回想其第一个入门教程,一个信号可以以三种不同的事件发出:

1、next

2、completed

3、error

超过了signal的生命周期,它将不会发出任何信号事件。

最后,为了充分利用信号,在viewDidLoad后面添加如下代码;

1
2
3
4
[[self requestAccessToTwitterSignal]
  subscribeNext:^(id x) {
    NSLog(@"Access granted");  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);  }];

如果你运行程序,将会看到一个弹出框:

提示是否允许访问权限,如果ok,则打印出来Access granted ,否则将会走error。

Accounts Framework会记住你的决定,因此如果想再次测试,你需要针对模拟机进行:Reset Contents and Settings。

Chaining Signals

一旦用户允许访问Twitter账户,为了执行twitter,程序将会不断监听搜索内容textField的变化.

程序需要等待信号,它请求访问Twitter去发出completed事件,然后订阅textField的信号。不同信号连续的链是一个共有的问题,但是ReactiveCocoa处理起来非常优雅。

用下面的代码替换当前在viewDidLoad后面的管道:

1
2
3
4
5
6
7
8
9
10
[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;
  }]
  subscribeNext:^(id x) {
    NSLog(@"%@", x);
  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);
  }];

then方法会一直等待,知道completed事件发出,然后订阅者通过自己的block参数返回,这有效地将控制从一个信号传递给下一个。

Note:上面已经写过了@weakly(self);所以这里就不用再写了。

then方法传递error事件。因此最后的subscribeNext:error: block还接收初始的访问请求错误。

当你运行的时候,然后允许访问,你应该可以在控制台看到打印出来的你输入的东西。

然后,添加filter操作到管道去移除无效的搜索字符串。在这个实例中,他们是不到三个字符的string:

1
2
3
4
5
6
7
8
9
10
[[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;  }]
  filter:^BOOL(NSString *text) {
    @strongify(self)
    return [self isValidSearchText:text];  }]
  subscribeNext:^(id x) {
    NSLog(@"%@", x);  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);  }];

运行就可以在控制台看到只有三个以上的才能输出。

图解一下上边的管道:

程序管道从requestAccessToTwitterSignal信号开始,然后转换到tac_textSignal。同事next事件通过filter,最后到达订阅block.你也可以看到任何通过第一步的error事件。

现在你有一个发出搜索text的信号,它可以用来搜索Twitter了。很有趣吧。

Searching Twitter

Social Framework是一个访问Twitter 搜索API的选项。然而,它并无法响应搜索,下一步就是给信号包括API请求方法。在当前的控制器中,添加如下方法:

1
2
3
4
5
- (SLRequest *)requestforTwitterSearchWithText:(NSString *)text { 
     NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"];      NSDictionary *params = @{@"q" : text};   
     SLRequest *request =  [SLRequest requestForServiceType:SLServiceTypeTwitter                                           requestMethod:SLRequestMethodGET  URL:url parameters:params        ];  
     return request;
 }

这创建了一个请求:搜索Twitter(V.1.1REST API)。这个是调用Twitter的api。

下一步就是创建一个基于request的信号量。添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (RACSignal *)signalForSearchWithText:(NSString *)text {
  
  // 1 - define the errors
  NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain
                                                 code:RWTwitterInstantErrorNoTwitterAccounts
                                             userInfo:nil]; 
  NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain
                                                      code:RWTwitterInstantErrorInvalidResponse
                                                  userInfo:nil]; 
  // 2 - create the signal block
  @weakify(self)
  return [RACSignal createSignal:^RACDisposable *(id subscriber) {
    @strongify(self); 
    // 3 - create the request
    SLRequest *request = [self requestforTwitterSearchWithText:text]; 
    // 4 - supply a twitter account
    NSArray *twitterAccounts = [self.accountStore
      accountsWithAccountType:self.twitterAccountType];    if (twitterAccounts.count == 0) {
      [subscriber sendError:noAccountsError];    } else {
      [request setAccount:[twitterAccounts lastObject]]; 
      // 5 - perform the request
      [request performRequestWithHandler: ^(NSData *responseData,                                          NSHTTPURLResponse *urlResponse, NSError *error) {
        if (urlResponse.statusCode == 200) {
  
          // 6 - on success, parse the response
          NSDictionary *timelineData =
             [NSJSONSerialization JSONObjectWithData:responseData
                                             options:NSJSONReadingAllowFragments
                                               error:nil];          [subscriber sendNext:timelineData];          [subscriber sendCompleted];        }
        else {
          // 7 - send an error on failure
          [subscriber sendError:invalidResponseError];        }
      }];    }
  
    return nil;  }];}

然后在viewDidLoad方法中进一步添加信号量:

1
2
3
4
5
6
7
8
9
10
11
12
13
[[[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;  }]
  filter:^BOOL(NSString *text) {
    @strongify(self)
    return [self isValidSearchText:text];  }]
  flattenMap:^RACStream *(NSString *text) {
    @strongify(self)
    return [self signalForSearchWithText:text];  }]
  subscribeNext:^(id x) {
    NSLog(@"%@", x);  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);  }];

运行:

即可在控制台里面打印出来筛选的数据。

Threading

我很确信你这会亟待把JSON数据放到UI里面,但是在放到UI里面之前你需要做最后一件事:找到他是什么,你需要做一些探索!

添加一个端点到subscribeNext:error:那个步,然后我们会看到Xcode左侧的Thread,我们发现如果想加载图片的话必须在主线程里面,但是他不在主线程中,所以我们就可以做如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[[[[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;
  }]
  filter:^BOOL(NSString *text) {
    @strongify(self)
    return [self isValidSearchText:text];
  }]
  flattenMap:^RACStream *(NSString *text) {
    @strongify(self)
    return [self signalForSearchWithText:text];
  }]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(id x) {
    NSLog(@"%@", x);
  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);
  }];

这样就会在主线程中运行。也就是更新了管道:添加了deliverOn:操作。

然后再次运行我们就会发现他是在主线程上执行了。这样你就可以更新UI了。

Updating the UI

这里用到了另一个库:LinqToObjectiveC。安装方式就不说了和ReactiveCocoa一样

我们在RWSearchFormViewController中导入:

1
2
#import "RWTweet.h"
#import "NSArray+LinqExtensions.h"

然后在输出json数据的地方修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[[[[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;  }]
  filter:^BOOL(NSString *text) {
    @strongify(self)
    return [self isValidSearchText:text];  }]
  flattenMap:^RACStream *(NSString *text) {
    @strongify(self)
    return [self signalForSearchWithText:text];  }]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(NSDictionary *jsonSearchResult) {
    NSArray *statuses = jsonSearchResult[@"statuses"];    NSArray *tweets = [statuses linq_select:^id(id tweet) {
      return [RWTweet tweetWithStatus:tweet];    }];    [self.resultsViewController displayTweets:tweets];  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);  }];

运行:

就可以看到右侧的详情页面加载到数据了。刚引入的类库其实就是将json数据转换成了model.加载数据的效果如下:

Asynchronous Loading of Images

现在内容都加载出来了,就差图片了。在RWSearchResultsViewController.m中添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {
  
  RACScheduler *scheduler = [RACScheduler
                         schedulerWithPriority:RACSchedulerPriorityBackground];
  
  return [[RACSignal createSignal:^RACDisposable *(id subscriber) {
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
    UIImage *image = [UIImage imageWithData:data];
    [subscriber sendNext:image];
    [subscriber sendCompleted];
    return nil;
  }] subscribeOn:scheduler];
  
}

这会你一ing该就会很熟悉这种模式了。然后在tableview:cellForRowAtIndex:方法里面添加:

1
2
3
4
5
6
7
cell.twitterAvatarView.image = nil;
  
[[[self signalForLoadingImage:tweet.profileImageUrl]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(UIImage *image) {
   cell.twitterAvatarView.image = image;
  }];

再次运行就可以出来效果了:

Throttling(限流)

你可能注意到这个问题:每次输入一个字符串都会立即执行然后导致刷新太快 ,导致每秒会显示几次搜索结果。这不是理想的状态。

一个好的解决方式就是如果搜索内容不变之后的时间间隔后在搜索比如500毫秒。

而ReactiveCocoa是这个工作变的如此简单。

打开RWSearchFormViewController.m然后更新管道,调整如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[[[[[[[self requestAccessToTwitterSignal]
  then:^RACSignal *{
    @strongify(self)
    return self.searchText.rac_textSignal;
  }]
  filter:^BOOL(NSString *text) {
    @strongify(self)
    return [self isValidSearchText:text];
  }]
  throttle:0.5]
  flattenMap:^RACStream *(NSString *text) {
    @strongify(self)
    return [self signalForSearchWithText:text];
  }]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(NSDictionary *jsonSearchResult) {
    NSArray *statuses = jsonSearchResult[@"statuses"];
    NSArray *tweets = [statuses linq_select:^id(id tweet) {
      return [RWTweet tweetWithStatus:tweet];
    }];
    [self.resultsViewController displayTweets:tweets];
  } error:^(NSError *error) {
    NSLog(@"An error occurred: %@", error);
  }];

你会发现这样就可以了。throttle操作只是发送一个操作,这个操作在时间到之后继续进行。

iOS开发 ReactiveCocoa入门教程 第二部分的更多相关文章

  1. iOS开发 ReactiveCocoa入门教程 第一部分

    作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation).但是这些事件都用不同的方式来处理 ...

  2. ReactiveCocoa入门教程--第二部分

    翻译自:http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2 ReactiveCocoa 是一个框架,它允许你在你的iOS程序中使 ...

  3. ReactiveCocoa入门教程——第二部分(转)

    ReactiveCocoa是一个框架,它能让你在iOS应用中使用函数响应式编程(FRP)技术.在本系列教程的第一部分中,你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号.你还学到了如何转换 ...

  4. ReactiveCocoa入门教程——第二部分【转载】

    ReactiveCocoa是一个框架,它能让你在iOS应用中使用函数响应式编程(FRP)技术.在本系列教程的第一部分中,你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号.你还学到了如何转换 ...

  5. 【转】iOS 开发怎么入门?

    原文网址:http://www.zhihu.com/question/20264108 iOS 开发怎么入门? 请问有设计模式.内存管理方面的资料吗?最好有除了官方文档之外的其它内容,10 条评论 分 ...

  6. 李洪强iOS开发之-入门指南

    李洪强iOS开发之-入门指南 1零基础小白如何进行iOS系统学习 首先,学习目标要明确:其次,有了目标,要培养兴趣,经常给自己一些正面的反馈,比如对自己的进步进行鼓励,在前期小步快走:再次,学技术最重 ...

  7. iOS 开发如何入门

    iOS 开发如何入门 新人如何入门 上一篇文章的回复中,很多读者让我推荐入门图书.其实我觉得每个人可能有自己喜欢的学习方式,我习惯的不一定适合你.不过我可以分享一下我当时是如何学习 iOS 开发的. ...

  8. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  9. 游戏控制杆OUYA游戏开发快速入门教程

    游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的 ...

随机推荐

  1. 基于Spark1.3.0的Spark sql三个核心部分

    基于Spark1.3.0的Spark sql三个核心部分: 1.可以架子啊各种结构化数据源(JSON,Hive,and Parquet) 2.可以让你通过SQL,saprk内部程序或者外部攻击,通过标 ...

  2. C#编程利器之一:类(Class)【转】

    C#编程利器之一:类(Class) 面向对象的程序设计(Object-Oriented Programming,简记为OOP)是一种功能非常强大的编程方法,立意于创建软件重用代码,以类为基础去思考编程 ...

  3. transform的用法和注意事项

    1.作用: 1)transform可以控制平移.比例缩放和旋转. 2)transform中的方法主要分为两种:带make和不带make的方法. 3)带make的方法主要是基于控件最初的状态进行改变,所 ...

  4. Linux下安装Redis3.2.4

    安装: 通过wget方式直接在linux上下载Redis $ wget http://download.redis.io/releases/redis-3.2.4.tar.gz , 默认下载到路径是r ...

  5. CDN缓存那些事

    CDN是什么? 谈到CDN的作用,可以用8年买火车票的经历来形象比喻: 8年前,还没有火车票代售点一说,12306.cn更是无从说起.那时候火车票还只能在火车站的售票大厅购买,而我所住的小县城并不通火 ...

  6. Decimal To Fraction 小数转换成分数

    以0.25为例, 0.25 * 100 = 25, 求25 和 100 的最大公约数gcd. 25/gcd 为分子. 100/gcd为分母. //小数转分数 //0.3 -> 3/10, 0.2 ...

  7. ios - 纯代码创建collectionView

    开始考虑好一点点时间,因为一般的都是用xib,或者storyboard来写的.这次用纯代码...废话较多请看 首先把storyboard干掉,工程里面的main干掉 由于干掉了storyboard则启 ...

  8. 第一次尝试编写java

    昨晚手贱,不小心把环境变量path里面都东西全删除了 然后上百度搜了一波又一波 最后还是复制达达的 感动 然后还是不行,最后发现错误竟然是分号用了汉字的分号而不是英文的分号 这个问题在编写C语言也出现 ...

  9. javascript jsscript .js xml html json soap

    javascript ecma标准的脚本语言用于 jsscript 微软标准的一种脚本语言 .js javascript或jsscript保存成文件的形式可用于在html里重复引用 jsscript只 ...

  10. 基于android-async-http的android服务

    1:服务器端/** * Created by LiuFei on 2016/1/22. */public class HttpService extends Service{ @Override pu ...