ReactiveCocoa入门教程:第二部分

翻译自:http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2

原文链接:

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中添加方法如下:

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

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

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

然后,我们导入头文件:

#import <ReactiveCocoa.h>

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

[[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实例,它允许你通过处置方法手动的移除订阅。下面是一个使用当前管道的快速的例子。

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

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

[[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,当前的代码可以这样写:

 

__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框架包含了一个小诀窍,你可以使用它代替上百年的代码。添加下面的引用:

#import "RACEXTScope.h"

@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中导入以下文件即可:

#import <Accounts/Accounts.h>
#import <Social/Social.h>

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

typedef NS_ENUM(NSInteger, RWTwitterInstantError) {
    RWTwitterInstantErrorAccessDenied,
    RWTwitterInstantErrorNoTwitterAccounts,
    RWTwitterInstantErrorInvalidResponse
};

static NSString * const RWTwitterInstantDomain = @"TwitterInstant";

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

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

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

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

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

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

- (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<RACSubscriber> 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后面添加如下代码;

[[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后面的管道:

[[[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:

[[[[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请求方法。在当前的控制器中,添加如下方法:

- (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;
}

下一步就是创建一个基于request的信号量。添加如下方法:这创建了一个请求:搜索Twitter(V.1.1REST API)。这个是调用Twitter的api。

- (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<RACSubscriber> 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方法中进一步添加信号量:

[[[[[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,我们发现如果想加载图片的话必须在主线程里面,但是他不在主线程中,所以我们就可以做如下操作:

[[[[[[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中导入:

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

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

[[[[[[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中添加如下方法:

-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {

  RACScheduler *scheduler = [RACScheduler
                         schedulerWithPriority:RACSchedulerPriorityBackground];

  return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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:方法里面添加:

cell.twitterAvatarView.image = nil;

[[[self signalForLoadingImage:tweet.profileImageUrl]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(UIImage *image) {
   cell.twitterAvatarView.image = image;
  }];

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

 

Throttling(限流)

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

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

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

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

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

 

Wrap Up

现在我们知道ReactiveCocoa是多么的优雅。

时间: 2024-11-05 12:08:34

ReactiveCocoa入门教程:第二部分的相关文章

学习正则表达式30分钟入门教程(第二版)_正则表达式

由于本人内容过多排版比较乱,推荐大家浏览单独网页版 http://www.jb51.net/tools/zhengze.html 本文目标 30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它. 如何使用本教程 最重要的是--请给我30分钟,如果你没有使用正则表达式的经验,请不要试图在30秒内入门--除非你是超人 :) 别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并没有你想像中的那么困难.当然,如果你看完了这篇教程之后,发现

Docker安装和基础用法 Docker入门教程第二篇_docker

本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 (4)Docker 容器的隔离性 - 使用 cgroups 限制容器使用的资源 (5)Docker 网络 1. 安装 1.1 在 Ubuntu 14.04 上安装 Docker 前提要求: 内核版本必须是3.10或者以上 依次执行下面的步骤: sudo apt-get update sudo apt

SEO实战入门教程:元标签的详细介绍(上)

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 在上节教程<SEO实战入门教程:网站结构>中我们有粗略的提到网站的"元标签",从文中我们基本了解了什么是元标签以及元标签的位置.但是对于元标签的作用以及优化方式没有涉及到,那么这节教程我们就来详细的分析下"元标签".(上节中将title标签定义到元标签中,从网页设计的角度来讲是不科学的,但是

ArcGIS for Desktop入门教程_第二章_Desktop简介 - ArcGIS知乎-新一代ArcGIS问答社区

原文:ArcGIS for Desktop入门教程_第二章_Desktop简介 - ArcGIS知乎-新一代ArcGIS问答社区 1 Desktop简介1.1 ArcGIS for Desktop ArcGIS for Desktop是ArcGIS产品线上的桌面端软件产品,为GIS专业人士提供的信息制作和使用的工具.利用ArcGIS for Desktop,你可以实现任何从简单到复杂的GIS任务,包括制图,地理分析,数据编辑,数据管理,可视化和空间处理等.它可以作为三个独立的软件产品购买,每个产

php中的curl使用入门教程和常见用法实例

[目录] php中的curl使用入门教程和常见用法实例 一.curl的优势 二.curl的简单使用步骤 三.错误处理 四.获取curl请求的具体信息 五.使用curl发送post请求 六.文件上传 七.文件下载 八.http 验证 九.通过代理发送请求 十.发送json数据 十一.cURL批处理(multi cURL) 十二.总结 起先cURL是做为一种命令行工具设计出来的,比较幸运的是,php也支持cURL了.通过cURL这个利器,我们能在php程序中自由地发送 HTTP请求到某个url来获取

Div+CSS布局入门教程(四) -- 用好border和clear

clear|css|教程|入门教程 四.页面制作(1)----用好border和clear 由于找工作找房子的原因,隔了这么久才能开始写教程,心里感觉很对不起一直在关注本站的朋友,今天是找到房子的第二天,于是赶快继续写教程. 这一节里面,主要就是想告诉大家如何使用好border和clear这两个属性. 首先,如果你曾用过table制作网页,你就应该知道,如果要在表格中绘制一条虚线该如何做,那需要制作一个很小的图片来填充,其实我们还有更简单的办法,只要在<td></td>中加入这么一

XML入门教程:XML 解析器

xml|教程|入门教程 如需读取.更新.创建或者操作某个XML文档,则需要XML解析器. 实例 解析XML文件 - 跨浏览器的实例 本例是一个跨浏览器的实例,把某个XML文档("note.xml")载入XML解析器. <html><body><script type="text/vbscript">set xmlDoc=CreateObject("Microsoft.XMLDOM")xmlDoc.async=&

XML新手入门教程:了解学习 XML 属性

xml|教程|入门教程 与HTML类似,XML元素也可以在开始标签中包含属性. 属性被用于提供关于元素的附加信息. XML 属性 XML元素可拥有属性. 回忆一下HTML的这个标签:<IMG SRC="http://www.webjx.com/htmldata/2007-06-20/computer.gif">.SRC属性提供了关于img元素的附加信息. 在HTML(以及XML)中,属性可提供有关元素的附加(额外的)信息: <img src="http://

XML入门教程:掌握学习 XML 语法规则

xml|教程|入门教程|语法 XML的语法规则既很简单,又很严格.这些规则很容易学习,也很容易使用. 正因为如此,创建可读取及操作XML的软件不是一件难事. 一个XML文档的例子 XML使用一种可自我描述的简单的语法. <?xml version="1.0" encoding="ISO-8859-1"?><note><to>Tove</to><from>Jani</from><headin