为了统一,各视图分别使用如下组件
二、选择器Picker
1、事项选择器:ItemPickerUtil
1 | import 'package:flutter_datepicker/flutter_datepicker.dart'; |
2、日期选择器DatePickerUtil
1 | import 'package:flutter_datepicker/flutter_datepicker.dart'; |
为了统一,各视图分别使用如下组件
1 | import 'package:flutter_datepicker/flutter_datepicker.dart'; |
1 | import 'package:flutter_datepicker/flutter_datepicker.dart'; |
1 | class WishPublishPage extends BJHBasePage { |
详看:UI框架.xmind
详看:框架的重要性.pptx
框架是什么?
框架是解决问题的具体实现方法,能直接执行或复用。
为什么需要框架?
简单的讲是为了约束和统一。
举个例子:我们想要让所有的页面支持在无网络的时候,都有个缺省页。
如果没有一个框架,则我们每个页面都得进行很多判断和视图操作。
且后期如果需要修改,也会导致工作量巨大。
框架的好处是什么?
我们先从常见的几个工作中实际场景介绍:
网络请求:不进行框架/封装处理的话,可能遇到的问题:
①、每次进行网络请求,都得写一堆代码才能完成最基础的请求操作;
②、如果还要求对每个请求都添加一些公共参数、错误码处理,则又得每个请求添加一遍;
页面的缺省:不进行框架/封装处理的话,可能遇到的问题:
①、无网络等状态下,直接无缺省页,显示成白屏,体验极差;
②、有设置缺省页,但无进行框架化,导致每个页面都得堆一堆代码来实现缺省页功能;
③、有设置缺省页,但无进行框架化,后期需要修改时候,每个页面都修改,维护成本巨大;
视图控件(如按钮):不进行框架/封装处理的话,可能遇到的问题:
①、代码实现特长;
②、还没有点击效果,如果要添加又要一堆代码;
③、后期需要修改时候,每个页面都修改,维护成本巨大;
测试框架:
①、开发联调接口,无法设置代理抓包查看;
②、用户反馈问题,无法提供问题出现的版本等信息;
③、已发布包程序出现异常,开发无法定位;
场景问题解决要点/框架能解决的问题:
①、不用堆一堆类似代码,几行代码就实现
②、规范变化的时候,不要我关心和修改
③、增加通用功能的时候,不需要自己再去实现一遍
④、app异常时候的监测和反馈
框架化后,以上这些问题都能够得到解决。
所以,框架的好处有统一设计,
建立框架的意义:
好的框架能够保证和提升项目的可维护性,扩展性,健壮性。
能够提高工作效率
能够让风格更统一
各框架提供的功能
缺省页框架:
解决可能的初始”白屏”
无网络情况下的”空白页”
无数据情况下的”空白页”
为页面提供网络异常页并伴有刷新恢复重试
测试框架:
新增抓包设置(代理)
新增查看版本页面,方便对应反馈的问题出现的版本(防止是旧包)。
程序异常(上报+提示)
视图控件框架:
定义加载动画loading
愿望灯动画效果优化(底部下沉)
框架的使用
使用前(纯代码,未有任何封装):
1 | GestureDetector( |
框架化后:
1 | ThemeBGButton( |
为了有更好的用户体验,我们提出以下优化和升级方案。
白屏体验
网络恢复
页面无任何占位视图。如页面本身就是一个列表。
页面数据来源于网络,网络请求回来前,空。
主要有以下两种方案,各自独立,分场景使用。
①界面框架预染页(美团/饿了么的灰底效果)
②数据预加载(首页)
美团/饿了么的灰底效果(如商品详情页)
1 | // bad |
当realWidgets中的数据全部来自网络时候,也有白屏的可能,如列表,如需确实有界面框架显示,使用如下方式:
初次冷启动:
使用与产品约定的默认数据来加载
其他:
保存缓存数据,下次使用缓存数据来加载
框架接入 2d
②处理请求后的场景:文字提示+页面展示(无数据+无网络)
2、网络异常:
为页面提供网络异常页并伴有刷新恢复重试
框架接入 2d
根据情况,弹出易于用户理解的错误提示
1 | // bad |
接入bugly服务+自定义异常页
接入第三方bugly 或华为AGC的崩溃服务
默认的程序异常页 VS 处理后的程序异常页

程序异常上报后的后台:
异常上报 1d
新增查看版本页面,方便对应反馈的问题出现的版本(防止是旧包)。
新增代理设置,方便接口人员抓包排查
定义加载动画loading
愿望灯动画效果优化(底部下沉)
UI提供 images 或 json,自定义loading动画
采用上层愿望灯不变,底部进行下沉动画来实现愿望灯上飘的效果
图层
内部界面
开发中/已实现:
点击后直接进入,并展示
待开发:
点击后,弹出”将进入XXX功能,其待开发中”
产品未规划:
不显示该入口
重定向
域名切换:tke
通过页面分析,与产品和后台确认哪些页面需要进行接口拆分。
目前可能需要处理的有:
使用 image 或 json 实现loading动画
1 | static void showLoading(bool show); |
| 编号 | 页面状态PageType | 功能 | 人日 | |
|---|---|---|---|---|
| 1 | 加载失败 | errorWidget | 0.25 | |
| 2 | 加载成功,但无数据 | nodataWidget(含”重新加载”操作) | 0.5 | |
| 3 | 加载成功,且有数据 | successWidget | 0 |
通过传入不同的状态,显示不同状态下的页面
5、含状态管理的页面基类
1 | int currentPageType = successWidget; // 默认的状态页面 |
重构及增加错误码友好提示处理 1d
键盘收起
默认颜色+自定义背景颜色
状态获取
导航栏重用success上添加的
1
2
3
4
5
6
7 Visibility 隐藏/可见,能保存组件的状态;Offstate不能保存组件的状态,组件重新加载
* 控制child是否显示
*
当offstage为true,控件隐藏; 当offstage为false,显示;
当Offstage不可见的时候,如果child有动画等,需要手动停掉,Offstage并不会停掉动画等操作。
const Offstage({ Key key, this.offstage = true, Widget child })
白屏:
①处理请求前的场景:数据预加载+框架预染页(美团/饿了么的灰底效果)
②处理请求后的场景:文字提示+页面展示(无数据+无网络)
2、网络异常:
为页面提供网络异常页并伴有刷新恢复重试
3、程序异常(上报+提示):
接入bugly服务+自定义异常页
4、视觉体验优化
定义加载动画loading
愿望灯动画效果优化(底部下沉)
5、测试优化
新增查看版本页面,方便对应反馈的问题
新增代理设置,方便接口人员抓包排查
6、其他
AA送礼:提测,待测试验证后,如有问题修复
开发者账号申请审核未通过,正在处理。 (因为域名问题被拒,新域名已经准备好了,在跟apple沟通中)
下周:
继续我的模块开发,联调接口。
在完成”我的”模块或等待接口情况下,评估及开发产品规划的其他功能。
目前:
框架升级(白屏、预览页灰底、网络刷新恢复页,异常上报及异常页):今天
愿望灯 ok
许个愿 ok
我的:部分在联调,部分在等接口
AA送礼:提测,待测试验证后,如有问题修复
loading
开发者账号申请审核未通过,正在处理。 (因为域名问题被拒,新域名已经准备好了,在跟apple沟通中)
为了有更好的用户体验,我们对页面框架进行如下优化。
1、错误页和空白页的页面,需要自定义
1 | import 'package:flutter_effect/flutter_effect.dart'; |
buildInitWidget其他,如果你还想设置初始页面,目前初始视图默认是空白视图
1 |
|
设置背景图片
1 | // 背景视图(常用来设置背景图片) |
详细而全的文档:Pro Git(中文版)
回退命令:
1 | 在git push的时候,有时候我们会想办法撤销git commit的内容 |
1 | $ git reset HEAD^ 回退到上个版本,代码还在 |
当你有多处备份的时候,你可以强制性的执行
git reset –hard HEAD^
但是你刚commit上去,未push出去的就没法找回了。
网上的参考:pod install报错:ArgumentError - Malformed version number string
实际:通过执行sudo gem install cocoapods命令重装cocoapods即解决了。
出现 POST git-receive-pack (chunked) 的原因就是 当使用 HTTPS 提交到 Git 上时使用不检查加密要是东西过多将导致提交停止。
解决方法:
1 | 方案1> 使用 Git 提交代码 |
.png)
再次提交将会成功。
–
以下目录内容全部摘自:iOS中的单例模式
# 目录 * [一、什么是单例模式](#)
1 ...
1 | 1、内存问题 |
1、单例:dispatch_once (使用dispatch_once时,不用使用@synchronized)
单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
或者我的理解是:单例是一种类,该类只能实例化一个对象。
实现单例模式的函数就是void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
Apple的GCD Documentation证实了这一点:
如果被多个线程调用,该函数会同步等等直至代码块完成。
示例:在整个应用中访问某个类的共享实例
1 | + (NetworkManager *)sharedInstance |
就这些,你现在在应用中就有一个共享的实例,该实例只会被创建一次。
下次你任何时候访问共享实例,需要做的仅是:NetworkManager *networkManager = [NetworkManager sharedInstance];
2、线程的同步执行@synchronized
为了防止多个线程同时执行同一个代码块,OC提供了@synchronized()指令。使用@synchronized()指令可以锁住在线程中执行的某一个代码块。存在被保护(即被锁住)的代码块的其他线程,将被阻塞,这也就意味着,他们将在@synchronized()代码块的最后一条语句执行结束后才能继续执行。
@synchronized()指令的唯一参数可以使用任何OC对象,包括self。这个对象就是我们所谓的信号量。
用过环信SDK的同学应该对多代理不陌生了,请看下面代码:
1 | /* |
平时我们写得比较多的代理: @property (nonatomic,weak) id<EMChatManagerDelegate>delegate; 写了上面属性后系统会默认生成set方法: - (void)setDelegate:(id<EMChatManagerDelegate>)delegate; 通过对两个接口的比较就不难看出:单代理只能设置一个,而多代理可以设置多个,准确来说应该是多代理可以添加多个。
以上摘自:iOS 实现多代理的方法及实例代码
XMPP以及类似IM框架里通常会有这种需求:打开多个聊天窗口,和多个人聊天。然而框架底层消息转发管理器却只有一个。通常是这两个窗口都要收到消息回调,然后取自己有用的消息。。。
大概就这么个意思,我两年前用了下,具体也解释不清楚,欢迎指点交流,反正就是有一个需求需要多代理回调,这种IM框架通常有这种方法[xmppRoom addDelegate:self delegateQueue:dispatch_get_main_queue()];
以上摘自:iOS 多代理的实现
参考文章:iOS实现多重代理及应用场景
系统不是已经有通知中心NSNotificationCenter了吗?为什么还要自己实现一个呢?
举个例子,现在我们有一个模块需要抛一个通知出来,通知其它模块用户名改变了,我们来看代码大致是怎么写的
1 | // 发通知一方 |
从例子中可以看到有的缺点:
1.对于接收同一个事件的通知,不同的人可能会用不同的方法名来执行(例子中是UserNameChanged1和UserNameChanged1),无法统一。
2.对于多参数支持不方便。
所以本文我们的优化的是统一接收方的执行方法,并为该方法提供明确的参数。
背景描述如下:
vc中所做的某个操作(指一个操作),想要发送给多个人,让他们接收到信息后,自己处理。
我们假设接收者为
1 | DelegateReceivedViewModel1 *delegateReceiver1 = [[DelegateReceivedViewModel1 alloc] init]; |
①发送状态变化的信息如下:
1 | // OneToManyDelegateNormalViewController1.h |
②设置代理的地方会是如下:
1 | OneToManyDelegateNormalViewController1 *viewController = [[OneToManyDelegateNormalViewController1 alloc] init]; |
依赖注入:把做的事情交给某人。至于依赖的某人有什么能力,依靠外部决定。
参考文档:
注入可以通过对象的初始化(或者可以说构造器)或者通过特性(setter),即构造器注入和setter方法注入。
OneToManyDelegateNormalMediator21
1 | OneToManyDelegateNormalMediator21 *delegateMediator |
参考的其他文档:
EXTConcreteProtocol虽然没有直接叫做依赖注入,而是叫做混合协议,但是充分使用了 OC 动态语言的特性,不侵入项目,高度自动化,框架十分轻量,使用非常简单。
为了优化代码,我们从vc中抽离delegate,则此时
①设置代理的地方会是如下:
1 | OneToManyDelegateNormalMediator21 *delegateMediator = [[OneToManyDelegateNormalMediator21 alloc] init]; |
②发送状态变化的信息如下:
1 | // OneToManyDelegateNormalViewController21.m |
③此时delegateMediator中的delegate_didUpdateLoginState:方法如下:
1 | #pragma mark - TSDelegate |
为了减少设置代理的变量,我们使用delegate数组,则此时
①设置代理的地方会是如下:
1 | OneToManyDelegateArrayMediator31 *delegateMediator = [[OneToManyDelegateArrayMediator31 alloc] init]; |
②发送状态变化的信息如下:不变
③此时delegateMediator中的delegate_didUpdateLoginState:方法如下:
1 | // OneToManyDelegateArrayMediator31.m |
1 | // vc.m |
为了能够在Mediator中减少我们要实现每个代理的每个方法,我们进行如下优化:
1 | // vc.m |
对Mediator对象调用delegate_didUpdateLoginState方法时,因为含delegates的Mediator对象并没有实现协议中的方法,如delegate_didUpdateLoginState,所以,我们只能要么补充实现,要么不实现的话,就利用消息转发,将协议中的方法转发到自己delegate链中的对象。
1 | // vc.m |
此方式的问题:由于[self.delegateMediator delegate_didUpdateLoginState:YES];,所以delegateMediator需要在@interface支持@optional。
结论:基于此方式的问题,所以为了减少对其他的约束,我们这里最后采用2的优化方式。
举例:在vc.m中想要告诉大家去实现协议1中的showMessage方法,但是如果不说明是协议1中的方法,可能有个协议2也有同样的方法,导致执行的方法错了。
分析:这种情况是可能出现的,但是这种情况出现的时候,不会被引用到同一个监听者,不然分不清delegateReceiver中现在执行的是哪一个。
如原本名字不一样,我们区分得开。
1 | // viewModel.m |
我们直接在上述原本实现的基础上看问题,修改后为:
1 | // vc.m |
同理:添加delegate的时候,也
1 | if (![delegateReceiver conformsToProtocol:protocol]) { // 处理多个protocal中有同名方法,通过传进来的protocol参数,我们在判断接收者有我们想要的方法前,先找到有实现我们想要协议的那个接收者 |
场景:B页面发送一个信号,B页面的viewModel会去执行,当然还有其他地方也有可能执行。
那么当B发送信号出去,还在遍历执行的时候,突然某个从B页面返回A页面,B页面释放,B的viewModel也跟着释放,即这个信号的其中一个接收者B的viewModel被释放了,那么当执行的时候相当于给nil发送信息,不会崩溃。
1 | for (NSUInteger i = 0; i < self.delegates.count; i++) { |
需要注意数据安全的地方,应该是避免重复添加和重复删除。
所以,我们使用@synchronized来保护。
1 | @synchronized(xxx) { |
发送时候的核心代码,大概如下:
1 | // vc.m |
接收时候的核心代码,大概如下:
1 | // receiver.m |
答:因为delegate本身为了避免循环应用,所以其是弱引用对象。而要保存弱引用对象,我们不能够用NSArray,因为NSArray是强引用,而应该用NSHashTable。
1 | // 方法1:NSPointerArray |
这边用了NSHashTable来存储遵守协议的类,NSHashTable和NSSet类似,但又有所不同,总的来说有这几个特点:
NSHashTable中的元素可以通过Hashable协议来判断是否相等.
NSHashTable中的元素如果是弱引用,对象销毁后会被移除,可以避免循环引用.
如果你需要保存一堆block,并且希望它们能够响应特定的事件或操作,你通常会使用数组来存储这些block。然而,由于block在Objective-C中是对象,并且默认情况下它们会被强引用,你需要考虑循环引用的问题,特别是当block内部捕获了它们的捕获环境(例如,捕获了它们的创建者对象的强引用)时。
以下是几种处理block存储的方法:
__weak或weak修饰符:如果你的block捕获了它们的创建者对象的引用,你可以在block内部使用__weak(在Objective-C中)或weak(在Swift中)来避免强引用循环。NSHashTable或NSPointerArray**:如果你需要存储block的弱引用,可以使用NSHashTable或NSPointerArray,并设置它们的pointerFunctions属性以使用NSPointerFunctionsWeakMemory,这样它们就会存储block的弱引用。1 | @property (nonatomic, strong) NSPointerArray *blocks; |
其他参考文章: