吴昌文

总有骄阳


  • Home

  • Tags

  • Categories

  • Archives

GitBucket源码阅读笔记

Posted on 2018-11-24 | In 源码阅读

学习函数式编程已经有一个半月了,学习了RxSwift和RAC2.X,尝试过写一些小的demo,但是一直没有了解过MVVM中是如何使用RAC进行通信的,由于公司项目准备使用MVVM+RAC模式重构,在github找到了使用MVVM+RAC构建的Github开源app:GitBucket,本文主要记录该项目中RAC在MVVM模式中的应用。

项目类图


项目中主要有两大继承体系:MRCViewModel和MRCViewController,分别对应ViewModel层和Controller层,实现代码复用

在MRCViewModel父类中存放了一些需发送给Controller层的信号,如标题变化,错误信号,控制器隐藏信号等;
MRCViewController中实现了监听ViewModel信号的方法,如监听ViewModel的标题变化

1
2
3
4
5
6
7
8
9
10
11
RAC(self.navigationItem, titleView) = [RACObserve(self.viewModel, titleViewType).distinctUntilChanged map:^(NSNumber *value) {
MRCTitleViewType titleViewType = value.unsignedIntegerValue;
switch (titleViewType) {
case MRCTitleViewTypeDefault:
return titleView;
case MRCTitleViewTypeDoubleTitle:
return (UIView *)doubleTitleView;
case MRCTitleViewTypeLoadingTitle:
return (UIView *)loadingTitleView;
}
}];

另外项目中使用了Service层提供ViewModel所需的各种服务
OCTClient:Github的三方库api,提供了RAC的支持
MRCRepositoryService/MRCRepositoryServiceImpl:应用自有的服务类
MRCViewModelServices:提供界面跳转的实现

界面跳转

作者没有使用系统提供的push/present操作实现界面跳转,而是在MRCViewModelServices中实现了一系列空操作

1
2
3
4
5
6
7
8
9
10
11
- (void)pushViewModel:(MRCViewModel *)viewModel animated:(BOOL)animated {}

- (void)popViewModelAnimated:(BOOL)animated {}

- (void)popToRootViewModelAnimated:(BOOL)animated {}

- (void)presentViewModel:(MRCViewModel *)viewModel animated:(BOOL)animated completion:(VoidBlock)completion {}

- (void)dismissViewModelAnimated:(BOOL)animated completion:(VoidBlock)completion {}

- (void)resetRootViewModel:(MRCViewModel *)viewModel {}

之后在视图层维护了一个MRCNavigationControllerStack,通过RAC订阅Service的跳转方法的调用,提供真正的界面跳转的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@weakify(self)
[[(NSObject *)self.services
rac_signalForSelector:@selector(pushViewModel:animated:)]
subscribeNext:^(RACTuple *tuple) {
@strongify(self)

MRCViewController *topViewController = (MRCViewController *)[self.navigationControllers.lastObject topViewController];
if (topViewController.tabBarController) {
topViewController.snapshot = [topViewController.tabBarController.view snapshotViewAfterScreenUpdates:NO];
} else {
topViewController.snapshot = [[self.navigationControllers.lastObject view] snapshotViewAfterScreenUpdates:NO];
}

UIViewController *viewController = (UIViewController *)[MRCRouter.sharedInstance viewControllerForViewModel:tuple.first];
viewController.hidesBottomBarWhenPushed = YES;
[self.navigationControllers.lastObject pushViewController:viewController animated:[tuple.second boolValue]];
}];

这样Controller只需要做数据绑定即可,需要跳转界面时,ViewModel层调用service的方法,MRCNavigationControllerStack监听到方法调用后做界面跳转
[self.services pushViewModel:viewModel animated:YES];

另外由于VM层不可引入View层的类,所以跳转的一系列方法的传参都为ViewModel,通过维护一个ViewModel与Controller层的映射字典,实现了视图与界面的分离
@property (nonatomic, copy) NSDictionary *viewModelViewMappings; // viewModel到view的映射

#RAC用法

###通过RAC提供代理的函数实现

使用rac_signalForSelector:fromProtocol方法订阅键盘的回车键

1
2
3
4
5
6
7
8
@weakify(self)
[[self
rac_signalForSelector:@selector(textFieldShouldReturn:)
fromProtocol:@protocol(UITextFieldDelegate)]
subscribeNext:^(RACTuple *tuple) {
@strongify(self)
if (tuple.first == self.passwordTextField) [self.viewModel.loginCommand execute:nil];
}];

###RAC监听UI事件
使用rac_signalForControlEvents方法订阅按钮的点击信号,当按钮被点击时,执行登录的命令

1
2
3
4
5
6
[[self.loginButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
@strongify(self)
[self.viewModel.loginCommand execute:nil];
}];

###RAC监听通知
使用rac_addObserverForName方法订阅app从后台进入前台的通知,takeUntil保证在对象销毁后取消订阅

1
2
3
4
5
6
7
[[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:UIApplicationWillEnterForegroundNotification object:nil]
takeUntil:self.rac_willDeallocSignal]
subscribeNext:^(id x) {
@strongify(self)
[self.viewModel.requestRemoteDataCommand execute:nil];
}];

###RAC在TableView中的应用
监听ViewModel中dataSource数组的变化,dataSource发生改变时重新加载tableView

1
2
3
4
5
6
7
8
@weakify(self)
[[[RACObserve(self.viewModel, dataSource)
distinctUntilChanged]
deliverOnMainThread]
subscribeNext:^(id x) {
@strongify(self)
[self reloadData];
}];

刷新列表时显示loading菊花,刷新完成时隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[[RACSignal
combineLatest:@[ self.viewModel.requestRemoteDataCommand.executing, RACObserve(self.viewModel, dataSource) ]
reduce:^(NSNumber *executing, NSArray *dataSource) {
return @(executing.boolValue && dataSource.count == 0);
}]
deliverOnMainThread]
subscribeNext:^(NSNumber *showHUD) {
@strongify(self)
if (showHUD.boolValue) {
[MBProgressHUD showHUDAddedTo:self.view animated:YES].labelText = MBPROGRESSHUD_LABEL_TEXT;
} else {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];

使用rac_sequence和map将model转化为viewmodel数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSArray *)dataSourceWithEvents:(NSArray *)events {
if (events.count == 0) return nil;

@weakify(self)
NSArray *viewModels = [events.rac_sequence map:^(OCTEvent *event) {
@strongify(self)
MRCNewsItemViewModel *viewModel = [[MRCNewsItemViewModel alloc] initWithEvent:event];
viewModel.didClickLinkCommand = self.didClickLinkCommand;
return viewModel;
}].array;

return @[ viewModels ];
}

Mac开发监听默认音频设备的变化

Posted on 2018-10-20 | In Mac

公司项目使用的音视频库需要在用户切换麦克风时收到通知,在网上找了很久半天,最后在苹果的官方示例代码中发现了解决方案

1
2
3
4
5
AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };

AudioObjectAddPropertyListener(kAudioObjectSystemObject, &theAddress, AOPropertyListenerProc, (__bridge void * _Nullable)(self));

使用设备属性设置一个监听器,这里我需要监听麦克风设备的切换,所以选择了kAudioHardwarePropertyDefaultInputDevice,AOPropertyListenerProc为一个函数指针,作为通知后的回调
之后再实现AOPropertyListenerProc函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
OSStatus AOPropertyListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void* inClientData)
{

for (UInt32 x=0; x<inNumberAddresses; x++) {

switch (inAddresses[x].mSelector)
{
/*
* These are the other types of notifications we might receive, however, they are beyond
* the scope of this sample and we ignore them.
*
case kAudioHardwarePropertyDefaultInputDevice:
fprintf(stderr, "AOPropertyListenerProc: default input device changed\n");
break;

case kAudioHardwarePropertyDefaultOutputDevice:
fprintf(stderr, "AOPropertyListenerProc: default output device changed\n");
break;

case kAudioHardwarePropertyDefaultSystemOutputDevice:
fprintf(stderr, "AOPropertyListenerProc: default system output device changed\n");
break;
*/
case kAudioHardwarePropertyDefaultInputDevice:{
fprintf(stderr, "AOPropertyListenerProc: default input device changed\n");
}
break;
// case kAudioHardwarePropertyDevices:
// {
// fprintf(stderr, "AOPropertyListenerProc: kAudioHardwarePropertyDevices\n");
// }
// break;

default:
fprintf(stderr, "AOPropertyListenerProc: unknown message\n");
break;
}
}

return noErr;
}

使用AFNetworking库后设置HTTP代理出错

Posted on 2018-09-17 | In iOS

使用AFNetworking库用于http请求
手机网络设置中设置http代理模式
请求报错
Error Domain=NSURLErrorDomain Code=-999 “cancelled”
解决办法:在
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] 后加入
manager.securityPolicy.validatesDomainName = NO;

在NSUserDefault中存储包含自定义对象的数组或字典

Posted on 2018-08-21

使子视图超出父视图之外的部分也可以响应事件

Posted on 2018-07-20 | In iOS

在父视图中重写hitTest方法,该方法为UIView中的方法(不可在ViewController中重写该方法)

1
2
3
4
5
6
7
8
9
10
11
12
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if (view == nil) {
for (UIView *subView in self.subviews) {
CGPoint p = [subView convertPoint:point fromView:self];
if (CGRectContainsPoint(subView.bounds, p) && subView.tag == RESPOND_OUT_OF_SUPERVIEW && subView.hidden == NO) {
view = subView;
}
}
}
return view;
}

注意:重写该方法之后即使点击在SuperView之外或者父视图已经隐藏,系统也会调用该方法

Raywenderlich 的 Swift 代码规范

Posted on 2018-07-03 | In iOS

Raywenderlich 的 Swift 代码规范

Raywenderlich 的 OC 代码规范

Posted on 2018-07-03 | In iOS

Raywenderlich 的 OC 代码规范

iOS状态栏操作小结

Posted on 2018-05-07 | In iOS

找遍了网上一些设置状态栏的方法,没有比较全面的,所以自己总结了一个
设置样式和设置隐藏的方法基本一致,自行类比

##iOS6及之前版本
在Info.plist表单中设置 View controller-based status bar appearance字段为 NO(禁用系统自带的状态栏)
在代码中通过- (void)setStatusBarHidden:(BOOL)hidden animated:(BOOL)animated进行控制
这种方法对状态栏的修改是全局的,对所有视图控制器都会生效


##iOS7及之后版本

  1. iOS6的方法被不被苹果推荐使用,会报警告
  2. 在你需要隐藏状态栏的视图控制器类中添加这个方法
    - (BOOL)prefersStatusBarHidden{ return YES; }
    设置YES就隐藏状态栏,设置NO显示状态栏
    系统会自动调用这个函数
    如果需要更改StatusBar的状态的话,可以使用在类中添加一个属性isStatusBarHidden,在init中初始化为你想要的值,添加这个方法
    - (BOOL)prefersStatusBarHidden{ return self.isStatusBarHidden; }
    在需要显示状态栏的时候调用
    self.isStatusBarHidden = NO; [self setNeedsStatusBarAppearanceUpdate];
    在需要隐藏状态栏的时候调用
    self.isStatusBarHidden = YES; [self setNeedsStatusBarAppearanceUpdate];
    setNeedsStatusBarAppearanceUpdate这个方法的作用是更新状态栏,在每次修改状态栏设置或者样式之后调用

注意:此方法对NavigationController无效,要使其生效可创建一个NavigationController的类别,增加这个方法
- (UIStatusBarStyle)preferredStatusBarStyle { return [[self topViewController] preferredStatusBarStyle]; }

这个函数的返回值默认返回nil,此时系统就会调用当前viewControllerA的preferredStatusBarStyle函数;如果返回值是另一个viewControllerB那么系统就会调用viewControllerB的preferredStatusBarStyle函数

Rxswift和RxCocoa中何时使用unowned和weak

Posted on 2018-04-07 | In iOS

使用RxCocoa有时很难区分什么时候使用weak,什么时候使用unowned。当一个闭包执行时其中的self引用的对象有可能已经被释放掉时,我们会使用weak,所以将self声明为可选类型。当我们知道某个闭包执行时我们确定self引用的对象不会被释放掉,我们使用unowned,否则将会导致崩溃
在RxSwift中,特别是在RxCocoa中,选择关键字需遵从下列原则:

  • noting:在单例或者绝对不会被释放掉的视图控制器中使用(如根控制器)
  • unowned:在确认闭包执行完成之后视图控制器才会被释放时使用
  • weak:其余情况
    参考资料: RxSwift - By Raywenderlich

iOS中设置禁止自动锁屏无效的解决方案

Posted on 2018-02-26 | In iOS

一般情况下,在代码中设置

1
[UIApplication sharedApplication].idleTimerDisabled = NO;

这样可以禁止自动锁屏

然而从iOS 3 直到iOS10,禁止自动锁屏概率性失效的BUG苹果一直未修复

使用以下代码可完全禁止自动锁屏

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
-(void)callEveryTwentySeconds
{
// DON'T let the device go to sleep during our sync
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
}

-(void)loadDataFromWebService
{
self.stayAliveTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
target:self
selector:@selector(callEveryTwentySeconds)
userInfo:nil
repeats:YES];

//
// Code to call our web service in a background thread and wait
// for it to finish (which might take a few minutes)
//

// Kill off our "Stay alive" timer, and allow the device to Auto Lock whenever it wants.
[self.stayAliveTimer invalidate];

// Give our device permission to Auto-Lock when it wants to again.
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}

123

Jarvis Wu

唯有时刻保持清醒,才能看清真正的价值在哪里

21 posts
4 categories
22 tags
GitHub E-Mail Google
© 2019 Jarvis Wu
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4