NSProxy的用法

NSproxy是唯一不继承NSObject的基类,是一个只用于转发消息的抽象类。

基本用法和区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface MyProxy : NSProxy
@property (nonatomic, strong) id target;
@end

@implementation THProxyA
- (id)initWithObject:(id)object {
    self.target = object;
    return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [self.target methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end
  • NSProxy可以使用转发respondsToSelector:isKindOfClass:,而NSObject则不能,需要手动添加上面两个方法
  • NSObject不能转发category的方法,如valueForKey:是定义在NSKeyValueCoding这个NSObject的Category中的方法,而NSProxy不受影响。但由于ARC时会对声明的selector做严格监测,会报No visible @interface for Class declares the selector,所以最好事先声明或者使用performSelctor:或者NSInvocation

NSTimer

因为Timer会引用target,而target如果retain了timer,就造成了循环引用。折中方式是使用WeakProxy的对象来引用,打破循环引用

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
@interface WeakProxy : NSProxy
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end

@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
    _target = target;
    return self;
}
+ (instancetype)proxyWithTarget:(id)target{
    return [[self alloc] initWithTarget:target];
}4
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.target respondsToSelector:aSelector];
}
@end

如下使用

1
2
3
4
5
self.timer = [NSTimer timerWithTimeInterval:1
                                     target:[WeakProxy proxyWithTarget:self]
                                   selector:@selector(timerInvoked:)
                                   userInfo:nil
                                    repeats:YES];

取代delegate的胶水代码

1
2
3
4
id<PSPDFResizableViewDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(resizableViewDidBeginEditing:)]) {
    [delegate resizableViewDidBeginEditing:self];
}

具体参考PSTDelegateProxy,用Message forwarding机制,来构建一个delegateProxy.

  1. methodSignatureForSelector:andforwardInvocation:中判断是否respond
  2. 利用PSPDF_DELEGATE_PROXY宏定义
1
2
3
4
5
6
7
8
9
@interface PSPDFResizableView ()
@property (nonatomic, strong) id <PSPDFResizableViewDelegate> delegateProxy;
@end

PSPDF_DELEGATE_PROXY(id <PSPDFResizableViewDelegate>)

- (void)handleRecognizerStateBegan:(UIGestureRecognizer *)recognizer {
    [self.delegateProxy resizableViewDidBeginEditing:self];
}

纷享用到的IOC框架

这个下次讲述。

同时部署octopress到github和gitcafe

这个属于谈滥的话题,作者最开始用coding.net,无奈免费的演示平台不支持自定义域名,没办法又换回gitcafe

代码也并不复杂,直接贴代码

1
2
3
4
cd _deploy //注意发布的只是deploy文件夹内容而已
git remote add gitcafe git@gitcafe.com:passol/passol.git >> /dev/null 2>&1
git push -u gitcafe master:gitcafe-pages
//gitcafe一定要建立和username相同的repo名,并且只能提交到gitcafe-pages或gh-pages才可以

也可以直接修改Rakefile

1
2
system "git remote add gitcafe git@gitcafe.com:passol/passol.git >> /dev/null 2>&1"
system "git push -u gitcafe master:gitcafe-pages"

插入到


设置域名解析

  • 在repo目录下创建一个名为CNAME的文件(无后缀)
  • 打开CNAME,在里面写入你要绑定的域名(passol.tkwww.passol.tk)
  • 1)请在域名管理里添加一条CNAME记录,指向username.github.io
  • 2)或者域名管理里添加一条A记录,指向192.30.252.153192.30.252.154 等待生效,如果不知道ip,可以使用dig passol.github.io +nostats +nocmd +nocomments查询下具体的ip
  • gitcafe类似,可以自定义域名,就不需要手动添加CNAME文件
  • dnspod注册下域名,国外走github,国内走gitcafe
  • 如果还嫌不够,再搞个监控宝,监控下性能

一些常用的命令

1
2
git branch -d <branchname>    # 删除本地分支
git push origin :<branchname> # 删除origin仓库的分支
1
2
3
4
5
rake new_post[article name] //生成新博文
rake generate //生成静态文件
rake watch //检测文件变化,实时生成文件
rake preview //在localhost:4000预览效果
rake deploy //发布文件

事件处理文档

内置Gesture

UITapGestureRecognizer,UIPinchGestureRecognizer, UIPanGestureRecognizer, UISwipeGestureRecognizer, UIRotationGestureRecognizer,UILongPressGestureRecognizer,

  1. 指定顺序 swipe和pan手势
1
2
[self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];

  1. 防止手势被识别

    gestureRecognizer:shouldReceiveTouch: gestureRecognizerShouldBegin:

  2. 同时识别手势

    gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:

    [rotationGestureRecognizer canPreventGestureRecognizer:pinchGestureRecognizer]; canBePreventedByGestureRecognizer:

KVC程序设计(官方文档)

KVC的定义

该文档主要介绍NSKeyValueCoding协议,他实现了一种允许调用对象属性的名称(或键)的机制,而不是直接调用setter或者getter方法或者实例变量 实现KVC方法是重要的设计原则,有助于数据封装和配合kvo,CoreData,CocoaBinding等,甚至能简化代码

KVC和脚本

OSX主要依赖于KVC执行脚本命令,脚本内嵌的关键字也是KVC类型的API

利用KVC简化代码

未使用KVC
1
2
3
4
5
6
7
8
9
10
11
12
13
14

    - (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column
                                              row:(NSInteger)row {
      ChildObject *child = [childrenArray objectAtIndex:row];
      if ([[column identifier] isEqualToString:@"name"]) {
          return [child name];
      }
      if ([[column identifier] isEqualToString:@"age"]) {
          return [child age];
      }
      if ([[column identifier] isEqualToString:@"favoriteColor"]) {
          return [child favoriteColor];
      }
  }
使用KVC
1
2
3
4
5
6

    - (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column
                                           row:(NSInteger)row {
      ChildObject *child = [childrenArray objectAtIndex:row];
      return [child valueForKey:[column identifier]];
  }

基本术语

kvc主要用做取得三种类型的对象值(property):

  1. attribute属性关系,比如NSNumber,NSColor
  2. to-one relationship对一关系,通常是被所属关系,UIView的superView
  3. to-many relationship对多关系,通常是所属的集合类属性,如NSArray或NSSet

KVC基础

键和keyPaths

  1. key必须是ASCII编码,小写字母开头,不能包含空格,如payee
  2. key paths是用.隔开,遍历对象对最终的值,如 address.street

KVC取值

  • valueForKey:返回对象的对应的键值,如果没有方法或实例变量,receiver会发出valueForUndefinedKey:消息.而默认的valueForUndefinedKey:会抛出NSUndefinedKeyException异常
  • valueForKeyPath:也会抛出一样的异常
  • dictionaryWithValuesForKeys:返回对象所有的键关联的数组.返回的字典包含数组所有的键值

KVC赋值

  • setValue:forKey:赋对象键值.默认的方法自动解封装NSValue对象.如果key不存在,对象默认发送setValue:forUndefinedKey:方法,抛出NSUndefinedKeyException异常
  • setValue:forKeyPath:也有类似的处理方式
  • setValuesForKeysWithDictionary:就是N个setValue:forKey:(必要是要用nil或者NSNull)
  • 还有种情况需要考虑,对象给不是对象的属性赋nil值,就会发出setNilValueForKey:消息,抛出NSInvalidArgumentException异常

.语法和KVC

1
2
3
4
5
@interface MyClass
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end
直接用KVC修改
1
2
3
 MyClass *myInstance = [[MyClass alloc] init];
  NSString *string = [myInstance valueForKey:@"stringProperty"];
  [myInstance setValue:@2 forKey:@"integerProperty"];
.语法修改
1
2
3
 MyClass *anotherInstance = [[MyClass alloc] init];
  myInstance.linkedInstance = anotherInstance;
  myInstance.linkedInstance.integerProperty = 2;
结合起来修改
1
2
3
 MyClass *anotherInstance = [[MyClass alloc] init];
  myInstance.linkedInstance = anotherInstance;
  [myInstance setValue:@2 forKeyPath:@"linkedInstance.integerProperty"];

KVC赋/取方法常用方法:

  • valueForKey:,
  • setValue:forKey:,
  • mutableArrayValueForKey:,
  • mutableSetValueForKey:

通用accessor方法 - getter. - - 第二种 -is - setter, -set: - 如果属性不是对象,你就必须在适当的时候手动赋给nil值

针对to-many属性集合类型 - 有序的accessor类型

不可变

  1. countOf 必须
  2. -objectInAtIndex: or -AtIndexes: 必须实现一个.他对应objectAtIndex:objectsAtIndexes:
  3. -get:range: 可选实现,返回NSArray.对应NSArray的getObjects:range:

可变

  1. -insertObject:inAtIndex: or -insert:atIndexes: 至少实现一个
  2. -removeObjectFromAtIndex: or -removeAtIndexes: 至少实现一个
  3. -replaceObjectInAtIndex:withObject: or -replaceAtIndexes:with:可选

  • 无序的accessor类型

不可变

  1. -countOf 必须 count
  2. -enumeratorOf 必须objectEnumerator
  3. -memberOf 必须

可变

  1. -addObject: or -add: 必须实现一个
  2. -removeObject: or -remove: 必须实现一个
  3. -intersect 可选

KVC验证

调用验证方法

  • validateValue:forKey:error:默认实现是匹配模式validate:error:
  • 自动验证,一般CoreData会自动验证
  • 验证scalar values

确保兼容KVC

属性和对一关系

  • -<key>,-is<key>或者实例变量<key>或者_<key>,也可以用大写
  • 可变属性也有set<key>
  • set<key>不会执行验证
  • 如果验证对key合适,实现-validate<key>:error:

有序对多关系

  • -<key>
  • 实例变量<key>,或者_<key>
  • -countOf<key>-objectIn<Key>AtIndex:-<key>AtIndexes:
  • -get<key>:range: 对可变有序对多关系来说,还要
  • insertObject:in<Key>AtIndex:-insert<Key>:atIndexes:
  • removeObjectFrom<Key>AtIndex:remove<Key>AtIndexes:
  • 可选,-replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<ley>:

无序序对多关系

  • -<key>
  • 实例变量<key>_<key>
  • -countOf<key>,-enumerateOf<key>-memberOf<key> 对可变的无序关系来说,还要
  • -add<key>Object:-add<Key>
  • -remove<key>Object:-remove<key>
  • 可选,-intersect<key>:-set<Key>:

扩展数据类型

表示非对象类型的值

valueForKey:setValue:forKey:提供自动封装非对象类型的值

处理空值

默认的setNilValueForKey:会抛出NSInvalidArgumentException异常,子类可以重写该方法

对集合的操作

keypathToCollection.@collectionOperator.keypathToProperty

@avg

  • NSNumber *transactionAverage = [transactions valueForKeyPath:@"@avg.amount"];@count
  • NSNumber *numberOfTransactions = [transactions valueForKeyPath:@"@count"]; @max- NSDate *latestDate = [transactions valueForKeyPath:@"@max.date"];@min

  • NSDate *earliestDate = [transactions valueForKeyPath:@"@min.date"];@sum

  • NSNumber *amountSum = [transactions valueForKeyPath:@"@sum.amount"];@distinctUnionOfObjects

  • NSArray *payees = [transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];@unionOfObjects
  • NSArray *payees = [transactions valueForKeyPath:@"@unionOfObjects.payee"];数组和set操作 @distinctUnionOfArrays
  • NSArray *payees = [arrayOfTransactionsArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"]@unionOfArrays
  • NSArray *payees = [arrayOfTransactionsArraysvalueForKeyPath:@"@unionOfArrays.payee"]; @distinctUnionOfSets

KVC方法搜索实现细节

简单属性

setValue:forKey:调用成员是按照以下模式: 1. 对象类搜索是否存在存取方法set<Key>: 2. 如果没有,对象类方法accessInstanceVariablesDirectly返回YES.对象一词按顺序搜索_<key>,_is<Key>,<key>,is<Key> 3. 如果定位到函数或实例变量,进行设置值.如有必要,可以拆开对象(NSNumber,NSValue) 4. 如果仍然没有,调用setValue:forUndefinedKey:

valueForKey:调用成员是按照以下模式: 1. 按顺序搜索get<Key>,<key>,is<Key>.如果结果是扩展类型,也可以返回NSNumber或NSValue类型 2. countOf<Key>objectIn<Key>AtIndex:(NSArray)或<key>AtIndexes:,备选get<Key>:range: 3. countOf<Key>,enumeratorOf<Key>memberOf<Key> 4. 如果accessInstanceVariablesDirectly返回YES.类会寻找实例变量_<key>,_is<key>,<key>is<key>.必要时做转化 5. valueForUndefinedKey:

读有序集合

  1. insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:(对应NSMutableArray的 insertObject:atIndex: and removeObjectAtIndex:).或者insert<Key>:atIndexes:remove<Key>AtIndexes:.或者提高性能使用replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:
  2. 搜索set<Key>:
  3. 如果类方法accessInstanceVariablesDirectly返回YES.搜索实例变量_<key><key>
  4. setValue:forUndefinedKey:(报错)

写有序集合

  1. insertObject:in<key>AtIndex:removeObjectFrom<Key>AtIndex:
  2. set<Key>:
  3. 如果类方法accessInstanceVariablesDirectly返回YES.搜索_<key><key>
  4. setValue:forUndefinedKey:(报错)

读无序集合

  1. add<Key>Object:remove<Key>Object:
  2. CoreData似管理
  3. set<Key>:
  4. 如果类方法accessInstanceVariablesDirectly返回YES,搜索实例变量_<key><key>
  5. setValue:forUndefinedKey:

更多资料

  1. http://stackoverflow.com/questions/15968741/ios-kvc-why-doesnt-it-invoke-countofkey-when-i-invoke-myobject-valueforke/
  2. http://iloss.me/post/kai-fa/2014-01-18-ios-kvc
  3. http://nijino.qiniudn.com/blog/2013/07/02/using-kvc/
  4. http://zhangbin.cc/2012/08/16/kvc-kvo/
  5. http://nshipster.com/kvc-collection-operators/

KVO程序设计(官方文档)

简要介绍

kvo是允许对象监视另一对象属性变化的一种机制.

初步认识

kvo常用作model和controller相互通信(在osx中, controller通常用绑定实现kvo机制).controller经常监视model属性变化,然后刷新UI.model对象也可以观察另外的model属性变化.

kvo可以观察哪些属性呢?基本属性,对单关系,对多关系等.

下面通过一个例子说明kvo怎么运作 1. 场景:当一个对象的某个对象发生变化,总希望通知另一对象.如银行的存款发生变化总会通知用户 2. 户主就必须注册为观察者,发送addObserver:forKeyPath:options:context:的消息 3. 为了能应对收到通知,观察者还必须实现observeValueForKeyPath:ofObject:change:context:. 4. 当对象属性发生变化时,observeValueForKeyPath:ofObject:change:context:立即调用

kvo的好处在于你不必写很多基础代码,由于已经集成在框架里,甚至可以轻松设置多个观察者.通知不是使用NSNotificationCenter,没有中枢管理所有观察者的通知.NSObject已经提供了KVO的实现,所以你可以完全使用.

注册kvo

观察者需要满足三个条件: 1. 被观察的类属性必须兼容kvo(“KVO Compliance”) 2. addObserver:forKeyPath:options:context:. 3. observeValueForKeyPath:ofObject:change:context:.

注册

使用NSKeyValueObservingOptionOld或者NSKeyValueObservingOptionNew标记改变前后的值

1
2
3
4
 [account addObserver:inspector
              forKeyPath:@"openingBalance"
                  options:(NSKeyValueObservingOptionNew |                            NSKeyValueObservingOptionOld)
                  context:NULL];

context是独特的标示符,或者引用指针.

收到通知

NSKeyValueChangeNewKey NSKeyValueChangeInsertion NSKeyValueChangeRemoval NSKeyValueChangeReplacement

取消观察

如果context指向的对象,你必须保持强引用防止被删除

KVO Compliance

特定值适合kvo comliance: 1. 特定的属性满足kvo 2. 发出通知 3. 关联值注册

自动改变通知

使用mutableArrayValueForKey

手动改变通知

  • automaticallyNotifiesObserversForKey返回NO关闭自动通知
1
2
3
[self willChangeValueForKey:@"openingBalance"];
_openingBalance = theBalance;
[self didChangeValueForKey:@"openingBalance"];

注册依赖键

单一关系

1
2
3
- (NSString *)fullName{
  return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

使用keyPathsForValuesAffectingValueForKey增加对firstNamelastName的观察 也可以直接使用keyPathsForValuesAffectingFullName

对多关系

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
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
     change:(NSDictionary *)change context:(void *)context {
         if (context == totalSalaryContext) {
             [self updateTotalSalary];
         }
         else
         // deal with other observations and/or invoke super...
}
- (void)updateTotalSalary {

[self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];

}

- (void)setTotalSalary:(NSNumber *)newTotalSalary {
         if (totalSalary != newTotalSalary) {
             [self willChangeValueForKey:@"totalSalary"];
             _totalSalary = newTotalSalary;
             [self didChangeValueForKey:@"totalSalary"];
} }

- (NSNumber *)totalSalary {
         return _totalSalary;
}

也可以使用CoreData

并发编程指南(官方文档)

并发与程序设计

用RAC使用MVVM教程(1)

所谓的IOS架构,MVC就代表重量级的VC – Colin Campbell

本教程将讲述另一种架构APP的方式:MVVM.它搭配MVVM方式,提供了可替代MVC方式的完美方案

RAC简要介绍

  • RAC的核心就是信号,就是RACSignal类.信号可以发出事件流:next,completederror 在简单的场景中,rac可以取代代理模式.TA模式,和观察者模式等
  • 信号提供的API更容易使用和阅读,但是RAC更大的作用是能对信号做复杂的处理,满足实现要求.
  • 详情可以阅读earlier tutorial series

MVVM简要介绍

MV*系列的模式(MVC或MVP等)核心在于分割UI逻辑和业务逻辑,让发开更方便.

更多资料可以阅读EliAsh Furrow的文章

MVC模型图大致如下: Smaller icon 在MVC模式中,Model代表APP数据,View代表控件集合,而C则负责中间协调调度 设计的初衷是好的,但是Controler往往变得逻辑复杂,代码多

最近Martin Fowler提出了MVC的变种,也被微软采用了MVVM模式 Smaller icon

这种模式的核心是ViewModel,特殊的Model专门表示UI状态,我们可以称之为View的model.基本规则是: 1. View有一个引用到ViewModel,反之不一定.(简单来说,View总有一个VM可以管理他) 2. ViewModel有一个引用指向Model,反之不一定 这样的规定带来的好处有: 1. 轻量级的Views,所有的UI逻辑都在ViewModel里面,View显得更轻量级 2. 方便测试

MVVM和数据绑定

MVVM依赖于数据绑定,能自动关联对象属性到UI控件 - 微软的WPF框架就提供了这种双向绑定

1
<TextField Text=”{DataBinding Path=Username, Mode=TwoWay}”/>

VM的username属性和textField的内容就绑定到一起

  • Web端也提供了类似的框架Knockout
1
<input data-bind=”value: username”/>

实现HTML元素和JS Model的关联

  • 原生的IOS没有提供数据绑定的框架,而第三方库RAC就提供了这种功能

这有篇文章专门介绍GUI架构,来自Martin Fowler

动手编程

下载启动文件FlickrSearchStarterProject.zip

我们使用CocoaPods管理库文件,只需要运行pod install

FRP in IOS翻译(5)

MVVM

理解block

引言

Blocks非常方便使用,在使用中通常使用以下方式:

1
2
3
4
5
6
7
 __weak __typeof__(self) weakSelf = self;
  self.block = ^{
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf doSomething];//片段1
      [strongSelf doSomethingElse];//片段2
  };
  

block运行机制

  • block创建在栈上,当栈帧返回的时候释放.在栈中的时候,block不会对使用的任何对象保留或者释放.
  • 但当栈帧返回时候热然需要使用block的时候,我们会复制block到堆里(这是显式行为).Block会在整个作用域保留所有的使用对象.
  • block可以包含代码段,同时也要在生命周期保存状态.具体来讲,Block保留括号内所有变量.所以使用__weak避免重复包含.

  • 当代码执行时,有可能出现片段1成功执行,片段2不执行(因为self可能为nil)

  • 在block执行时创建指向self的强引用,然后保留对象
  • 实践来讲,最好创建指向weakSelf的强引用.他不会引起retain cycle因为强引用只存在于block执行期间,所以使用__strong

block一定不能retain self么

也不一定,当其他对象的block引用self,虽然也会保存状态,但是不会引起retain cycle

拓展

上述方式虽然好,不过不够简洁.幸好有libextobjc提供了@weakify()@strongify().上述代码可以精简成:

1
2
3
4
5
6
7
 @weakify(self);
  self.block = ^{
      @strongify(self);
      [self doSomething];//片段1
      [self doSomethingElse];//片段2
  };   
  

更多资料

  1. http://www.fantageek.com/1090/understanding-weak-self-and-strong-self/
  2. http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
  3. http://www.idryman.org/blog/2012/11/22/arc-best-practices-and-pitfalls/(内容详实)
  4. http://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/
  5. http://stackoverflow.com/questions/19018456/ios-blocks-and-strong-weak-references-to-self
  6. http://blackpixel.com/blog/2014/03/capturing-myself.html(我尚未看)
  7. http://albertodebortoli.github.io/blog/2013/08/03/objective-c-blocks-caveat/

FRP in IOS翻译(4)

RAC实践

作者举例FunctionalReactivePixels

基本信息

  • 分别创建UICollectionViewController的子类FRPGalleryViewControllerUICollectionViewFlowLayout的子类FRPGalleryFlowLayout
init初始化
1
2
3
4
5
6
7
 - (id)init
  {
      FRPGalleryFlowLayout *flowLayout = [[FRPGalleryFlowLayout alloc] init];
      self = [self initWithCollectionViewLayout:flowLayout];
      if(!self) return nil;
      return self;
  }
初始化*layout*
1
2
3
4
5
6
7
8
9
10
11
 - (instancetype)init {
      
      if(!(self = [super init])) return nil;
      
      self.itemSize = CGSizeMake(145, 145);
      self.minimumInteritemSpacing = 10;
      self.minimumLineSpacing = 10;
      self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
      
      return self;
  }
  • 初始化application: didFinishLaunchingWithOptions:函数
系统委托函数
1
2
3
4
5
6
7
8
-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  self.window.rootViewController = [[UINavigationController alloc]            initWithRootViewController:[[FRPGalleryViewController alloc] init]];
  self.window.backgroundColor = [UIColor whiteColor];
  [self.window makeKeyAndVisible];
  return YES;
}
  • 搭建model类FRPPhotoModel
1
2
3
4
5
6
7
8
9
10
11
12
13
 @interfaceFRPPhotoModel:NSObject
  
  @property(nonatomic,strong)NSString*photoName;
  @property(nonatomic,strong)NSNumber*identifier;
  @property(nonatomic,strong)NSString*photographerName;
  @property(nonatomic,strong)NSNumber*rating;
  @property(nonatomic,strong)NSString*thumbnailURL;
  @property(nonatomic,strong)NSData*thumbnailData;
  @property(nonatomic,strong)NSString*fullsizedURL;
  @property(nonatomic,strong)NSData*fullsizedData;
  
  @end
  
  • 和传统的用VC直接控制model.我们将抽象的逻辑放在另一个类中FRPPhotoImporter
1
2
3
 @interfaceFRPPhotoImporter:NSObject
+ (RACSignal *)importPhotos;
@end

图片引入函数importPhotos返回API结果的RAC信号.recommend against using RACSubjects,

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
 +(RACReplaySubject*)importPhotos{
      RACReplaySubject *subject = [RACReplaySubject subject];
      
      NSURLRequest *request = [self popularURLRequest];
      [NSURLConnection
        sendAsynchronousRequest:request
        queue:[NSOperationQueue mainQueue]
        completionHandler:^(NSURLResponse *response,
            NSData *data, NSError *connectionError) {
              if (data) {
                  id results = [NSJSONSerialization
                  JSONObjectWithData:data options:0 error:nil];
                  [subject sendNext:[[[results[@"photos"] rac_sequence] map:                  ^id(NSDictionary *photoDictionary) {
                      FRPPhotoModel *model = [FRPPhotoModel new];
                      [self configurePhotoModel:model
                              withDictionary:photoDictionary];
                      [self downloadThumbnailForPhotoModel:model];
                  return model; }] array]];
                  [subject sendCompleted];
              }
              else {
                  [subject sendError:connectionError];
      } }];

      return subject;
  }

首先,创建RACReplaySubject实例.然后,创建异步请求获取照片对象,再立刻返回信号. 当从API获取数据后,回调马上调用,所有订阅者都能收到值

RACSubject和子类RACReplaySubject区别在于: replay保证主要对象只被订阅一次,防止了重复工作(尤其对于网络活动尤为重要).同时,replay对象会存储值并转发到新的订阅者,正如Justin Spahr指出的那样,也能有效防止竞争者状态.

我们是直接发送完成信号而不是发送随着时间变化的流,如果要联合其他操作这就显得有点过度反应.不过,这有助于标志完成一段任务.如果想看更高级的联合,可以看看octokit

我们只检测是否有数据返回,虽然这不是最好的方式判断是否有数据,但可以作为教学例子.如果返回数据为nil,发送error信号.否则,解析json.

我们将结果的photo放入序列中,映射,再转回数组.model的数组就是要传递的值,最后发送completed信号.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 +(void)configurePhotoModel:(FRPPhotoModel*)photoModelwithDictionary:(NSDictiona\ ry*)dictionary{
  // Basics details fetched with the first, basic request
    photoModel.photoName = dictionary[@"name"];
    photoModel.identifier = dictionary[@"id"];
    photoModel.photographerName = dictionary[@"user"][@"username"];
    photoModel.rating = dictionary[@"rating"];

    photoModel.thumbnailURL = [self urlForImageSize:3 inArray:dictionary[@"images"]];
  
  // Extended attributes fetched with subsequent request
  if (dictionary[@"comments_count"]) {
      photoModel.fullsizedURL = [self urlForImageSize:4                                     inArray:dictionary[@"images"]];
  }
  
  }
1
2
3
4
5
6
7
 +(NSString*)urlForImageSize:(NSInteger)size inDictionary:(NSArray*)array{
  return [[[[[array rac_sequence] filter:^BOOL(NSDictionary *value) {
      return [value[@"size"] integerValue] == size;
  }] map:^id(id value) {
      return value[@"url"];
      }] array] firstObject];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
 +(void)downloadThumbnailForPhotoModel:(FRPPhotoModel*)photoModel{
      NSAssert(photoModel.thumbnailURL, @"Thumbnail URL must not be nil");
      
      NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:pho\
      toModel.thumbnailURL]];
      [NSURLConnection sendAsynchronousRequest:request
          queue:[NSOperationQueue mainQueue]
          completionHandler:^(NSURLResponse *response,
            NSData *data, NSError *connectionError) {
        photoModel.thumbnailData = data;
      }];
  
  }
1
2
3
 @interface FRPGalleryViewController()
  @property(nonatomic,strong)NSArray*photosArray;
  @end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 static NSString*CellIdentifier=@"Cell";
  -(void)viewDidLoad
  {
      [super viewDidLoad];
      
      // Configure self
      self.title = @"Popular on 500px";

      // Configure view
      [self.collectionView registerClass:[FRPCell class]
          forCellWithReuseIdentifier:CellIdentifier];

      // Reactive Stuff
      @weakify(self);
      [RACObserve(self, photosArray) subscribeNext:^(id x) {
          @strongify(self);
          [self.collectionView reloadData];
      }
      
      // Load data
      [self loadPopularPhotos];
      
  }
  

RACObserver是一个带有两个参数的C宏:对象和对象的KeyPath,当key path的值发生变化他返回信号.释放self的时候返回completed信号.当属性photosArray发生变化collectionView重新加载.

基本上,subscribeNext:会保留self.引起self和block的retain cycle.block被subscribeNext:的返回值-RACSubscriber实例保留,接着又被RACObserver保留.一旦释放self,RACObserver会自动释放.没有weakify/strongify,self不会被释放掉

1
2
3
4
5
6
7
-(void)loadPopularPhotos {
  [[FRPPhotoImporter importPhotos] subscribeNext:^(id x) {
      self.photosArray = x;
  } error:^(NSError *error) {
      NSLog(@"Couldn't fetch photos from 500px: %@",error);
  }];
}