iOS开发 ReactiveCocoa入门教程 第二部分

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操作只是发送一个操作,这个操作在时间到之后继续进行。

时间: 2024-11-05 07:12:02

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

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

ReactiveCocoa是一个框架,它能让你在iOS应用中使用函数响应式编程(FRP)技术.在本系列教程的第一部分中,你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号.你还学到了如何转换.分割和聚合这些信号. 在本系列教程的第二部分,你将会学到一些ReactiveCocoa的高级功能,包括: 另外两个事件类型:error 和 completed 节流 线程 延伸 其他 是时候深入研究一下了. Twitter Instant 在本教程中你将要开发的应用叫Twitter Instant

ReactiveCocoa入门教程——第二部分

ReactiveCocoa是一个框架,它能让你在iOS应用中使用函数响应式编程(FRP)技术.在本系列教程的第一部分中,你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号.你还学到了如何转换.分割和聚合这些信号. 在本系列教程的第二部分,你将会学到一些ReactiveCocoa的高级功能,包括: 另外两个事件类型:error 和 completed 节流 线程 延伸 其他 是时候深入研究一下了. Twitter Instant 在本教程中你将要开发的应用叫Twitter Instant

iOS 开发如何入门

iOS 开发如何入门 新人如何入门 上一篇文章的回复中,很多读者让我推荐入门图书.其实我觉得每个人可能有自己喜欢的学习方式,我习惯的不一定适合你.不过我可以分享一下我当时是如何学习 iOS 开发的. 我 当时首先快速看了一本小册子,把 Objective-C 的语法了解了一下.那本小册子叫 <From C++ to Objective-C>,里面只讲了一些 C++ 和 OC 的差异性的内容.因为我在学校学过 C++,所以我只需要快速了解一些语法差异就行了. 然 后我就直接看的斯坦福的视频,我看

HealthKit开发快速入门教程大学霸内部教程

HealthKit开发快速入门教程大学霸内部教程 国内第一本HealthKit专向教程.本教程详细讲解iOS中,如何使用HealthKit框架开发健康应用.最后,本教程结合HealthKit和苹果手表iWatch实现一个健身应用--立卧撑拍拍器. 试读下载地址:http://pan.baidu.com/s/1o6iLO2A 目  录 第1章  HealthKit开发概述- 1 1.1  HealthKit简介- 1 1.1.1  HealthKit特点- 1 1.1.2  HealthKit经典

哪有python开发语言入门教程免费下载?

人工智能时代,如果不想被机器人取代,最应该掌握的是编程.Python作为连续10年最受欢迎的编程语言,不但能开发Google .豆瓣等大型网站,还是人工智能领域的第一语言.那么,我猜你想问哪里有python开发语言入门教程. 千锋Python基础教程:http://pan.baidu.com/s/1qYTZiNE Python课程教学高手晋级视频总目录:http://pan.baidu.com/s/1hrXwY8k Python课程windows知识点:http://pan.baidu.com/

斯坦福IOS开发第五课(第二部分)

转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/27845257 作者:小马 五 代码示例 上面讲到的知识点在这个示例都有涉及.另外我这里也只是分析部分重要的代码,更多的知识了解请自行下载代码(文章最下面有地址)并结合公开课一起看. 新建一个single view的工程,然后新增一个视图类,叫FaceView,如下图所示: 然后我们在storyboard里拖进来一个通用的视图控件,作为上面那个视图类对应的视图,如下图所示:    

iOS开发-OpenGLES 入门踩坑

Flat coloring(单色) 是通知OpenGL使用单一的颜色来渲染,OpenGL将一直使用指定的颜色来渲染直到你指定其它的颜色. 指定颜色的方法为 public abstract void glColor4f(float red, float green, float blue, float alpha). 缺省的red,green,blue为1,代表白色. Smooth coloring (平滑颜色过渡) 当给每个顶点定义一个颜色时,OpenGL自动为不同顶点颜色之间生成中间过渡颜色(

微信小程序开发的入门教程

广州微信小程序开发公司(www.dthulian.com)品向科技,下面跟大家详细介绍微信小程序开发的入门教程: 开发前准备: 注册小程序帐号 绑定开发者 登录微信公众平台小程序,进入用户身份- 开发者,新增绑定开发者. 已认证的小程序可以绑定不多于20个开发者.未认证的小程序可以绑定不多于10个开发者. 获取AppID下载并安装开发者工具 下载完成后,使用管理员或者绑定的开发者微信号扫码登录. 一个微信小程序 创建项目 我们需要通过开发者工具,来完成小程序创建和代码编辑. 开发者工具安装完成后

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

游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的视图 q  蓝牙无线连接: q  用于游戏控制的按键:双摇杆(analog joystick).方向按键(D-pad).8个动作按钮(action button).1个系统按键(system button)和1个触控板(touchpad): q  2个5号电池: 提示:游戏控制杆中,电池的安装位置在