日期: 2021 年 7 月 12 日

RxJava系列 过滤操作符

前面一篇文章中我们介绍了转换类操作符,那么这一章我们就来介绍下过滤类的操作符。顾名思义,这类operators主要用于对事件数据的筛选过滤,只返回满足我们条件的数据。过滤类操作符主要包含: FilterTake TakeLast TakeUntil Skip SkipLast ElementAt Debounce Distinct DistinctUntilChanged First Last等等。

Filter

filter(Func1)用来过滤观测序列中我们不想要的值,只返回满足条件的值,我们看下原理图:

%title插图%num
filter(Func1)

还是拿前面文章中的小区Community[] communities来举例,假设我需要赛选出所有房源数大于10个的小区,我们可以这样实现:

Observable.from(communities)
        .filter(new Func1<Community, Boolean>() {
            @Override
            public Boolean call(Community community) {
                return community.houses.size()>10;
            }
        }).subscribe(new Action1<Community>() {
    @Override
    public void call(Community community) {
        System.out.println(community.name);
    }
});

Take

take(int)用一个整数n作为一个参数,从原始的序列中发射前n个元素.

%title插图%num
take(int)

现在我们需要取小区列表communities中的前10个小区

Observable.from(communities)
        .take(10)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                System.out.println(community.name);
            }
        });

TakeLast

takeLast(int)同样用一个整数n作为参数,只不过它发射的是观测序列中后n个元素。

%title插图%num
takeLast(int)

获取小区列表communities中的后3个小区

Observable.from(communities)
        .takeLast(3)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                System.out.println(community.name);
            }
        });

TakeUntil

takeUntil(Observable)订阅并开始发射原始Observable,同时监视我们提供的第二个Observable。如果第二个Observable发射了一项数据或者发射了一个终止通知,takeUntil()返回的Observable会停止发射原始Observable并终止。

%title插图%num
takeUntil(Observable)
Observable<Long> observableA = Observable.interval(300, TimeUnit.MILLISECONDS);
Observable<Long> observableB = Observable.interval(800, TimeUnit.MILLISECONDS);

observableA.takeUntil(observableB)
        .subscribe(new Subscriber<Long>() {
            @Override
            public void onCompleted() {
                System.exit(0);
            }
            @Override
            public void onError(Throwable e) {

            }
            @Override
            public void onNext(Long aLong) {
                System.out.println(aLong);
            }
        });

try {
    Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
    e.printStackTrace();
}

程序输出:

0
1

takeUntil(Func1)通过Func1中的call方法来判断是否需要终止发射数据。

%title插图%num
takeUntil(Func1)
Observable.just(1, 2, 3, 4, 5, 6, 7)
                .takeUntil(new Func1<Integer, Boolean>() {
                    @Override
                    public Boolean call(Integer integer) {
                        return integer >= 5;
                    }
                }).subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                System.out.println(integer);
            }
        });

程序输出:

1
2
3
4
5

Skip

skip(int)让我们可以忽略Observable发射的前n项数据。

%title插图%num
skip(int)

过滤掉小区列表communities中的前5个小区

Observable.from(communities)
        .skip(5)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                System.out.println(community.name);
            }
        });

SkipLast

skipLast(int)忽略Observable发射的后n项数据。

%title插图%num
skipLast(int)

ElementAt

elementAt(int)用来获取元素Observable发射的事件序列中的第n项数据,并当做唯一的数据发射出去。

%title插图%num
elementAt(int)

Debounce

debounce(long, TimeUnit)过滤掉了由Observable发射的速率过快的数据;如果在一个指定的时间间隔过去了仍旧没有发射一个,那么它将发射*后的那个。通常我们用来结合RxBing(Jake Wharton大神使用RxJava封装的Android UI组件)使用,防止button重复点击。

%title插图%num
debounce(long, TimeUnit)

debounce(Func1)可以根据Func1的call方法中的函数来过滤,Func1中的中的call方法返回了一个临时的Observable,如果原始的Observable在发射一个新的数据时,上一个数据根据Func1的call方法生成的临时Observable还没结束,那么上一个数据就会被过滤掉。

%title插图%num
debounce(Func1)

Distinct

distinct()的过滤规则是只允许还没有发射过的数据通过,所有重复的数据项都只会发射一次。

%title插图%num
distinct()

过滤掉一段数字中的重复项:

Observable.just(2, 1, 2, 2, 3, 4, 3, 4, 5, 5)
        .distinct()
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer i) {
                System.out.print(i + " ");
            }
        });

程序输出:

2 1 3 4 5 

distinct(Func1)参数中的Func1中的call方法会根据Observable发射的值生成一个Key,然后比较这个key来判断两个数据是不是相同;如果判定为重复则会和distinct()一样过滤掉重复的数据项。

%title插图%num
distinct(Func1)

假设我们要过滤掉一堆房源中小区名重复的小区:

List<House> houses = new ArrayList<>();
//House构造函数中的*个参数为该房源所属小区名,第二个参数为房源描述
List<House> houses = new ArrayList<>();
houses.add(new House("中粮·海景壹号", "中粮海景壹号新出大平层!总价4500W起"));
houses.add(new House("竹园新村", "满五唯一,黄金地段"));
houses.add(new House("竹园新村", "一楼自带小花园"));
houses.add(new House("中粮·海景壹号", "毗邻汤臣一品"));
houses.add(new House("中粮·海景壹号", "顶级住宅,给您总统般尊贵体验"));
houses.add(new House("竹园新村", "顶层户型,两室一厅"));
houses.add(new House("中粮·海景壹号", "南北通透,豪华五房"));
Observable.from(houses)
        .distinct(new Func1<House, String>() {

            @Override
            public String call(House house) {
                return house.communityName;
            }
        }).subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
            }
        });            

程序输出:

小区:中粮·海景壹号; 房源描述:中粮海景壹号新出大平层!总价4500W起
小区:竹园新村; 房源描述:满五唯一,黄金地段

DistinctUntilChanged

distinctUntilChanged()distinct()类似,只不过它判定的是Observable发射的当前数据项和前一个数据项是否相同。

%title插图%num
distinctUntilChanged()

同样还是上面过滤数字的例子:

Observable.just(2, 1, 2, 2, 3, 4, 3, 4, 5, 5)
        .distinctUntilChanged()
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer i) {
                System.out.print(i + " ");
            }
        });

程序输出:

2 1 2 3 4 3 4 5 

distinctUntilChanged(Func1)distinct(Func1)一样,根据Func1中call方法产生一个Key来判断两个相邻的数据项是否相同。

%title插图%num
distinctUntilChanged(Func1)

我们还是拿前面的过滤房源的例子:

Observable.from(houses)
        .distinctUntilChanged(new Func1<House, String>() {

            @Override
            public String call(House house) {
                return house.communityName;
            }
        }).subscribe(new Action1<House>() {
    @Override
    public void call(House house) {
        System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
    }
});

程序输出:

小区:中粮·海景壹号; 房源描述:中粮海景壹号新出大平层!总价4500W起
小区:竹园新村; 房源描述:满五唯一,黄金地段
小区:中粮·海景壹号; 房源描述:毗邻汤臣一品
小区:竹园新村; 房源描述:顶层户型,两室一厅
小区:中粮·海景壹号; 房源描述:南北通透,豪华五房

First

first()顾名思义,它是的Observable只发送观测序列中的*个数据项。

%title插图%num
first()

获取房源列表houses中的*套房源:

Observable.from(houses)
        .first()
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
            }                
        });

程序输出:

小区:中粮·海景壹号; 房源描述:中粮海景壹号新出大平层!总价4500W起

first(Func1)只发送符合条件的*个数据项。

%title插图%num
first(Func1)

现在我们要获取房源列表houses中小区名为竹园新村的*套房源。

Observable.from(houses)
        .first(new Func1<House, Boolean>() {
            @Override
            public Boolean call(House house) {
                return "竹园新村".equals(house.communityName);
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
            }
        });

程序输出:

小区:竹园新村; 房源描述:满五唯一,黄金地段

Last

last()只发射观测序列中的*后一个数据项。

%title插图%num
last()

获取房源列表中的*后一套房源:

Observable.from(houses)
        .last()
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
            }
        });

程序输出:

小区:中粮·海景壹号; 房源描述:南北通透,豪华五房

last(Func1)只发射观测序列中符合条件的*后一个数据项。

%title插图%num
last(Func1)

获取房源列表houses中小区名为竹园新村的*后一套房源:

Observable.from(houses)
        .last(new Func1<House, Boolean>() {
            @Override
            public Boolean call(House house) {
                return "竹园新村".equals(house.communityName);
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:" + house.communityName + "; 房源描述:" + house.desc);
            }
        });

程序输出:

小区:竹园新村; 房源描述:顶层户型,两室一厅

这一章我们就先聊到这,更多的过滤类操作符的介绍大家可以去查阅官方文档和源码;在下一章我们将继续介绍组合类操作符。

RxJava系列 转换操作符

前面两篇文章中我们介绍了RxJava的一些基本概念和RxJava*简单的用法。从这一章开始,我们开始聊聊RxJava中的操作符Operators,RxJava中的操作符主要分成了三类:

  1. 转换类操作符(map flatMap concatMap flatMapIterable switchMap scan groupBy …);
  2. 过滤类操作符(fileter take takeLast takeUntil distinct distinctUntilChanged skip skipLast …);
  3. 组合类操作符(merge zip join combineLatest and/when/then switch startSwitch …)。

这一章我们主要讲讲转换类操作符。所有这些Operators都作用于一个可观测序列,然后变换它发射的值,*后用一种新的形式返回它们。概念实在是不好理解,下面我们结合实际的例子一一介绍。

map

map()函数接受一个Func1类型的参数(就像这样map(Func1<? super T, ? extends R> func)),然后吧这个Func1应用到每一个由Observable发射的值上,将发射的只转换为我们期望的值。这种狗屁定义我相信你也听不懂,我们来看一下官方给出的原理图:

%title插图%num
MapOperator

假设我们需要将一组数字装换成字符串,我们可以通过map这样实现:

Observable.just(1, 2, 3, 4, 5)
        .map(new Func1<Integer, String>() {
            @Override
            public String call(Integer i) {
                return "This is " + i;
            }
        }).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                System.out.println(s);
            }
        });

Func1构造函数中的两个参数分别是Observable发射值当前的类型和map转换后的类型,上面这个例子中发射值当前的类型是Integer,转换后的类型是String。

flatMap

flatMap()函数同样也是做转换的,但是作用却不一样。flatMap不开好理解,我们直接看例子(我们公司是个房产平台,那我就拿房子举例):假设我们有一组小区Community[] communites,现在我们要输出每个小区的名字;我们可以这样实现:

Observable.from(communities)
        .map(new Func1<Community, String>() {
            @Override
            public String call(Community community) {
                return community.name;
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String name) {
                System.out.println("Community name : " + name);
            }
        });

现在我们需求有变化,需要打印出每个小区下面所有房子的价格。于是我可以这样实现:

Community[] communities = {};
Observable.from(communities)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                for (House house : community.houses) {
                    System.out.println("House price : " + house.price);
                }
            }
        });

如果我不想在Subscriber中使用for循环,而是希望Subscriber中直接传入单个的House对象呢(这对于代码复用很重要)?用map()显然是不行的,因为map()是一对一的转化,而我现在的要求是一对多的转化。那么我们可以使用flatMap()把一个Community转化成多个House。

Observable.from(communities)
        .flatMap(new Func1<Community, Observable<House>>() {
            @Override
            public Observable<House> call(Community community) {
                return Observable.from(community.houses);
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("House price : " + house.price);
            }
        });

从前面的例子中你坑定发现了,flatMap()和map()都是把传入的参数转化之后返回另一个对象。但和map()不同的是,flatMap()中返回的是Observable对象,并且这个Observable对象并不是被直接发送到 Subscriber的回调方法中。

flatMap()的原理是这样的:

  1. 将传入的事件对象装换成一个Observable对象;
  2. 这是不会直接发送这个Observable, 而是将这个Observable激活让它自己开始发送事件;
  3. 每一个创建出来的Observable发送的事件,都被汇入同一个Observable,这个Observable负责将这些事件统一交给Subscriber的回调方法。

这三个步骤,把事件拆成了两级,通过一组新创建的Observable将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是flatMap()所谓的flat。

*后我们来看看flatMap的原理图:

%title插图%num
FlatMapOperator

concatMap

concatMap()解决了flatMap()的交叉问题,它能够把发射的值连续在一起,就像这样:

%title插图%num
ConcatMapOperator

flatMapIterable

flatMapIterable()flatMap()几乎是一样的,不同的是flatMapIterable()它转化的多个Observable是使用Iterable作为源数据的。

%title插图%num
FlatMapIterableOperator
Observable.from(communities)
        .flatMapIterable(new Func1<Community, Iterable<House>>() {
            @Override
            public Iterable<House> call(Community community) {
                return community.houses;
            }
        })
        .subscribe(new Action1<House>() {

            @Override
            public void call(House house) {

            }
        });

switchMap

switchMap()flatMap()很像,除了一点:每当源Observable发射一个新的数据项(Observable)时,它将取消订阅并停止监视之前那个数据项产生的Observable,并开始监视当前发射的这一个。

%title插图%num
SwitchMapOperator

scan

scan()对一个序列的数据应用一个函数,并将这个函数的结果发射出去作为下个数据应用合格函数时的*个参数使用。

%title插图%num
ScanOperator

我们来看个简单的例子:

Observable.just(1, 2, 3, 4, 5)
        .scan(new Func2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }).subscribe(new Action1<Integer>() {
    @Override
    public void call(Integer integer) {
        System.out.print(integer+“ ”);
    }
});

输出结果为:1 3 6 10 15

groupBy

groupBy()将原始Observable发射的数据按照key来拆分成一些小的Observable,然后这些小Observable分别发射其所包含的的数据,和SQL中的groupBy类似。实际使用中,我们需要提供一个生成key的规则(也就是Func1中的call方法),所有key相同的数据会包含在同一个小的Observable中。另外我们还可以提供一个函数来对这些数据进行转化,有点类似于集成了flatMap。

%title插图%num
GroupByOperator

单纯的文字描述和图片解释可能难以理解,我们来看个例子:假设我现在有一组房源List<House> houses,每套房子都属于某一个小区,现在我们需要根据小区名来对房源进行分类,然后依次将房源信息输出。

List<House> houses = new ArrayList<>();
houses.add(new House("中粮·海景壹号", "中粮海景壹号新出大平层!总价4500W起"));
houses.add(new House("竹园新村", "满五唯一,黄金地段"));
houses.add(new House("中粮·海景壹号", "毗邻汤臣一品"));
houses.add(new House("竹园新村", "顶层户型,两室一厅"));
houses.add(new House("中粮·海景壹号", "南北通透,豪华五房"));
Observable<GroupedObservable<String, House>> groupByCommunityNameObservable = Observable.from(houses)
        .groupBy(new Func1<House, String>() {

            @Override
            public String call(House house) {
                return house.communityName;
            }
        });

通过上面的代码我们创建了一个新的Observable:groupByCommunityNameObservable,它将会发送一个带有GroupedObservable的序列(也就是指发送的数据项的类型为GroupedObservable)。GroupedObservable是一个特殊的Observable,它基于一个分组的key,在这个例子中的key就是小区名。现在我们需要将分类后的房源依次输出:

Observable.concat(groupByCommunityNameObservable)
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                System.out.println("小区:"+house.communityName+"; 房源描述:"+house.desc);
            }
        });

执行结果:

小区:中粮·海景壹号; 房源描述:中粮海景壹号新出大平层!总价4500W起
小区:中粮·海景壹号; 房源描述:毗邻汤臣一品
小区:中粮·海景壹号; 房源描述:南北通透,豪华五房
小区:竹园新村; 房源描述:满五唯一,黄金地段
小区:竹园新村; 房源描述:顶层户型,两室一厅

转换类的操作符就先介绍到这,后续还会继续介绍组合、过滤类的操作符及源码分析,敬请期待!

Objective-c与javascript交互

在写 JavaScript 的时候,可以使用一个叫做 window 的对象,像是我们想要从现在的网页跳到另外一个网页的时候,就会去修改 window.location.href 的位置;在我们的 Objective-C 程序码中,如果我们可以取得指定的 WebView 的指标,也就可以拿到这个出现在 JavaScript 中的 window 对象,也就是 [webView windowScriptObject]。

这个对象就是 WebView 里头的 JS 与我们的 Objective-C程序之间的桥樑-window 对象可以取得网页里头所有的 JS 函数与对象,而如果我们把一个 Objective-C 对象设定成 windowScriptObject 的 value,JS 也便可以调用Objective-C对象的 method。于是,我们可以在Objective-C 程序里头要求 WebView 执行一段 JS,也可以反过来让 JS 调用一段用 Obj C 实作的功能。

※ 用Objective-C 取得与设定JavaScript 对象

要从 Objective-C取得网页中的 JavaScript 对象,也就是对 windowScriptObject 做一些 KVC 调用,像是 valueForKey: 与 valueForKeyPath:。如果我们在 JS 里头,想要知道目前的网页位置,会这么写:

var location = window.location.href;

用 Objective-C 就可以这么调用:

NSString *location = [[webView windowScriptObject] valueForKeyPath:@”location.href”];

如果我们要设定 window.location.href,要求开启另外一个网页,在 JS 里头:

window.location.href = ‘http://spring-studio.net’;

在Objective-C:

[[webView windowScriptObject] setValue:@”http://spring-studio.net”forKeyPath:@”location.href”];

由于Objective-C 与 JS 本身的语言特性不同,在两种语言之间相互传递东西之间,就可以看到两者的差别:

JS 虽然是 OO,但是并没有 class,所以将 JS 对象传到 Obj C 程序里头,除了基本字串会转换成 NSString、基本数字会转成 NSNumber,像是 Array 等其他对象,在 Objective-C 中,都是 WebScriptObject 这个 Class。意思就是,JS 的 Array 不会帮你转换成 NSArray。
从 JS 里头传一个空对象给 Objective-C 程序,用的不是 Objective-C 里头原本表示「没有东西」的方式,像是 NULL、nil、NSNull 等,而是专属 WebKit 使用的 WebUndefined。

所以,如果我们想要看一个 JS Array 里头有什麽东西,就要先取得这个对象里头叫做 length 的 value,然后用 webScriptValueAtIndex: 去看在该 index 位置的内容。

假如我们在 JS 里头这样写:

var JSArray = {‘zonble’, ‘dot’, ‘net’};

for (var i = 0; i < JSArray.length; i++) {
console.log(JSArray[i]);

}

在Objective-C 里头就会变成这样:

WebScriptObject *obj = (WebScriptObject *)JSArray;

NSUInteger count = [[obj valueForKey:@”length”] integerValue];

NSMutableArray *a = [NSMutableArray array];

for (NSUInteger i = 0; i < count; i++) {
NSString *item = [obj webScriptValueAtIndex:i];

NSLog(@”item:%@”, item);

}

※ 用Objective C 调用 JavaScript function

要用 Objective-C 调用网页中的 JS function,大概有几种方法。*种是直接写一段跟你在网页中会撰写的 JS 一模一样的程序,叫 windowScriptObject 用 evaluateWebScript: 执行。

例如,我们想要在网页中产生一个新的 JS function,内容是:

function x(x) {
return x + 1;

}

所以在 Objective-C 中可以这样写;

[[webView windowScriptObject] evaluateWebScript:@”function x(x) { return x + 1;}”];

接下来我们就可以调用 window.x():

NSNumber *result = [[webView windowScriptObject] evaluateWebScript:@”x(1)”];

NSLog(@”result:%d”, [result integerValue]); // Returns 2

由于在 JS 中,每个 funciton 其实都是对象,所以我们还可以直接取得 window.x 叫这个对象执行自己。

在 JS 里头如果这样写:

window.x.call(window.x, 1);

Objective-C 中便是这样:

WebScriptObject *x = [[webView windowScriptObject] valueForKey:@”x”];

NSNumber *result = [x callWebScriptMethod:@”call”withArguments:[NSArray arrayWithObjects:x, [NSNumbernumberWithInt:1], nil]];

这种让某个 WebScriptObject 自己执行自己的写法,其实比较不会用于从 Objective-C 调用 JS 这一端,而是接下来会提到的,由 JS 调用 Objective-C,因为这样 JS 就可以把一个 callback function 送到 Objective-C 程序里头。

如果我们在做网页,我们只想要更新网页中的一个区块,就会利用 AJAX 的技巧,只对这个区块需要的资料,对 server 发出 request,并且在 request 完成的时候,要求执行一段 callback function,更新这一个区块的显示内容。从 JS 调用 Objective-C也可以做类似的事情,如果 Objective-C程序里头需要一定时间的运算,或是我们可能是在 Objective-C 里头抓取网路资料,我们便可以把一个 callback function 送到 Objective-C程序里,要求Objective-C程序在做完工作后,执行这段 callback function。

※DOM

WebKit 里头,所有的 DOM 对象都继承自 DOMObject,DOMObject 又继承自 WebScriptObject,所以我们在取得了某个 DOM 对象之后,也可以从 Objective-C 程序中,要求这个 DOM 对象执行 JS 程序。

假如我们的网页中,有一个 id 叫做 “#s” 的文字输入框(text input),而我们希望现在键盘输入的焦点放在这个输入框上,在 JS 里头会这样写:

document.querySelector(‘#s’).focus();

在Objective-C中写法:

DOMDocument *document = [[webView mainFrame] DOMDocument];

[[document querySelector:@”#s”] callWebScriptMethod:@”focus”withArguments:nil];

※ 用 JavaScript 存取 Objective C 的 Value

要让网页中的 JS 程序可以调用 Objective-C 对象,方法是把某个 Objective-C 对象注册成 JS 中 window 对象的属性。之后,JS 便也可以调用这个对象的 method,也可以取得这个对象的各种 Value,只要是 KVC 可以取得的 Value,像是 NSString、NSNumber、NSDate、NSArray、NSDictionary、NSValue…等。JS 传 Array 到 Objective-C 时,还需要特别做些处理才能变成 NSArray,从 Obj C 传一个 NSArray 到 JS 时,会自动变成 JS Array。

首先我们要注意的是将 Objective-C 对象注册给 window 对象的时机,由于每次重新载入网页,window 对象的内容都会有所变动-毕竟每个网页都会有不同的 JS 程序,所以,我们需要在适当的时机做这件事情。我们首先要指定 WebView 的 frame loading delegate(用 setFrameLoadDelegate:),并且实作 webView:didClearWindowObject:forFrame:,WebView 只要更新了 windowScriptObject,就会调用这一段程序。

假如我们现在要让网页中的 JS 可以使用目前的 controller 对象,会这样写:

– (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame

{
[windowObject setValue:self forKey:@”controller”];

}

如此一来,只要调用 window.controller,就可以调用我们的 Objective-C 对象。假如我们的 Objective-C Class 里头有这些成员变数:

@interface MyController : NSObject

{
IBOutlet WebView *webView;

IBOUtlet NSWindow *window;

NSString *stringValue;

NSInteger numberValue;

NSArray *arrayValue;

NSDate *dateValue;

NSDictionary *dictValue;

NSRect frameValue;

}

@end

指定一下 Value:

stringValue = @”string”;

numberValue = 24;

arrayValue = [[NSArray arrayWithObjects:@”text”, [NSNumbernumberWithInt:30], nil] retain];

dateValue = [[NSDate date] retain];

dictValue = [[NSDictionary dictionaryWithObjectsAndKeys:@”value1″,@”key1″, @”value2″, @”key2″, @”value3″, @”key3″, nil] retain];

frameValue = [window frame];

用 JS 读读看:

var c = window.controller;

var main = document.getElementById(‘main’);

var HTML = ”;

if (c) {
HTML += ‘<p>’ + c.stringValue + ‘<p>’;

HTML += ‘<p>’ + c.numberValue + ‘<p>’;

HTML += ‘<p>’ + c.arrayValue + ‘<p>’;

HTML += ‘<p>’ + c.dateValue + ‘<p>’;

HTML += ‘<p>’ + c.dictValue + ‘<p>’;

HTML += ‘<p>’ + c.frameValue + ‘<p>’;

main.innerHTML = HTML;

}

结果如下:

string 24 text,30 2010-09-09 00:01:04 +0800 { key1 = value1; key2 = value2; key3 = value3; } NSRect: {{275, 72}, {570, 657}}

不过,如果你看完上面的范例,就直接照做,应该不会直接成功出现正确的结果,而是会拿到一堆 undefined,原因是,Objective-C 对象的 Value 预设被保护起来,不会让 JS 直接存取。要让 JS 可以存取 Objective-C 对象的 Value,需要操作 +isKeyExcludedFromWebScript: 针对传入的 Key 一一处理,如果我们希望 JS 可以存取这个 key,就回传 NO:

+ (BOOL)isKeyExcludedFromWebScript:(const char *)name

{
if (!strcmp(name, “stringValue”)) {
return NO;

}

return YES;

}

除了可以读取 Objective-C对象的 Value 外,也可以设定 Value,相当于在 Objective-C中使用 setValue:forKey:,如果在上面的 JS 程序中,我们想要修改 stringValue,直接调用 c.stringValue = ‘new value’ 即可。像前面提到,在这裡传给 Objective-C的 JS 对象,除了字串与数字外,class 都是 WebScriptObject,空对象是 WebUndefined。

※用 JavaScript调用 Objective C method

Objective-C 的语法沿袭自 SmallTalk,Objective-C 的 selector,与 JS 的 function 语法有相当的差异。WebKit 预设的实事是,如果我们要在 JS 调用 Objective-C selector,就是把所有的参数往后面摆,并且把所有的冒号改成底线,而原来 selector 如果有底线的话,又要另外处理。

假使我们的 controller 对象有个 method,在 Objective-C 中写成这样:

– (void)setA:(id)a b:(id)b c:(id)c;

在 JS 中就这么调用:

controller.setA_b_c_(‘a’, ‘b’, ‘c’);

实在有点丑。所以 WebKit 提供一个方法,可以让我们把某个 Objective-C selector 变成好看一点的 JS function。我们要实作 webScriptNameForSelector:

+ (NSString *)webScriptNameForSelector:(SEL)selector

{
if (selector == @selector(setA:b:c:)) {
return @”setABC”;

}

return nil;

}

以后就可以这么调用:

controller.setABC(‘a’, ‘b’, ‘c’);

我们同样可以决定哪些 selector 可以给 JS 使用,哪些要保护起来,方法是实作 isSelectorExcludedFromWebScript:。而我们可以改变某个 Objective-C selector 在 JS 中的名称,我们也可以改变某个 value 的 key,方法是实作 webScriptNameForKey:。

有几件事情需要注意一下:

用 JavaScript 调用 Objective C 2.0 的 property

在上面,我们用 JS 调用 window.controller.stringValue,与设定里头的 value 时,这边很像我们使用 Objective-C 2.0 的语法,但其实做的是不一样的事情。用 JS 调用 controller.stringValue,对应到的 Objective-C 语法是 [controller valueForKey:@”stringValue”],而不是调用 Objective-C 对象的 property。

如果我们的 Objective-C 对象有个 property 叫做 stringValue,我们知道,Objective-C property 其实会在编译时,变成 getter/setter method,在 JS 里头,我们便应该要调用 controller.stringValue() 与 controller.setStringValue_()。

Javascript 中,Function 即对象的特性

JS 的 function 是对象,当一个 Objective-C 对象的 method 出现在 JS 中时,这个 method 在 JS 中,也可以或多或少当做对象处理。我们在上面产生了 setABC,也可以试试看把它倒出来瞧瞧:

console.log(controller.setABC);

我们可以从结果看到:

function setABC() { [native code] }

这个 function 是 native code。因为是 native code,所以我们无法对这个 function 调用 call 或是 apply。

另外,在把我们的 Objective-C 对象注册成 window.controller 后,我们会许也会想要让 controller 变成一个 function 来执行,像是调用 window.controller();或是,我们就只想要产生一个可以让 JS 调用的 function,而不是整个对象都放进 JS 里头。我们只要在 Objective-C 对象中,实作 invokeDefaultMethodWithArguments:,就可以回传在调用 window.controller() 时想要的结果。

现在我们可以综合练习一下。前面提到,由于我们可以把 JS 对象以 WebScriptObject 这个 class 传入 Obj C 程序,Objective-C 程序中也可以要求执行 WebScriptObject 的各项 function。我们假如想把 A 与 B 两个数字丢进 Objective-C 程序里头做个加法,加完之后出现在网页上,于是我们写了一个 Objective-C method:

– (void)numberWithA:(id)a plusB:(id)b callback:(id)callback

{
NSInteger result = [a integerValue] + [b integerValue];

[callback callWebScriptMethod:@”call” withArguments:[NSArrayarrayWithObjects:callback, [NSNumber numberWithInteger:result],nil]];

}

JS 里头就可以这样调用:

window.controller.numberWithA_plusB_callback_(1, 2,function(result) {
var main = document.getElementById(‘main’);

main.innerText = result;

});

※其他平台上 WebKit的用法

除了 Mac OS X,WebKit 这几年也慢慢移植到其他的作业系统与 framework 中,也或多或少都有 Native API 要求 WebView 执行 Js,以及从 JS 调用 Native API 的机制。

跟 Mac OS X 比较起来,IOS 上 UIWebView 的公开 API 实在少上许多。想要让 UIWebView 执行一段 JS,可以透过调用 stringByEvaluatingJavaScriptFromString:,只会回传字串结果,所以能够做到的事情也就变得有限,通常大概就拿来取得像 window.title 这些资讯。在 IOS 上我们没办法将某个 Objective-C 对象变成 JS 对象,所以,在网页中触发了某些事件,想要通知 Objective-C 这一端,往往会选择使用像「zonble://」这类 Customized URL scheme。

ChromeOS 完全以 WebKit 製作使用者介面,不过我们没办法在 ChomeOS 上写我们在这边所讨论的桌面或行动应用程序,所以不在我们讨论之列。(顺道岔题,ChromeOS 是设计来给 Netbook 使用的作业系统,可是像 Toshiba 都已经用 Android,做出比 Netbook 更小的 Smartbook,而且应用程序更多,ChromeOS 的产品做出来的话,实在很像 Google 拿出两套东西,自己跟自己对打)。

Android 的 WebView 对象提供一个叫做 addJavascriptInterface() 的 method,可以将某个 Java 对象注册成 JS 的 window 对象的某个属性,就可以让 JS 调用 Java 对象。不过,在调用 Java 对象时,只能够传递简单的文字、数字,複杂的 JS 对象就没办法了。而在 Android 上想要 WebView 执行一段 JS,在文件中没看到相关资料,网路上面找到的说法是,可以透过 loadUrl(),把某段 JS 用 bookmarklet 的形式传进去。

在 QtWebKit 里头,可以对 QWebFrame 调用 addToJavaScriptWindowObject,把某个 QObject 暴露在 JS 环境中,我不清楚 JS 可以传递哪些东西到 QObject 里头就是了。在 QtWebKit 中也可以取得网页里头的 DOM 对象(QWebElement
、QWebElementCollection),我们可以对 QWebFrame 还有这些 DOM 对象调用 evaluateJavaScript,执行 Javascript。

GTK 方面,因为是 C API,所以在应用程序与 JS 之间,就不是透过操作包装好的对象,而是调用 WebKit 里头 JavaScript Engine 的 C API。

※ JavaScriptCore Framework

我们在 Mac OS X 上面,也可以透过 C API,要求 WebView 执行 Javascript。首先要 import 。如果我们想要简单改一下 window.location.href:

JSGlobalContextRef globalContext = [[webView mainFrame] globalContext];

JSValueRef exception = NULL;

JSStringRef script = JSStringCreateWithUTF8CString(“window.location.href=’http://spring-studio.net'”);

JSEvaluateScript(globalContext, script, NULL, NULL, 0, &exception);

JSStringRelease(script);

如果我们想要让 WebView 里头的 JS,可以调用我们的 C Function:

– (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame

{
JSGlobalContextRef globalContext = [frame globalContext];

JSStringRef name = JSStringCreateWithUTF8CString(“myFunc”);

JSObjectRef obj = JSObjectMakeFunctionWithCallback(globalContext, name, (JSObjectCallAsFunctionCallback)myFunc);

JSObjectSetProperty (globalContext, [windowObject JSObject], name, obj, 0, NULL);

JSStringRelease(name);

}

那麽,只要 JS 调用 window.myFunc(),就可以取得们放在 myFunc 这个 C function 中回传的结果:

JSValueRef myFunc(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)

{
return JSValueMakeNumber(ctx, 42);

}

iOS———-常用三方库

1.笔者常用三方库

名称 作用 说明
<small>AFNetworking <small>基于HTTP/HTTPS 联网请求 <small>
<small>SDWebImage <small>图片异步加载和缓存 <small> image图像没做压缩处理
<small> FMDB <small>SQLite数据库处理 <small>将xcode嵌入的数据库api进行封装
<small>MJRefresh <small>上拉刷新,触底加载 <small>用于数据分页再次加载时(多用于tableView)
<small>MBProgressBUD <small>提示框 <small>支持多种动画(推荐)
<small> Masonry <small>自动布局(autoLayout) <small>
<small> SDCycleScrollView <small>轮播图 <small> SD大神不知道是谁,写了很多有用三方库
<small> YYModel <small>JSON-模型对象转换(非侵入性) <small> 90后大神写神作(YYKIT)
<small> MJExtesion <small>JSON-模型对象转换(非侵入性) <small> MJ用运行时写的
<small> FSCalendar <small>日历控件 <small>
<small> JT3DScrollView <small>滚动视图动画效果 <small>
<small> poping <small>动画效果 <small>一位大神根据facebook动画开源后写的
<small> MWPhotoBrowser <small>图片浏览器 <small>
<small> SDPhotoBrowser <small>图片浏览器 <small>
<small> TZImagePickerController <small>图片选择器(仿微信) <small>
<small> MMDrawerController <small>侧滑栏 <small>
<small> JSAnimatedImagesView <small>图片背景渐变效果 <small>
<small> RPSlidingMenu <small>UltraVisual 风格的集合视图菜单 <small>
<small> PYSearch <small>优雅的搜索控制器 <small>

2.笔者常用三方api接口

名称 作用 说明
<small> SMSDK <small>免费短信验证 <small>地址:http://www.mob.com/
<small>ShareSDK <small>社会化分享 <small>地址:http://www.mob.com/
<small>友盟(umeng ) <small>社会化分享 <small>地址:http://www.umeng.com/
<small>BaiduMapKit(百度地图) <small>地图api <small>地址:http://lbsyun.baidu.com/
<small>*光推送(GTSDK) <small>推送 <small>地址:http://www.getui.com/
<small> 支付宝 <small>支付 <small>地址:https://openhome.alipay.com
<small> 微信 <small>支付 <small>地址:https://pay.weixin.qq.com
<small> 融云 <small>IM即时通讯 <small>地址:http://www.rongcloud.cn/

以上是我开发中用到的三方库和三方api接口,有什么不对的地方希望大家指出来,下面是没用过搜集的

3.笔者搜集的三方库(API)

名称 作用 说明
<small> 高德地图 <small>地图 <small>地址:http://lbs.amap.com/
<small> 环信 <small>即时通讯 <small>地址:http://www.easemob.com/
<small> ZXing Objc <small>二维码 <small>
<small> 讯飞 <small>语音识别/合成等 <small>地址:http://www.xfyun.cn/
<small> GPUImage <small>图片滤镜 <small>
<small> FXBlurView <small>模糊 <small>
<small> StreamingKit <small>流媒体 <small>
<small> FreeStreamer <small>流媒体 <small>
<small> DOUAudioStreamer <small>流媒体 <small>
<small> SVProgressHUD <small>提示框 <small>
<small> KVNProgressHUD <small>提示框 <small>
<small> IQKeyboardManager <small>键盘管理 <small>
<small> YYKeyboardManager <small>键盘管理 <small>
<small> ZSSRichTextEditor <small>富文本编辑 <small>
<small> TMCache <small>缓存 <small>
<small> CocoaAsyncSocket <small>异步套接字 <small>
<small> MGTemplateEngine <small>模板引擎 <small>
<small> GRMustache <small>模板引擎 <small>
<small> sskeychain <small>钥匙串 <small>
<small> RESideMenu <small>侧滑栏 <small>
<small> ReactiveCocoa <small>响应式编程 <small>
<small> WebViewJavaScriptBridge <small>Objective-C和JavaScript混编 <small>
<small> NJKWebViewProgress <small>UIWebView进度条 <small>
<small> LTNavigationBar <small>导航栏定制 <small>
<small> FoldingTabBar.iOS <small>可折叠分栏条 <small>
<small> UITableView-FDTemplateLayoutCell <small>表格视图单元格自适应 <small>
<small> PNChart <small>统计图表 <small>
<small> Specta <small>TDD/BDD <small>
<small> Kiwi <small>TDD/BDD <small>
<small> iCarousel <small>轮播 <small>
<small> MagicalRecord <small>简化CoreData <small>
<small> CocoaLumberjack <small>RESTful客户端 <small>
<small> RestKit <small>日志 <small>
<small> FlatUIKit <small> 扁平化UI <small>
<small> Realm <small>移动数据库 <small>

4.YYKIT工具库

名称 作用 说明
<small> YYModel <small>高性能的 iOS JSON模型框架 <small>
<small> YYCache <small>高性能的 iOS 缓存框架 <small>
<small> YYImage <small>强大的 iOS 图像框架 <small>
<small> YYWebImage <small>高性能的 iOS 异步图像加载框架 <small>
<small> YYText <small>功能强大的 iOS 富文本框架 <small>
<small> YYKeyboardManager <small>iOS 键盘监听管理工具 <small>
<small> YYDispatchQueuePool <small>iOS 全局并发队列管理工具 <small>
<small> YYAsyncLayer <small>iOS 异步绘制与显示的工具 <small>
<small>YYCategories <small>功能丰富的 Category 类型工具库。 <small>

以上三方库基本在GitHub都能找到,*好是通过cocopods来管理

iOS开发笔记 – Objective-C和JavaScript的混编

*近看了一个对Github上面编程语言使用统计的排行榜,JavaScript真可以说是一枝独秀,很难想象20年前,这个语言只是浏览器中的装饰性语言,能做的事情也就是一点特效或者检查一下要提交给服务器的表单是否满足要求。今天的JavaScript已经是一个全栈语言,从客户端到服务器无所不在。很多编程语言都提供了跟JavaScript进行交互的接口,这一点在iOS开发中也不例外。
iOS7以前,在App中调用JavaScript的方式只有一种,就是通过UIWebView对象的stringByEvaluatingJavaScriptFromString:方法。由于UIWebView中包含了CSS渲染引擎和JavaScript执行引擎(说白了就是微型一个浏览器),因此这个方法可以让UIWebView通过它的JavaScript运行时环境执行JavaScript代码,但是能做的事情非常有限,我们可以先看看下面的例子。

– (void)webViewDidFinishLoad:(UIWebView *)webView {
// 获得UIWebView中加载页面的标题
NSString *title = [webView stringByEvaluatingJavaScriptFromString:
@”document.title”];
NSLog(@”%@”, title);
// 获得UIWebView中加载页面的链接地址
NSString *urlStr = [webView stringByEvaluatingJavaScriptFromString:
@”location.href”];
NSLog(@”%@”, urlStr);
}

从iOS7开始,我们可以使用JavaScriptCore框架来让我们的Objective-C代码和JavaScript进行深度交互,简单的说我们可以在Objective-C代码中访问JavaScript中的变量或调用JavaScript的函数,也可以JavaScript中使用Objective-C的对象和方法。我们可以先看一个简单的例子。

先加入JavaScriptCore的头文件。

#import <JavaScriptCore/JavaScriptCore.h>
1
在Objective-C中使用JavaScript的正则表达式验证字符串。

// 创建JavaScript执行环境(上下文)
JSContext *context = [[JSContext alloc] init];
NSString *funCode =
@”var isValidNumber = function(phone) {”
” var phonePattern = /^1[34578]\\d{9}$/;”
” return phone.match(phonePattern);”
“};”;
// 执行上面的JavaScript代码
[context evaluateScript:funCode];
// 获得isValidNumber函数并传参调用
JSValue *jsFunction = context[@”isValidNumber”];
JSValue *value1 = [jsFunction callWithArguments:@[ @”13012345678″ ]];
NSLog(@”%@”, [value1 toBool]? @”有效”: @”无效”); // 有效
JSValue *value2 = [jsFunction callWithArguments:@[ @”12345678899″ ]];
NSLog(@”%@”, [value2 toBool]? @”有效”: @”无效”); // 无效

在Objective-C中调用JavaScript函数求阶乘。

// 创建JavaScript执行环境(上下文)
JSContext *context = [[JSContext alloc] init];
// 可以将一个block传给JavaScript上下文
// 它会被转换成一个JavaScript中的函数
context[@”factorial”] = ^(int x) {
double result = 1.0;
for (; x > 1; x–) {
result *= x;
}
return result;
};
// 执行求阶乘的函数
[context evaluateScript:@”var num = factorial(5);”];
JSValue *num = context[@”num”];
NSLog(@”5! = %@”, num); // 5! = 120

 

JavaScript和Objective-C中类型的对应关系如下表所示:

Objective-C类型 JavaScript类型
nil undefined
NSNull null
NSString string
NSNumber number, boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock Function object
id Wrapper object
Class Constructor object

再来看一个例子。我们在根视图控制器中放置一个按钮,点击后会导航到下一个视图控制器,其中有一个UIWebView加载了一个网页,页面中有一颗按钮,我们希望点击按钮后导航到上一个视图控制器,要做到这一点,就需要在JavaScript中访问Objective-C对象和方法。

页面的代码:

<!doctype html>
<html>
<head>
<meta charset=”utf-8″ />
<title>测试页面</title>
<style type=”text/css”>
#backButton {
display: inline-block;
width:50px; height:30px;
}
</style>
</head>

<body>
<button id=”backButton”>返回</button>
<script type=”text/javascript”>
var btn = document.getElementById(“backButton”);
var cb = function() {
window.alert(‘Hello’);
};
btn.addEventListener(‘click’, cb, false);
</script>
</body>
</html>

 

第二个视图控制器的代码:

#import “ViewController.h”
#import <JavaScriptCore/JavaScriptCore.h>

@protocol MyProtocol <JSExport>

– (void) letsGoBack;

@end

@interface SecondViewController : ViewController <MyProtocol>

@end

 

@interface SecondViewController () <UIWebViewDelegate>

@property (weak, nonatomic) IBOutlet UIWebView *myWebViw;

@end

@implementation SecondViewController

– (void)viewDidLoad {
[super viewDidLoad];

[_myWebViw loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:
@”http://localhost:8080/myweb/test.html”]]];
_myWebViw.delegate = self;
}

– (void)webViewDidFinishLoad:(UIWebView *)webView {
// 通过UIWebView获得网页中的JavaScript执行环境
JSContext *context = [webView valueForKeyPath:
@”documentView.webView.mainFrame.javaScriptContext”];
// 设置处理异常的block回调
[context setExceptionHandler:^(JSContext *ctx, JSValue *value) {
NSLog(@”error: %@”, value);
}];

context[@”callBackObj”] = self;
// 下面的代码移除了按钮原先绑定的事件回调重新绑定返回上一个视图控制器的代码
NSString *code =
@”var btn = document.getElementById(‘backButton’);”
“btn.removeEventListener(‘click’, cb);”
“btn.addEventListener(‘click’, function() {”
” callBackObj.letsGoBack();”
“});”;
[context evaluateScript:code];
}

// 实现协议中的方法
– (void) letsGoBack {
// 必须回到主线程刷新用户界面
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}

@end

可以在Github上下载到上面例子的完整代码。

ios中objective-c与js的交互

iOS中js与objective-c的交互
因为在iOS中没有WebKit.Framework这个库的,所以也就没有 windowScriptObject对象方法了。要是有这个的方法的话
就方便多了,(ps:MacOS中有貌似)
现在我们利用其他方法去达到js与objective-c的交互效果。
首先是objective-c调用js中的代码,可以用uiwebview中的一个方法
stringByEvaluatingJavaScriptFromString:后面接的是js中的方法名。这个函数的返回值就是所调用js方法
的返回值。
而在js调用objective-c的方法就没那么简单了,
在js中的代码应该这么做:
function testFunc(cmd,parameter1)
{
alert(1);
document.write(Date());
document.location=”objc://”+cmd+”:/”+parameter1; //cmd代表objective-
c中的的方法名,parameter1自然就是参数了
}

而在objective-c中,也是利用uiwebview的一个方法,
– (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:
(NSURLRequest*)request navigationType:
(UIWebViewNavigationType)navigationType //这个方法是网页中的每一个请求都会被触发的
{
NSString *urlString = [[request URL] absoluteString];
NSArray *urlComps = [urlString
componentsSeparatedByString:@”://”];

if([urlComps count] && [[urlComps objectAtIndex:0]
isEqualToString:@”objc”])
{
NSArray *arrFucnameAndParameter = [(NSString*)[urlComps
objectAtIndex:1] componentsSeparatedByString:@”:/”];
NSString *funcStr = [arrFucnameAndParameter objectAtIndex:0];
if (1 == [arrFucnameAndParameter count])
{
// 没有参数
if([funcStr isEqualToString:@”doFunc1″])
{
/*调用本地函数1*/
NSLog(@”doFunc1″);
}
}
else if(2 == [arrFucnameAndParameter count])
{
//有参数的
if([funcStr isEqualToString:@”doFunc1″] &&
[arrFucnameAndParameter objectAtIndex:1])
{
/*调用本地函数1*/
NSLog(@”doFunc1:parameter”);
}
}
return NO;
};
return YES;
}

这个方法是可以达到js调用本地objective-c的方法,可惜没办法把返回值返回给js,所以还是要绕过
stringByEvaluatingJavaScriptFromString:这个弯,用
stringByEvaluatingJavaScriptFromString:

函数去掉js的方法,把返回值当做js方法中的参数形式传回去给
js。这个可以有,呵呵~

[iOS] 使用UIWebView时objective-c与javascript互相调用 1

在写 JavaScript 的时候,可以使用一个叫做 window 的对象,像是我们想要从现在的网页跳到另外一个网页的时候,就会去修改 window.location.href 的位置;在我们的 Objective C 程序码中,如果我们可以取得指定的 WebView 的指标,也就可以拿到这个出现在 JavaScript 中的 window 对象,也就是 [webView windowScriptObject]。

这个对象就是 WebView 里头的 JS 与我们的 Obj C 程序之间的桥樑-window 对象可以取得网页里头所有的 JS 函数与对象,而如果我们把一个 Obj C 对象设定成 windowScriptObject 的 value,JS 也便可以调用 Obj C 对象的 method。于是,我们可以在 Obj C 程序里头要求 WebView 执行一段 JS,也可以反过来让 JS 调用一段用 Obj C 实作的功能。

※ 用 Objective C 取得与设定 JavaScript 对象

要从 Obj C 取得网页中的 JavaScript 对象,也就是对 windowScriptObject 做一些 KVC 调用,像是 valueForKey: 与 valueForKeyPath:。如果我们在 JS 里头,想要知道目前的网页位置,会这麽写:

1
varlocation = window.location.href;

用 ObjC 就可以这麽调用:

1
  1. NSString*location = [[webView windowScriptObject] valueForKeyPath:@“location.href”];

如果我们要设定 window.location.href,要求开启另外一个网页,在 JS 里头:

1
  1. window.location.href =‘http://spring-studio.net’;

Obj C:

1
[[webView windowScriptObject] setValue:@"http://spring-studio.net"forKeyPath:@"location.href"];

由于 Obj C 与 JS 本身的语言特性不同,在两种语言之间相互传递东西之间,就可以看到两者的差别-

  • JS 虽然是 OO,但是并没有 class,所以将 JS 对象传到 Obj C 程序里头,除了基本字串会转换成 NSString、基本数字会转成 NSNumber,像是 Array 等其他对象,在 Obj C 中,都是 WebScriptObject 这个 Class。意思就是,JS 的 Array 不会帮你转换成 NSArray。
  • 从 JS 里头传一个空对象给 Obj C 程序,用的不是 Obj C 里头原本表示「没有东西」的方式,像是 NULL、nil、NSNull 等,而是专属 WebKit 使用的 WebUndefined。

所以,如果我们想要看一个 JS Array 里头有什麽东西,就要先取得这个对象里头叫做 length 的 value,然后用 webScriptValueAtIndex: 去看在该 index 位置的内容。假如我们在 JS 里头这样写:

1
2
3
4
  1. varJSArray = {‘zonble’,‘dot’,‘net’};
  2. for(vari = 0; i < JSArray.length; i++) {
  3. console.log(JSArray[i]);
  4. }

Obj C 里头就会变成这样:

1
2
3
4
5
6
7
8
  1. WebScriptObject *obj = (WebScriptObject *)JSArray;
  2. NSUIntegercount = [[obj valueForKey:@“length”] integerValue];
  3. NSMutableArray*a = [NSMutableArrayarray];
  4. for(NSUIntegeri = 0; i < count; i++) {
  5. NSString*item = [obj webScriptValueAtIndex:i];
  6. NSLog(@“item:%@”, item);
  7. }

※ 用 Objective C 调用 JavaScript function

要用 Obj C 调用网页中的 JS function,大概有几种方法。*种是直接写一段跟你在网页中会撰写的 JS 一模一样的程序,叫 windowScriptObject 用 evaluateWebScript: 执行。例如,我们想要在网页中产生一个新的 JS function,内容是:

1
2
3
  1. functionx(x) {
  2. returnx + 1;
  3. }

所以在 Obj C 中可以这样写;

1
  1. [[webView windowScriptObject] evaluateWebScript:@“function x(x) { return x + 1;}”];

接下来我们就可以调用 window.x():

1
2
  1. NSNumber*result = [[webView windowScriptObject] evaluateWebScript:@“x(1)”];
  2. NSLog(@“result:%d”, [result integerValue]);// Returns 2

由于在 JS 中,每个 funciton 其实都是对象,所以我们还可以直接取得 window.x 叫这个对象执行自己。在 JS 里头如果这样写:

1
  1. window.x.call(window.x, 1);

Obj C 中便是这样:

1
2
  1. WebScriptObject *x = [[webView windowScriptObject] valueForKey:@“x”];
  2. NSNumber*result = [x callWebScriptMethod:@“call”withArguments:[NSArrayarrayWithObjects:x, [NSNumbernumberWithInt:1],nil]];

这种让某个 WebScriptObject 自己执行自己的写法,其实比较不会用于从 Obj C 调用 JS 这一端,而是接下来会提到的,由 JS 调用 Obj C,因为这样 JS 就可以把一个 callback function 送到 Obj C 程序里头。

如果我们在做网页,我们只想要更新网页中的一个区块,就会利用 AJAX 的技巧,只对这个区块需要的资料,对 server 发出 request,并且在 request 完成的时候,要求执行一段 callback function,更新这一个区块的显示内容。从 JS 调用 Obj C也可以做类似的事情,如果 Obj C 程序里头需要一定时间的运算,或是我们可能是在 Obj C 里头抓取网路资料,我们便可以把一个 callback function 送到 Obj C 程序裡,要求 Obj C 程序在做完工作后,执行这段 callback function。

※ DOM

WebKit 里头,所有的 DOM 对象都继承自 DOMObject,DOMObject 又继承自 WebScriptObject,所以我们在取得了某个 DOM 对象之后,也可以从 Obj C 程序中,要求这个 DOM 对象执行 JS 程序。

假如我们的网页中,有一个 id 叫做 “#s” 的文字输入框(text input),而我们希望现在键盘输入的焦点放在这个输入框上,在 JS 里头会这样写:

1
document.querySelector('#s').focus();

Obj C:

1
2
  1. DOMDocument *document = [[webView mainFrame] DOMDocument];
  2. [[document querySelector:@“#s”] callWebScriptMethod:@“focus”withArguments:nil];

※ 用 JavaScript 存取 Objective C 的 Value

要让网页中的 JS 程序可以调用 Obj C 对象,方法是把某个 Obj C 对象注册成 JS 中 window 对象的属性。之后,JS 便也可以调用这个对象的 method,也可以取得这个对象的各种 Value,只要是 KVC 可以取得的 Value,像是 NSString、NSNumber、NSDate、NSArray、NSDictionary、NSValue…等。JS 传 Array 到 ObjC 时,还需要特别做些处理才能变成 NSArray,从 Obj C 传一个 NSArray 到 JS 时,会自动变成 JS Array。

首先我们要注意的是将 Obj C 对象注册给 window 对象的时机,由于每次重新载入网页,window 对象的内容都会有所变动-毕竟每个网页都会有不同的 JS 程序,所以,我们需要在适当的时机做这件事情。我们首先要指定 WebView 的 frame loading delegate(用 setFrameLoadDelegate:),并且实作 webView:didClearWindowObject:forFrame:,WebView 只要更新了 windowScriptObject,就会调用这一段程序。假如我们现在要让网页中的 JS 可以使用目前的 controller 对象,会这样写:

1
2
3
4
  1. – (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame
  2. {
  3. [windowObject setValue:selfforKey:@“controller”];
  4. }

如此一来,只要调用 window.controller,就可以调用我们的 Obj C 对象。假如我们的 Obj C Class 里头有这些成员变数:

01
02
03
04
05
06
07
08
09
10
11
12
13
  1. @interfaceMyController :NSObject
  2. {
  3. IBOutletWebView *webView;
  4. IBOUtlet NSWindow*window;
  5. NSString*stringValue;
  6. NSIntegernumberValue;
  7. NSArray*arrayValue;
  8. NSDate*dateValue;
  9. NSDictionary*dictValue;
  10. NSRectframeValue;
  11. }
  12. @end

指定一下 Value:

1
2
3
4
5
6
  1. stringValue =@“string”;
  2. numberValue = 24;
  3. arrayValue = [[NSArrayarrayWithObjects:@“text”, [NSNumbernumberWithInt:30],nil] retain];
  4. dateValue = [[NSDatedate] retain];
  5. dictValue = [[NSDictionarydictionaryWithObjectsAndKeys:@“value1”,@“key1”,@“value2”,@“key2”,@“value3”,@“key3”,nil] retain];
  6. frameValue = [window frame];

用 JS 读读看:

01
02
03
04
05
06
07
08
09
10
11
12
  1. varc = window.controller;
  2. varmain = document.getElementById(‘main’);
  3. varHTML =”;
  4. if(c) {
  5. HTML +=’<p>‘+ c.stringValue +’<p>‘;
  6. HTML +=’<p>‘+ c.numberValue +’<p>‘;
  7. HTML +=’<p>‘+ c.arrayValue +’<p>‘;
  8. HTML +=’<p>‘+ c.dateValue +’<p>‘;
  9. HTML +=’<p>‘+ c.dictValue +’<p>‘;
  10. HTML +=’<p>‘+ c.frameValue +’<p>‘;
  11. main.innerHTML = HTML;
  12. }

结果如下:

string24text,302010-09-09 00:01:04 +0800{ key1 = value1; key2 = value2; key3 = value3; }NSRect: {{275, 72}, {570, 657}}

不过,如果你看完上面的范例,就直接照做,应该不会直接成功出现正确的结果,而是会拿到一堆 undefined,原因是,Obj C 对象的 Value 预设被保护起来,不会让 JS 直接存取。要让 JS 可以存取 Obj C 对象的 Value,需要实作 +isKeyExcludedFromWebScript: 针对传入的 Key 一一处理,如果我们希望 JS 可以存取这个 key,就回传 NO:

1
2
3
4
5
6
7
  1. + (BOOL)isKeyExcludedFromWebScript:(constchar*)name
  2. {
  3. if(!strcmp(name,“stringValue”)) {
  4. returnNO;
  5. }
  6. returnYES;
  7. }

除了可以读取 Obj C 对象的 Value 外,也可以设定 Value,相当于在 Obj C 中使用 setValue:forKey:,如果在上面的 JS 程序中,我们想要修改 stringValue,直接调用 c.stringValue = ‘new value’ 即可。像前面提到,在这裡传给 Obj C 的 JS 对象,除了字串与数字外,class 都是 WebScriptObject,空对象是 WebUndefined。

※ 用 JavaScript 调用 Objective C method

Obj C 的语法沿袭自 SmallTalk,Obj C 的 selector,与 JS 的 function 语法有相当的差异。WebKit 预设的实作是,如果我们要在 JS 调用 Obj C selector,就是把所有的参数往后面摆,并且把所有的冒号改成底线,而原来 selector 如果有底线的话,又要另外处理。假使我们的 controller 对象有个 method,在 Obj C 中写成这样:

1
  1. – (void)setA:(id)a b:(id)b c:(id)c;

在 JS 中就这麽调用:

  1. controller.setA_b_c_(‘a’,‘b’,‘c’);
1
controller.setA_b_c_(‘a’,’b’,’c’);

实在有点丑。所以 WebKit 提供一个方法,可以让我们把某个 Obj C selector 变成好看一点的 JS function。我们要实作 webScriptNameForSelector:

1
2
3
4
5
6
7
  1. + (NSString*)webScriptNameForSelector:(SEL)selector
  2. {
  3. if(selector ==@selector(setA:b:c:)) {
  4. return@“setABC”;
  5. }
  6. returnnil;
  7. }

以后就可以这麽调用:

1
controller.setABC('a','b','c');

我们同样可以决定哪些 selector 可以给 JS 使用,哪些要保护起来,方法是实作 isSelectorExcludedFromWebScript:。而我们可以改变某个 Obj C selector 在 JS 中的名称,我们也可以改变某个 value 的 key,方法是实作 webScriptNameForKey:。

有几件事情需要注意一下:

用 JavaScript 调用 Objective C 2.0 的 property

在上面,我们用 JS 调用 window.controller.stringValue,与设定里头的 value 时,这边很像我们使用 Obj C 2.0 的语法,但其实做的是不一样的事情。用 JS 调用 controller.stringValue,对应到的 Obj C 语法是 [controller valueForKey:@”stringValue”],而不是调用 Obj C 对象的 property。

如果我们的 Obj C 对象有个 property 叫做 stringValue,我们知道,Obj C property 其实会在编译时,变成 getter/setter method,在 JS 里头,我们便应该要调用 controller.stringValue() 与 controller.setStringValue_()。

Javascript 中,Function 即对象的特性

JS 的 function 是对象,当一个 Obj C 对象的 method 出现在 JS 中时,这个 method 在 JS 中,也可以或多或少当做对象处理。我们在上面产生了 setABC,也可以试试看把它倒出来瞧瞧:

1
console.log(controller.setABC);

我们可以从结果看到:

function setABC() { [native code] }

这个 function 是 native code。因为是 native code,所以我们无法对这个 function 调用 call 或是 apply。

另外,在把我们的 Obj C 对象注册成 window.controller 后,我们会许也会想要让 controller 变成一个 function 来执行,像是调用 window.controller();或是,我们就只想要产生一个可以让 JS 调用的 function,而不是整个对象都放进 JS 里头。我们只要在 Obj C 对象中,实作 invokeDefaultMethodWithArguments:,就可以回传在调用 window.controller() 时想要的结果。

现在我们可以综合练习一下。前面提到,由于我们可以把 JS 对象以 WebScriptObject 这个 class 传入 Obj C 程序,Obj C 程序中也可以要求执行 WebScriptObject 的各项 function。我们假如想把 A 与 B 两个数字丢进 Obj C 程序里头做个加法,加完之后出现在网页上,于是我们写了一个 Obj C method:

1
2
3
4
5
  1. – (void)numberWithA:(id)a plusB:(id)b callback:(id)callback
  2. {
  3. NSIntegerresult = [a integerValue] + [b integerValue];
  4. [callback callWebScriptMethod:@“call”withArguments:[NSArrayarrayWithObjects:callback, [NSNumbernumberWithInteger:result],nil]];
  5. }

JS 里头就可以这样调用:

1
2
3
4
  1. window.controller.numberWithA_plusB_callback_(12,function(result) {
  2. varmain = document.getElementById(‘main’);
  3. main.innerText = result;
  4. });

※ 其他平台上 WebKit 的实作

除了 Mac OS X,WebKit 这几年也慢慢移植到其他的作业系统与 framework 中,也或多或少都有 Native API 要求 WebView 执行 Js,以及从 JS 调用 Native API 的机制。

跟 Mac OS X 比较起来,iPhone 上 UIWebView 的公开 API 实在少上许多。想要让 UIWebView 执行一段 JS,可以透过调用 stringByEvaluatingJavaScriptFromString:,只会回传字串结果,所以能够做到的事情也就变得有限,通常大概就拿来取得像 window.title 这些资讯。在 iPhone 上我们没办法将某个 Obj C 对象变成 JS 对象,所以,在网页中触发了某些事件,想要通知 Obj C 这一端,往往会选择使用像「zonble://」这类 Customized URL scheme。

ChromeOS 完全以 WebKit 製作使用者介面,不过我们没办法在 ChomeOS 上写我们在这边所讨论的桌面或行动应用程序,所以不在我们讨论之列。(顺道岔题,ChromeOS 是设计来给 Netbook 使用的作业系统,可是像 Toshiba 都已经用 Android,做出比 Netbook 更小的 Smartbook,而且应用程序更多,ChromeOS 的产品做出来的话,实在很像 Google 拿出两套东西,自己跟自己对打)。

Android 的 WebView 对象提供一个叫做 addJavascriptInterface() 的 method,可以将某个 Java 对象注册成 JS 的 window 对象的某个属性,就可以让 JS 调用 Java 对象。不过,在调用 Java 对象时,只能够传递简单的文字、数字,複杂的 JS 对象就没办法了。而在 Android 上想要 WebView 执行一段 JS,在文件中没看到相关资料,网路上面找到的说法是,可以透过 loadUrl(),把某段 JS 用 bookmarklet 的形式传进去。

在 QtWebKit 里头,可以对 QWebFrame 调用 addToJavaScriptWindowObject,把某个 QObject 暴露在 JS 环境中,我不清楚 JS 可以传递哪些东西到 QObject 里头就是了。在 QtWebKit 中也可以取得网页里头的 DOM 对象(QWebElement
、QWebElementCollection),我们可以对 QWebFrame 还有这些 DOM 对象调用 evaluateJavaScript,执行 Javascript。

GTK 方面,因为是 C API,所以在应用程序与 JS 之间,就不是透过操作包装好的对象,而是调用 WebKit 里头 JavaScript Engine 的 C API。

※ JavaScriptCore Framework

我们在 Mac OS X 上面,也可以透过 C API,要求 WebView 执行 Javascript。首先要 import 。如果我们想要简单改一下 window.location.href:

1
2
3
4
5
  1. JSGlobalContextRef globalContext = [[webView mainFrame] globalContext];
  2. JSValueRef exception = NULL;
  3. JSStringRef script = JSStringCreateWithUTF8CString(“window.location.href=’http://spring-studio.net'”);
  4. JSEvaluateScript(globalContext, script, NULLNULL0, &exception);
  5. JSStringRelease(script);

如果我们想要让 WebView 里头的 JS,可以调用我们的 C Function:

1
2
3
4
5
6
7
8
  1. – (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame
  2. {
  3. JSGlobalContextRef globalContext = [frame globalContext];
  4. JSStringRef name = JSStringCreateWithUTF8CString(“myFunc”);
  5. JSObjectRef obj = JSObjectMakeFunctionWithCallback(globalContext, name, (JSObjectCallAsFunctionCallback)myFunc);
  6. JSObjectSetProperty (globalContext, [windowObject JSObject], name, obj, 0,NULL);
  7. JSStringRelease(name);
  8. }

那麽,只要 JS 调用 window.myFunc(),就可以取得们放在 myFunc 这个 C function 中回传的结果:

1
2
3
4
  1. JSValueRef myFunc(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,size_targumentCount,constJSValueRef arguments[], JSValueRef* exception)
  2. {
  3. returnJSValueMakeNumber(ctx, 42);
  4. }

RxJava系列 基本概念及使用介绍

前言

上一篇的示例代码中大家一定发现了Observable这个类。从纯Java的观点看,Observable类源自于经典的观察者模式。RxJava的异步实现正是基于观察者模式来实现的,而且是一种扩展的观察者模式。

观察者模式

观察者模式基于Subject这个概念,Subject是一种特殊对象,又叫做主题或者被观察者。当它改变时那些由它保存的一系列对象将会得到通知,而这一系列对象被称作Observer(观察者)。它们会对外暴漏了一个通知方法(比方说update之类的),当Subject状态发生变化时会调用的这个方法。

观察者模式很适合下面这些场景中的任何一个:

  1. 当你的架构有两个实体类,一个依赖另一个,你想让它们互不影响或者是独立复用它们时。
  2. 当一个变化的对象通知那些与它自身变化相关联的未知数量的对象时。
  3. 当一个变化的对象通知那些无需推断具体类型的对象时。

通常一个观察者模式的类图是这样的:

%title插图%num
Observer

如果你对观察者模式不是很了解,那么强烈建议你先去学习下。关于观察者模式的详细介绍可以参考我之前的文章:设计模式之观察者模式

扩展的观察者模式

在RxJava中主要有4个角色:

  • Observable
  • Subject
  • Observer
  • Subscriber

Observable和Subject是两个“生产”实体,Observer和Subscriber是两个“消费”实体。说直白点Observable对应于观察者模式中的被观察者,而ObserverSubscriber对应于观察者模式中的观察者Subscriber其实是一个实现了Observer的抽象类,后面我们分析源码的时候也会介绍到。Subject比较复杂,以后再分析。

上一篇文章中我们说到RxJava中有个关键概念:事件。观察者Observer和被观察者Observable通过subscribe()方法实现订阅关系。从而Observable 可以在需要的时候发出事件来通知Observer

RxJava如何使用

我自己在学习一种新技术的时候通常喜欢先去了解它是怎么用的,掌握了使用方法后再去深挖其原理。那么我们现在就来说说RxJava到底该怎么用。

*步:创建观察者Observer

Observer<Object> observer = new Observer<Object>() {

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onNext(Object s) {

    }
 };

这么简单,一个观察者Observer创建了!

大兄弟你等等…,你之前那篇观察者模式中不是说观察者只提供一个update方法的吗?这特么怎么有三个?!!

少年勿急,且听我慢慢道来。在普通的观察者模式中观察者一般只会提供一个update()方法用于被观察者的状态发生变化时,用于提供给被观察者调用。而在RxJava中的观察者Observer提供了:onNext()、 onCompleted()onError()三个方法。还记得吗?开篇我们讲过RxJava是基于一种扩展的观察这模式实现,这里多出的onCompleted和onError正是对观察者模式的扩展。ps:onNext就相当于普通观察者模式中的update

RxJava中添加了普通观察者模式缺失的三个功能:

  1. RxJava中规定当不再有新的事件发出时,可以调用onCompleted()方法作为标示;
  2. 当事件处理出现异常时框架自动触发onError()方法;
  3. 同时Observables支持链式调用,从而避免了回调嵌套的问题。

第二步:创建被观察者Observable

Observable.create()方法可以创建一个Observable,使用crate()创建Observable需要一个OnSubscribe对象,这个对象继承Action1。当观察者订阅我们的Observable时,它作为一个参数传入并执行call()函数。

Observable<Object> observable = Observable.create(new               Observable.OnSubscribe<Object>() {
    @Override
    public void call(Subscriber<? super Object> subscriber) {

    }
});

除了create(),just()和from()同样可以创建Observable。看看下面两个例子:

just(T...)将传入的参数依次发送

Observable observable = Observable.just("One", "Two", "Three");
//上面这行代码会依次调用
//onNext("One");
//onNext("Two");
//onNext("Three");
//onCompleted();

from(T[])/from(Iterable<? extends T>)将传入的数组或者Iterable拆分成Java对象依次发送

String[] parameters = {"One", "Two", "Three"};
Observable observable = Observable.from(parameters);
//上面这行代码会依次调用
//onNext("One");
//onNext("Two");
//onNext("Three");
//onCompleted();

第三步:被观察者Observable订阅观察者Observerps:你没看错,不同于普通的观察者模式,这里是被观察者订阅观察者

有了观察者和被观察者,我们就可以通过subscribe()来实现二者的订阅关系了。

observable.subscribe(observer);
%title插图%num
observable.subscribe(observer)

连在一起写就是这样:

Observable.create(new Observable.OnSubscribe<Integer>() {

    @Override
    public void call(Subscriber<? super Integer> subscriber) {
        for (int i = 0; i < 5; i++) {
            subscriber.onNext(i);
        }
        subscriber.onCompleted();
    }

}).subscribe(new Observer<Integer>() {

    @Override
    public void onCompleted() {
        System.out.println("onCompleted");
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("onError");
    }

    @Override
    public void onNext(Integer item) {
        System.out.println("Item is " + item);
    }
});

至此一个完整的RxJava调用就完成了。

兄台,你叨逼叨叨逼叨的说了一大堆,可是我没搞定你特么到底在干啥啊?!!不急,我现在就来告诉你们到底发生了什么。

首先我们使用Observable.create()创建了一个新的Observable<Integer>,并为create()方法传入了一个OnSubscribe,OnSubscribe中包含一个call()方法,一旦我们调用subscribe()订阅后就会自动触发call()方法。call()方法中的参数Subscriber其实就是subscribe()方法中的观察者Observer。我们在call()方法中调用了5次onNext()和1次onCompleted()方法。一套流程周下来以后输出结果就是下面这样的:

Item is 0
Item is 1
Item is 2
Item is 3
Item is 4
onCompleted

看到这里可能你又要说了,大兄弟你别唬我啊!OnSubscribe的call()方法中的参数Subscriber怎么就变成了subscribe()方法中的观察者Observer?!!!这俩儿货明明看起来就是两个不同的类啊。

我们先看看Subscriber这个类:

public abstract class Subscriber<T> implements Observer<T>, Subscription {
    
    ...
}

从源码中我们可以看到,Subscriber是Observer的一个抽象实现类,所以我首先可以肯定的是Subscriber和Observer类型是一致的。接着往下我们看看subscribe()这个方法:

public final Subscription subscribe(final Observer<? super T> observer) {

    //这里的if判断对于我们要分享的问题没有关联,可以先无视
    if (observer instanceof Subscriber) {
        return subscribe((Subscriber<? super T>)observer);
    }
    return subscribe(new Subscriber<T>() {

        @Override
        public void onCompleted() {
            observer.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            observer.onError(e);
        }

        @Override
        public void onNext(T t) {
            observer.onNext(t);
        }

    });
}

我们看到subscribe()方法内部首先将传进来的Observer做了一层代理,将它转换成了Subscriber。我们再看看这个方法内部的subscribe()方法:

public final Subscription subscribe(Subscriber<? super T> subscriber) {
    return Observable.subscribe(subscriber, this);
}

进一步往下追踪看看return后面这段代码到底做了什么。精简掉其他无关代码后的subscribe(subscriber, this)方法是这样的:

private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {

    subscriber.onStart();
    try {
        hook.onSubscribeStart(observable, observable.onSubscribe).call(subscriber);
        return hook.onSubscribeReturn(subscriber);
    } catch (Throwable e) {
        return Subscriptions.unsubscribed();
    }
}

我们重点看看hook.onSubscribeStart(observable, observable.onSubscribe).call(subscriber),前面这个hook.onSubscribeStart(observable, observable.onSubscribe)返回的是它自己括号内的第二个参数observable.onSubscribe,然后调用了它的call方法。而这个observable.onSubscribe正是create()方法中的Subscriber,这样整个流程就理顺了。看到这里是不是对RxJava的执行流程清晰了一点呢?这里也建议大家在学习新技术的时候多去翻一翻源码,知其然还要能知其所以然不是吗。

subscribe()的参数除了可以是Observer和Subscriber以外还可以是Action1、Action0;这是一种更简单的回调,只有一个call(T)方法;由于太简单这里就不做详细介绍了!

异步

上一篇文章中开篇就讲到RxJava就是来处理异步任务的。但是默认情况下我们在哪个线程调用subscribe()就在哪个线程生产事件,在哪个线程生产事件就在哪个线程消费事件。那怎么做到异步呢?RxJava为我们提供Scheduler用来做线程调度,我们来看看RxJava提供了哪些Scheduler。

%title插图%num

同时RxJava还为我们提供了subscribeOn()observeOn()两个方法来指定Observable和Observer运行的线程。

Observable.from(getCommunitiesFromServer())
            .flatMap(community -> Observable.from(community.houses))
            .filter(house -> house.price>=5000000).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(this::addHouseInformationToScreen);

上面这段代码大家应该有印象吧,没错正是我们上一篇文章中的例子。subscribeOn(Schedulers.io())指定了获取小区列表、处理房源信息等一系列事件都是在IO线程中运行,observeOn(AndroidSchedulers.mainThread())指定了在屏幕上展示房源的操作在UI线程执行。这就做到了在子线程获取房源,主线程展示房源。

好了,RxJava系列的入门内容我们就聊到这。下一篇我们再继续介绍更多的API以及它们内部的原理。

iOS在label中显示表情

iOS在UILabel、UITextFeild等中显示表情的方法,可以使用表情的编码方式来显示。如下

face.text = @”\ue415\ue056″; //其中\ue415,ue056就是表情对应的编码

iOS中表情的编码列表如下:

\ue415 \ue056 \ue057 \ue414 \ue405 \ue106 \ue418
\ue417 \ue40d \ue40a \ue404 \ue105 \ue409 \ue40e
\ue402 \ue108 \ue403 \ue058 \ue407 \ue401 \ue40f
\ue40b \ue406 \ue413 \ue411 \ue412 \ue410 \ue107
\ue059 \ue416 \ue408 \ue40c \ue11a \ue10c \ue32c
\ue32a \ue32d \ue328 \ue32b \ue022 \ue023 \ue327
\ue329 \ue32e \ue32f \ue335 \ue334 \ue021 \ue337
\ue020 \ue336 \ue13c \ue330 \ue331 \ue326 \ue03e
\ue11d \ue05a \ue00e \ue421 \ue420 \ue00d \ue010
\ue011 \ue41e \ue012 \ue422 \ue22e \ue22f \ue231
\ue230 \ue427 \ue41d \ue00f \ue41f \ue14c \ue201
\ue115 \ue428 \ue51f \ue429 \ue424 \ue423 \ue253
\ue426 \ue111 \ue425 \ue31e \ue31f \ue31d \ue001
\ue002 \ue005 \ue004 \ue51a \ue519 \ue518 \ue515
\ue516 \ue517 \ue51b \ue152 \ue04e \ue51c \ue51e
\ue11c \ue536 \ue003 \ue41c \ue41b \ue419 \ue41a
\ue04a \ue04b \ue049 \ue048 \ue04c \ue13d \ue443
\ue43e \ue04f \ue052 \ue053 \ue524 \ue52c \ue52a
\ue531 \ue050 \ue527 \ue051 \ue10b \ue52b \ue52f
\ue528 \ue01a \ue134 \ue530 \ue529 \ue526 \ue52d
\ue521 \ue523 \ue52e \ue055 \ue525 \ue10a \ue109
\ue522 \ue019 \ue054 \ue520 \ue306 \ue030 \ue304
\ue110 \ue032 \ue305 \ue303 \ue118 \ue447 \ue119
\ue307 \ue308 \ue444 \ue441
\ue436 \ue437 \ue438 \ue43a \ue439 \ue43b \ue117
\ue440 \ue442 \ue446 \ue445 \ue11b \ue448 \ue033
\ue112 \ue325 \ue312 \ue310 \ue126 \ue127 \ue008
\ue03d \ue00c \ue12a \ue00a \ue00b \ue009 \ue316
\ue129 \ue141 \ue142 \ue317 \ue128 \ue14b \ue211
\ue114 \ue145 \ue144 \ue03f \ue313 \ue116 \ue10f
\ue104 \ue103 \ue101 \ue102 \ue13f \ue140 \ue11f
\ue12f \ue031 \ue30e \ue311 \ue113 \ue30f \ue13b
\ue42b \ue42a \ue018 \ue016 \ue015 \ue014 \ue42c
\ue42d \ue017 \ue013 \ue20e \ue20c \ue20f \ue20d
\ue131 \ue12b \ue130 \ue12d \ue324 \ue301 \ue148
\ue502 \ue03c \ue30a \ue042 \ue040 \ue041 \ue12c
\ue007 \ue31a \ue13e \ue31b \ue006 \ue302 \ue319
\ue321 \ue322 \ue314 \ue503 \ue10e \ue318 \ue43c
\ue11e \ue323 \ue31c \ue034 \ue035 \ue045 \ue338
\ue047 \ue30c \ue044 \ue30b \ue043 \ue120 \ue33b
\ue33f \ue341 \ue34c \ue344 \ue342 \ue33d \ue33e
\ue340 \ue34d \ue339 \ue147 \ue343 \ue33c \ue33a
\ue43f \ue34b \ue046 \ue345 \ue346 \ue348 \ue347
\ue34a \ue349
\ue036 \ue157 \ue038 \ue153 \ue155 \ue14d \ue156
\ue501 \ue158 \ue43d \ue037 \ue504 \ue44a \ue146
\ue50a \ue505 \ue506 \ue122 \ue508 \ue509 \ue03b
\ue04d \ue449 \ue44b \ue51d \ue44c \ue124 \ue121
\ue433 \ue202 \ue135 \ue01c \ue01d \ue10d \ue136
\ue42e \ue01b \ue15a \ue159 \ue432 \ue430 \ue431
\ue42f \ue01e \ue039 \ue435 \ue01f \ue125 \ue03a
\ue14e \ue252 \ue137 \ue209 \ue154 \ue133 \ue150
\ue320 \ue123 \ue132 \ue143 \ue50b \ue514 \ue513
\ue50c \ue50d \ue511 \ue50f \ue512 \ue510 \ue50e
\ue21c \ue21d \ue21e \ue21f \ue220 \ue221 \ue222
\ue223 \ue224 \ue225 \ue210 \ue232 \ue233 \ue235
\ue234 \ue236 \ue237 \ue238 \ue239 \ue23b \ue23a
\ue23d \ue23c \ue24d \ue212 \ue24c \ue213 \ue214
\ue507 \ue203 \ue20b \ue22a \ue22b \ue226 \ue227
\ue22c \ue22d \ue215 \ue216 \ue217 \ue218 \ue228
\ue151 \ue138 \ue139 \ue13a \ue208 \ue14f \ue20a
\ue434 \ue309 \ue315 \ue30d \ue207 \ue229 \ue206
\ue205 \ue204 \ue12e \ue250 \ue251 \ue14a \ue149
\ue23f \ue240 \ue241 \ue242 \ue243 \ue244 \ue245
\ue246 \ue247 \ue248 \ue249 \ue24a \ue24b \ue23e
\ue532 \ue533 \ue534 \ue535 \ue21a \ue219 \ue21b
\ue02f \ue024 \ue025 \ue026 \ue027 \ue028 \ue029
\ue02a \ue02b \ue02c \ue02d \ue02e \ue332 \ue333
\ue24e \ue24f \ue537

 

IPhone开源代码 *对经典!

扫描wifi信息:

http://code.google.com/p/uwecaugmentedrealityproject/

http://code.google.com/p/iphone-wireless/

条形码扫描:

http://zbar.sourceforge.net/iphone/sdkdoc/install.html

tcp/ip的通讯协议:

http://code.google.com/p/cocoaasyncsocket/

voip/sip:

http://code.google.com/p/siphon/

http://code.google.com/p/asterisk-voicemail-for-iphone/

http://code.google.com/p/voiphone/

three20

https://github.com/facebook/three20

google gdata

http://code.google.com/p/gdata-objectivec-client/

720全景显示panoramagl

http://code.google.com/p/panoramagl/

jabber client

http://code.google.com/p/ichabber/

PLBlocks

http://code.google.com/p/plblocks/

image processing

http://code.google.com/p/simple-iphone-image-processing/

json编码解码: http://code.google.com/p/json-framework

base64编码解码: http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/?r=87

xml解析: https://github.com/schwa/TouchXML

安全保存用户密码到keychain中: https://github.com/ldandersen/scifihifi-iphone

加载等待特效框架(private api): https://github.com/jdg/MBProgressHUD

http等相关协议封装: http://allseeing-i.com/ASIHTTPRequest

下拉刷新 代码: https://github.com/enormego/EGOTableViewPullRefresh

异步加载 图片并缓存代码: http://www.markj.net/iphone-asynchronous-table-image/

iphone TTS: https://bitbucket.org/sfoster/iphone-tts

iphone cook book 源码: https://github.com/erica/iphone-3.0-cookbook-
iphone  正则表达式: http://regexkit.sourceforge.net/RegexKitLite/

OAuth认证:   http://code.google.com/p/oauth/
http://code.google.com/p/oauthconsumer/

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速