翻新翻新,新开一篇
以后应该大部分都用swift来写,不写手生要废了。。。
19.06.28
swift中的@objc
补一下上周五的。。。周五去浪了。。。
我们的ERP老古董项目的swift部分还停留在swift3.0,拿一台mac更新了Xcode之后发现,好家伙,swift最低支持版本是4.0了,这就意味着swift部分早晚就要有个大变动了。
之前老觉得银行这类应用做的烂体验差还不改,现在也能体谅那种稳定为主能用就好的心态了。
说一下3.0和4.0比较大的差异之一吧,@objc的出现
swift虽然想通过编译的时候就尽可能地确定能确定的事情来提高性能,但其实还是离不开很多动态化运行时的事情,就离不开我们的老大哥OC。4.0之前在语法的要求上没有界定那么清楚,编译器也可能自动帮我做了一些事情,但是4.0开始,基于OC运行时的地方就要前缀上@objc,指明这里需要OC的特性去处理。
比较常用的一些地方:
1.代理中方法的可选类型
2.selector选择器选择的方法
3.观察者观察的某一个属性
...
只要是需要OC特性的都需要在其前面添加前缀@objc,当然你忘了也没关系,编译器会给报错修正提示你
这里有个地方要注意,就是加了@objc修饰之后,这个类型可能就不是一个完完全全的swift类型了。
举个例子:
@objc protocol caiXunKunSportProtocol : class {
func playBall(ballType:NSInteger)
func run()
@objc optional func canChoose()
}
extension caiXunKunSportProtocol {
func playBall(ballType:NSInteger) {
if ballType == 1 {
print("篮球")
}
}
}
@objc对协议的影响,会使得swift中协议的特性失效,上面extension中扩展了协议的默认实现,服从此协议的类可以不实现这个方法因为有默认实现,但caiXunKunSportProtocol被@objc修饰之后,这个特性消失,扩展的协议方法默认实现失效,服从此协议的类必须实现这个协议方法。
19.07.01
把轮播图的demo整理了下
写一点造轮子的想法,当然都是很小的控件轮子,目前的能力造不出大级别的轮子:
个人还是很倾向于造轮子的,虽然说造轮子耗时耗力,但是确实很锻炼个人的思维和能力。日常工作的时候很少有时间去造轮子,为了减少时间,大部分是几天前就开始考虑这个轮子的大体方案,如果要做的时候连方案都没有就暂时放弃,先用第三方的,后续参考第三方做出思路方案,如果能想到方案,就开始做一些初步的可行性分析,如果可行就开始自己实施。
19.07.02
关于collectionView和tableView的选取
因为之前写过一次瀑布流的效果,深知自定义layout的工作量有多大,布局完完全全地就是自己在计算在设置,所以从那次之后,能用tableview的地方就尽可能地用tableview,能用flowlayout的地方就尽量用flowlayout,万不得已不会自己坑自己去自定义layout。
上面说的有点夸张了,回到选取的方案上来,由于collectionView足够强大,tableview能实现的collectionView也能实现,但工作量可能提升了不少。那么如何对这两者进行一个合适的选取呢?我觉得主要考虑一下几个方面:
1.重用范围的覆盖。这里是最重要的,考虑好重用范围的覆盖有利于效率和以后的扩展。举个例子,如果一个页面中由很多大小不同的item组成的分区构成,如果每个item是可重用的(都是同一种或者很有限的种类),同时它又是规整的高度(不是瀑布流的形式),像下面这样:
section1:
item item item
item item2
section2:
item item
item item2 item
那么建议使用collectionView,因为此时最小的重用单元就是每个item,对于每个item我们也是可以控制其左右间距和itemSize的大小。你可能会说这种明显就是collectionView最简单了,首选肯定是collectionView,那么下面这种我相信很多人可能会选择tableView:
section1:
item item item
section2:
item item item
这种规整划一的结构tableView实现起来也是很简单的,这个时候你的选择就非常地重要了,tableView能做,以每行3个item进行重用,但是后期扩展如果item稍微一变化,就需要多个cell的种类,对于扩展性是非常糟糕的,如果改动过多,很可能你的tableView完全力不从心。
所以第一点非常要强调的就是:重用覆盖的范围,理论上来说,重用覆盖的范围越大即重用视图划分越细致,那么对于后期的扩展性就越好。
2.复杂度。说复杂度就要说layout,自定义layout令人头大,尤其是奇怪的规则多样的layout,所以从个人角度来说,我可能会尽量避免自定义layout。向下面这样:
item item item item
item item item
item XXX XXX
item******* XXX
XXX表示纵向继续向下延伸,***表示横向继续延伸。
对于这种我的思路是这样的:如果能抽出某块重用的视图,就用tableView,后期扩展先不考虑,如果不能抽出,普通的flowLayout显然不能满足,需要自定义layout,此时请注意,这个排列规则一定要让产品写清楚,这个规则是你自定义layout的基础,拿到这个规则不要忙着开工,多想想规则是否有缺陷是否需要改进或者优化,不然中途做一半了再又变动真的是头疼*2。
按理说这里应该是用自定义layout的,但由于自定义layout复杂度不一,而且坑也不少,所以如果难度太大超过自己可控范围不建议自定义layout。
3.tableView行不通的,只能用collectionView的。一般来说这种被限制会出现在多列且高度不统一上,比如瀑布流的效果,这样一来如果是tableView就找不到完全可以重用的模块,只能去使用collectionView,也就只能老老实实去自定义layout了。
19.07.03
gpx文件配置好像对一些APP失效了
这两天在用模拟定位的时候发现一些APP不能被模拟了,不知道是限制了什么还在找原因。
gpx文件可以让我们配置一些经纬度用在测试时候的定位模拟:
1.创建一个gpx文件,cmd+n,选择下面这个文件:
GPX文件
2.在新生成的gpx文件中输入我们需要的经纬度,由于你使用不同的的拾取坐标系统有差异,所以转换后的也可能会有点差异。
配置好之后将其进行关联,点击Edit Scheme,Options选项卡下面:
关联配置
运行起来就可以用了,这个模拟是全局效果,理论上所有的APP都是可以被模拟到的,但最近不知道怎么了,有些APP似乎加强了位置的校验,导致不能被模拟。目前还能被模拟的有高德、京东、美团外卖等等。有空测试下不能被模拟的原因,初步猜测可能是wifi或者sim卡移动信号再次做了位置校验。
19.07.04
ReactiveCocoa要解决的问题
才开始了解ReactiveCocoa,目前的工程中也只是局部用了ReactiveCocoa,没有大范围地使用。网上很多关于ReactiveCocoa的文章只是简单地告诉你怎么简单地使用,并没有一个系统地讲解。去搜集比较核心的问题的时候也是比较困难,总结一下我自己搜集到的和一些理解。
ReactiveCocoa要解决的问题。
我们的APP对事务的处理可以看做是一个流,我们把各个要处理的事务放到这个流中,A->B->C...依次处理。但总有一些特殊的事务:
1.耗时的事务
2.互相依赖的事务
3.引起流变化的事务(改变当前事务的同时会对其他事务产生影响)
这类事务如果只是单纯地放入我们的事务流中显然是不可行的,所以我们要特殊处理他们,于是异步任务就承接了这类事务,异步的出现像是我们的主事务流中分出了一些小流,这些小流单独承接了某个或多个事务的处理,处理完毕之后汇入大流中,这样一来,我们的主流依旧流畅,不受这类事务的影响。
到了这里,之前的问题似乎都得到了解决,但引入异步的解决方案便会带来异步的问题:如何组织管理这些异步事务和依赖事务。
这样描述可能并不形象,举一个形象点的例子:多个网络请求如何等其完全回调之后进行处理?一个表单填写完毕之后如何对填充的数据逐个校验之后再进行下一步?如何及时响应某个多变的事务?
显然,目前的状态下我们可能要写不少组织这些事务的操作。ReactiveCocoa就是为解决这些事情而生。ReactiveCocoa在不影响异步事务的情况下更好地管理这些事务流,使每个事务流都能很好地“关联”在一起,汇入主事务流中。可以说ReactiveCocoa是一个管理事务流的框架。在这个框架下,所有的事务都能产生信号和被订阅信号,这样一来,不同的事务的通讯就很简单了。你可能会说,通知也能实现啊。但是通知的方式使得代码结构混乱,毫无聚合,在开发和维护上非常弱势。而ReactiveCocoa响应式的框架,能使得代码高聚合低耦合,统一使用了信号的方式,使得代码结构统一且清晰。
19.07.05
如何学习一个新的框架
本来准备周五写的,周五还没把RAC全部概览一边,所以等着概览之后再来记录更有体会,把我个人的感受总结一下。
RAC其实很早就导入了,也有一小部分代码在用,但也很局限于几个方法中,例如同步网络请求、合并多个信号为一个信号等常用的几个用法,没有去扩展也没有去了解过内部的结构。周末的时候对整个框架做了一个大体的概览,又有了进一步的认识,正好也把学习新框架的感想记录一下。
我们可以像看书一样,去学习一个新的事物:
1.了解主题和思想。首先我们应该知道,要使用的这个框架要做什么事情,为了解决什么问题,用了什么方式去做;
2.了解目录,也就是整个框架的构成。可能一开始并没有一个现成的目录让我们去了解,我们可以搜集一下网上的资料进行一个大体目录的梳理,这一步也就是对整个框架一个初步的了解,只需要只要有些什么,什么和什么好像有联系等等,不用深入去了解。
3.了解重要的章节,也就是框架中比较核心的几个部分。比如RAC中,比较重要的有信号、command、多播等等,这些比较重要的是需要我们特别去深入了解的;
4.遇到不会的先做记录,快速推进。我们在学一个新东西的时候经常会遇到有不会不懂的地方,如果卡在了一个地方而短时间内又无法有突破不妨先将其放一边,继续向前推进;
5.找到联系,融会贯通。如果不是系统性地学习一个事务,我们学到的很大一部分都是点状的,学了知识点A,再去学知识点B、C、D等等。有很多的记忆法,都是基于事物之间的联系。这里我们也可以借鉴这种,上面第4点说道快速推进,是为了能获得更多地点,当我们点足够多,就能尽可能多地理清点和点之间的联系,有了联系能帮助我们更好地理解这些点,有些学着学着就自然而通;
6.冥想,梳理学到的。学而不思则罔,说的就是要思考。无论是从书本上还是别人的总结中获得的知识,都需要你自己有一套自己对其的认识才算是学到了,否则只是简单地对其做了一次记忆copy。经常思考有助于对知识的加深和理解,温故而知新。
19.07.08
swift和OC中的方法
OC中的方法只认方法名和参数个数,无视参数类型,而swift对于参数类型也做了限制,如果参数类型不符合,则不认为是同一个方法。
这个问题出现在我想在swift用OC的一个创建cell的写法中,这个写法是这样的:动态创建cell
每个cell对应一个model,在子类cell中重写父类的赋值方法,利用多态的特性,方法的参数由baseModel变换成子类的model。在OC中这样玩的顺风顺水,但是到了swift中,重写父类方法变得不可行,因为参数类型不一样,swift并不认为这是覆盖了父类的方法,override不可用。
暂时还没想好swift中如何去完全替代OC中的继承+工厂模式,明天想一想
19.07.09
处理昨天的问题
一开始想着如何使用协议的方式去实现,没有想出比较好的方案,暂时先用最简单的方式去解决吧,swift中的泛型
测试文件:one 或者 two 均继承自base
测试文件
在基类中我们写入某个方法,参数定为泛型:
func setUpColor<T>(model:T) {
print("基类的方法")
}
在子类中我们去重写这个方法,并做对应的实现,例如twoModel和twoView中:
twoModel中有一个变量名name:
class oneModel: BaseModel {
var viewColor : UIColor = UIColor.red
}
twoView中重写基类方法,并指定参数model为对应类型的model
override func setUpColor<T>(model: T) {
if let model = model as? twoModel {
print(model.name)
}
}
在外部调用上,用了一组测试数据:
let classNameArray : Array<String> = ["Play.BaseView","Play.twoView","Play.oneView"]
let modelNameArray : Array<String> = ["Play.BaseModel","Play.twoModel","Play.oneModel"]
var viewArray = Array<BaseView>()
var modelArray = Array<BaseModel>()
for str in classNameArray {
if let cls = NSClassFromString(str) {
if let cls = cls as? BaseView.Type {
let view = cls.init(frame: CGRect.init(x: 10, y: 10, width: 10, height: 10))
viewArray.append(view)
}
}
}
for str in modelNameArray {
if let cls = NSClassFromString(str) {
if let cls = cls as? BaseModel.Type {
let model = cls.init()
modelArray.append(model)
}
}
}
//赋值
for i in 0...2 {
viewArray[i].setUpColor(model: modelArray[I])
}
这样一来,基本上已经和OC版本的一致了。
swift中的泛型T似乎就是为了解决这类问题而生的,通过泛型的使用,使我们能运用到多态的特性,不过在这方面上OC感觉更加“如鱼得水”一些。
之前有被大佬说过swift有自己的特性,如果一直拿OC的思想去写代码,就算你设计模式用的再好,也只是把语言单纯地换下而已,并没有发挥swift真正的优势。Emmm,准备抽空好好感受下。。。
19.07.10
swift闭包对变量的捕获
swift的闭包和python的闭包几乎是一个模子中造出来的,两者不论是从语法上还是结构上都非常的相近。扩大一下闭包的范围,我个人觉得block就是闭包的另外一种体现,闭包这种形式的构造在python中非常常用,例如python中装饰器上的闭包,在swift中也是比较常用的,但可能是受OC的影响,个人更喜欢用Block,觉得闭包的写法更偏向于C的函数式写法,所以用的也比较少,今天梳理一下闭包对于外部临时变量的捕获。
来一个小的例子:
func FuncBlock() -> ((NSInteger)->(NSInteger)) {
var num = 0
func addNumber(x:NSInteger) -> NSInteger {
num += x
return num
}
return addNumber
}
这是一个比较简单的闭包,内部函数addNumber捕获了在FuncBlock中的num这个临时变量。我们进行一次打印:
let fuc = FuncBlock()
print(fuc(1))
print(fuc(2))
print(fuc(3))
1
3
6
我们可以看到临时变量num好像延长了它的生命周期,它并没有随着FuncBlock的结束而结束。
这是由于内部函数addNumber对num进行了捕获,捕获指的是获取到num的内存区域并进行读写,在addNumber的生命周期中保证了num的生命周期。如何证明内部函数addNumber对num是“引用”了原始的地址,而不是值的copy呢?
我们加入地址打印看一下:
func FuncBlock() -> ((NSInteger)->(NSInteger)) {
var num = 0
withUnsafeMutablePointer(to: &num, { (ptr: UnsafeMutablePointer<Int>) in
print(ptr)
})
func addNumber(x:NSInteger) -> NSInteger {
withUnsafeMutablePointer(to: &num, { (ptr: UnsafeMutablePointer<Int>) in
print(ptr)
})
num += x
return num
}
return addNumber
}
0x0000600001d65030
0x0000600001d65030
1
0x0000600001d65030
3
0x0000600001d65030
6
可以看到从一开始到被捕获读写,num的地址在内存中没有变化。
这说明闭包对外部变量的捕获是以引用的方式进行的捕获。
19.07.11
正弦曲线
高中学的知识,长期不用早已忘得一干二净,最近要用到,捡起来梳理一下。
正弦函数y=Asin(ωx+φ)+k
A :振幅,表示曲线的波动高低;
ω :角速度,表示多少为一个曲线周期或者;
φ: 初相,表示X轴上的偏移;
k : 偏距,表示Y轴上的偏移;
通常我们会将公式和贝塞尔曲线一起使用,画出一条正弦的曲线,像下面这样:
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
for (CGFloat i = 0.0; i < 200; i++) {
CGFloat point_Y = 10 * sin((M_PI * 2)/200.0 * i) +20;
[bezierPath addLineToPoint:CGPointMake(i, point_Y)];
}
如果要使其做出波浪的动画效果,则需要使φ不停地移动,相当于每间隔φ的距离快速重绘一次线条,达到动画效果,像下面这样(需要计时器不断调用displayOffsetX方法):
- (void)setUpFuncSetting {
//以下三项是可以确定配置的,动画效果由sin_X的变动产生
sin_A = 10;
sin_W = (M_PI * 2) / self.bounds.size.width;
sin_C = self.bounds.size.height * 0.5;
sin_CumulativeData = 0;
}
#pragma mark 处理X轴上的偏移累加,只有不断累加重新画Path才有动画效果
- (void)displayOffsetX {
sin_CumulativeData += sin_W * 0.01 * self.bounds.size.width;
[self drawPathWithCumulativeData:sin_CumulativeData];
}
#pragma mark 动画效果,绘制路径waveLayer的路径
- (void)drawPathWithCumulativeData:(CGFloat)cumulativeData {
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(0, sin_C)]; //起点
//正弦曲线部分(用float类型使曲线更加细腻)
for (CGFloat i = 0.0; i < self.bounds.size.width; i++) {
CGFloat point_Y = sin_A * sin(sin_W * i + cumulativeData) + sin_C;
[bezierPath addLineToPoint:CGPointMake(i, point_Y)];
}
self.waveLayer.path = bezierPath.CGPath;
}
19.07.12
weakSelf和StrongSelf
找些资料的时候无意中搜到了这个,也算是当时遗留的一个问题,顺便查阅了一些文档和别人的见解,自己梳理了一个思路去理解:为什么block中需要再次使用StrongSelf去引用一次weakSelf。
为了解决循环引用问题,我们经常会用weak去修饰self,使其引用计数不+1,这点理解起来没有任何问题,但推荐的写法是在block内部再次使用Strong去修饰weakSelf,这点理解起来有点奇怪,我外部已经使用了weak做了弱引用处理,内部再用Strong不是会引起循环引用吗?
我的理解是:Strong和weak在编译之后的状态是不同的,weakSelf会被放在Block中成为Block结构体的一部分,被Block所持有,而StrongSelf则只是一个普通的内部作用域的变量,并没有成为Block结构体的一部分,但使指向对象引用计数+1,造成了循环引用,避免提前释放产生不可预期的一些后果,保证回调执行完毕。这点是非常重要的,当Blcok作用域结束,这个变量的生命周期也就结束了,相当于强制将其置为nil,此时循环引用打破。
If ‘weakSelf’ is equal to ‘self’, then ‘strongSelf’ retains it, and it stays retained until the block returns, when its released. It’s all or nothing.
参考文章:文章
19.07.15
解决非weak形式下的循环引用
这类一般是加入到runLoop中的定时器、通知、WK或者UIWeb与JS的交互引起的。
1.针对控制器类型的,善用viewWillAppear和viewWillDisappear这一对组合。例如WK中的ScriptMessageHandler,我们在viewWillAppear中添加addScriptMessageHandlerForName,在viewWillDisappear中removeScriptMessageHandlerForName。一些文章在解决这类循环引用中喜欢引入一个中间类,我觉得是非常没有必要的。
2.针对视图类型的,使用willMoveToSuperview解决。一些视图控件中往往加入了NSTimer、CADisplaylink或者通知等会造成循环引用的方案。对于视图来说,比较好的方式就是在willMoveToSuperview这个方法中处理循环引用的问题,举个解决CADisplaylink的例子:
#pragma mark 父视图状态变化,此处方式内存泄露
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
//还存在父视图则将计时器放入loop
if (newSuperview) {
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
//不存在则移除loop,同时将其置位失效和nil
[self.displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.displayLink invalidate];
self.displayLink = nil;
}
}
这里通过判断其父视图是否为nil来处理持有的CADisplaylink属性。
19.07.16
没有被指针引用生成的对象会被立即释放
一直以来我都以为OC中对象被释放的时机都是在loop的一次循环迭代后,直到下面的一种情况出现:
对象释放时机测试
在A_Model和B_Model的dealloc中分别打印了A被销毁了和B被销毁了。按照之前的认知分析,我觉得这里生成的对象释放的时机都会在当前的loop循环迭代之后,结果却不一样:
测试对象被销毁的顺序
可以看到,有指针指向的对象释放时机符合猜想的逻辑,但是没有指针指向的对象却立即释放掉了,这和之前想的有些不一样,之前认为对象生成之后都会放到loop循环中等待被释放的时机,现在看来没有被用到的对象是会被立即释放的。怎么去解释这个地方呢?我个人理解是被指针所指的对象,指针的作用于是在当前的方法中,首先会保证在方法结束之前这个对象是有效不会被释放的,方法结束跳出作用域之后,这个时候对象就被放到loop中等待合适的时机被释放;如果没有指针所指向也没有任何其他对象对其引用计数加一,那么这个对象就是创建即销毁的状态,它不需要再被加入到loop的循环中,立刻执行了释放。(可能不准确欢迎指正)。
19.07.17
被指针指向的对象释放的顺序
以下仅讨论有被指针指向的对象。
还是上面的例子,我们可以看到A对象比B对象释放要晚,这是偶然还是必然呢?这里是必然的。
autoreleasepool是由若干个autoreleasepoolpage以链表的形式组成的,而autoreleasepoolpage的结构是栈。在每个loop的迭代开始,系统自动插入autoreleasepoolpush,开始将这次循环迭代的事物加入到page中,A对象先生成则先加入,B对象后生成则后加入。在循环迭代结束后插入autoreleasepoolpop,一个loop的循环迭代就生成了。我们知道栈是先进后出的数据结构,当进行释放的时候,从pop开始往上依次进行,显然是后加入的B先进行释放,而A则在B之后进行释放。
19.07.18
分类没被import,属性或者方法不能被调用
今天又碰到这个讨论了,写了分类之后没有import是否能直接使用(OC),明显是不能的,我们做个简单的测试也是可以测试出来的。OC中对文件的引入必须要手动import一次,单纯的创建文件是无法被引入的,LLVM的前端直接就可以反映出来(没有联想),编译期也会报未找到的错误,分类也是同样的。
swift中是可以的,swift中的文件是不需要import的,默认全局都可以引用到。
19.07.19
谈谈离职和面试
今天签完字拿到离职证明就彻底走完了离职程序,算是结束了在成都的首个工作。
上周一提的离职,离职原因的话是多方面的吧,周一当天也没有什么因素刺激我,就是觉得不舒服想离职了,这个时候还没找工作,裸辞吧,提了申请,定到今天完成离职流程。
虽然说自己目前没有负债,不过裸辞也要顶着不少的压力,不能当个闲人啊,所以还是要找下工作。
我一直觉得如果想要更好的一方面是努力,而一方面是敢于舍弃当下安稳的现状。后者随着年龄的增长和认知的不断变化,可能会越来越困难。
周末准备了两天,这周二到周四约了四个面试。成都的面试机会还是不少的,定向投了点,没有海投,觉得海投意义不大,不如筛选一下。
谈一谈面试吧。每次面试总会遇到一些知识盲区,也算是一些收获吧。 这四场面试中有纯面试,有机试+面试,也有笔试加面试。机试没有当场上机,在家写好就行,我觉得还比较人性化。笔试的话,有一家笔试我做的比较差,算法+编译期内存的一些先后分配,基本全错。这里我承认这方面我很薄弱,但我还是想吐槽一下,笔试真的没必要出这种很带偏向的题,不应该是更注重基础和平常比较常用的么?例如编译之后的一些顺序,如果不是对编译有所了解或者是测试过,普通的开发者都是全靠猜,这些题真的意义不大。包括面试中一些很奇葩的题,如何在App的UI中显示你的git log,我觉得这些题除了刁难一下真没什么用,当然我也有我自己专门研究过比较偏门的,拿出来也很多人不知道,但我面试从来不问,这些没什么意思。
有两家我觉得面试的效果还是很好的,直接上名字了:tap4fun和天呈集团。前者按简历所述提问,加之一些扩展,后者又结合了点实际项目。我觉得这种面试官就是会面试的,去发现应试者会什么,会到什么程度,而不是准备一些奇奇怪怪的题专门问倒应试者。
目前收到了两个offer,不过和期望薪资还是有点距离,下周再努努力看看。
19.07.22
基础框架的搭建
一个项目就像是盖一栋楼,基础框架的搭建像地基一样重要,尤其是对于团队开发来说,地基搭的好多人配合效率就会很高,反之会给团队开发效率打上很大的折扣。
基础框架的搭建我觉得主要从以下几个方面来说:
1.视图框架的搭建
2.工具类和基类的抽调
3.三方库的选择
4.编码的规范
5.代码管理的规范
6.代码和可视化
这里我比较想先说最后一个,代码管理的规范。
我个人倾向于使用git管理代码,说一下我觉得比较规范的管理流程:
1.master主分支
2.任何新的需求都是从master单独拉取的分支,即使只是改动了一个字段,也要严格按照功能需求拉取分支;
3.每次发版建立发布版本分支,eg:publish_20190722,所有需要在这次版本上线的功能都合并到此分支上,提测使用此分支;
4.只有publish版本上线成功(通过审核),publish版本才可以合并到master分支;
5.上线成功之后通知其他人员及时更新master及合并至未上线的功能上;
6.关于个人分支是否推送到远端,我个人倾向于推送,防止本地代码丢失;
19.07.23
继续昨天的
1.视图框架的搭建:
这部分我觉得主要收业务驱动加上自身产品的风格,大众化的基本上tabBar+nav就能满足需求,这里没有过多的要求,主要看产品的设计;
2.工具类和基类的抽调和三方库的选择,这两个放到一起来说:
同一功能的三方或者工具类只保留一种;
建议由团队商议后由一个人单独添加;
工具类最好有统一的分类和规范,不能过于冗杂;
3.编码的规范
主要涉及到注释和命名上,这点不再多说,主要想说下关于代码设计上的问题。举个例子来说,一个VC如果过于臃肿,我们会将一部分业务逻辑提到model层或者用分类分散代码。这其中包括很多方面,网络server层的放置,view保证复用的处理方案,model做不做业务处理做哪些业务处理等等,需要尽可能地作出统一的方案,使整个项目代码结构清晰统一。
4.代码和可视化
19.07.24
项目要不要使用需要学习成本的新技术
这里有个前提,就是这些技术是已经成熟且经过认证的技术,比如RAC,而不是刚出来一两个月还处在概念阶段的技术。
虽然这类框架有不小的学习成本和对原有代码层面的入侵,但是我个人倾向是使用的,例如RAC的方案的引入,综合学习成本和带来的收益我觉得完全可行,尤其是可以从局部开始使用,再慢慢扩大,有一个循序渐进的过程。但类似组件化的框架就不一样了,这类框架对原有的体系入侵太大,不建议在非新项目中进行使用。
一般来说,如果是团队开发就需要考虑到整个团队对新框架的熟悉程度,相应地学习成本也会提高,但程序员是需要面对技术的更迭的,新技术的使用不仅是挑战也是机会,个人比较倾向新技术的使用。
19.07.25
电脑重新装了一遍cocoapods,记一下目前的坑
重新装了一遍,发现有点新坑:(以下均在有梯子的时候遇到问题)
1.安装Homebrew遇到的问题
解决方案参考:https://blog.csdn.net/qq_35624642/article/details/79682979
2.执行pod setup时遇到的问题
解决方案参考:https://www.jianshu.com/p/89211d2ddeac
19.07.26
善用git stash
stash顾名思义贮藏的意思,它的作用是:保存临时代码并清空当前所有的文件变更,给出一个干净的工作台。通常用在当前项目还不足以做一次提交又需要一个干净的工作台来开展新的任务时,算是一个比较常用的命令。但在实际开发的时候,我们往往会随意commit一次,用此方法去建立干净的工作台,一般来说这种方式也没有太大的错误,但是会积累很多无效的commit节点,在回滚或者查看日志的时候使得整个flow流冗杂不清晰。团队开发的时候也会造成很多不必要的麻烦。而stash的方式只会在本地保存代码片段而不会在flow上留下痕迹,使得整个flow不被污染。
19.07.29
复杂表单页面的处理思路
由于移动端在日常生活中的使用场景不断增多,很多像下面这种表单页面也在逐步向移动原生端靠拢:
表单页面.png
或者比其更复杂,cell种类更多。这种页面其实也不难,流水布局也能写出来,但想设计好却有些难度。
之前的项目偏向ERP,很多类似这种的表单页面,总结下这方面的思路。
1.由于样式多样,VC为了去除不必要的if判断,采用了工厂模式利用model创造对应的cell;
2.模型中的基类为了满足基本的需要,像下面这样:
/**
标题
*/
@property (nonatomic,copy) NSString *title;
/**
副标题
*/
@property (nonatomic,copy) NSString *subTitle;
/**
展示的值
*/
@property (nonatomic,copy) NSString *displayValue;
/**
提交的值
*/
@property (nonatomic,copy) NSString *submitValue;
/**
占位
*/
@property (nonatomic,copy) NSString *placeholderValue;
/**
要实例化的类名
*/
@property (nonatomic,copy) NSString *className;
以上的字段基本上满足普通类型的使用
3.cell的基类其实只做了左边的标题这一个作用;
4.由于cell样式多样,我想尽可能地去除if的判断,只能选择字符串映射的方式,或者在基类中提供公共接口,子类返回对应的方法字符串或者selector;
5.数据源部分按照模型基类即可,向下面这样:
数据源.png
之前我的写法是直接写在文件中,后来发现这样会越来越乱,维护起来不方便直观,建议配置plist文件,或者由后台返回;
6.交互上,输入类型进行自我校验,利用submitKey进行判断需要进行校验的方式,这里没有想到合适的方式去规避if判断;
整体框架搭建起来的时候还是有些麻烦,但是对于后期的扩展和维护非常友好,逻辑也比较清晰,目前我自己的工程中涉及到表单填写的部分均用的这种方式。
19.07.30
子类拥有不同的方法实现
有个有意思的需求,封装个单例类,实例化之后同一个方法根据状态不同具有不同的方法实现。
这个需求很自然就想到了用runtime去实现:
1.拥有一个公共调用方法:funA;
2.真实需要去实现的方法:funB,funC,funD...
3.在状态改变的时候,用具体的方法替换掉funA;
当然,一般方式也是可以实现的,我们替换掉上面的第三步:
3.根据不同的状态由funA再去分发各个funB,funC,funD;
另外runtime方法替换的方式有一个弊端,就是只能在单例中做应用,因为方法是全局替换的效果,而对象则会生成多个,无法应用到多对多的场景。
19.07.31
SEL和IMP
iOS方法调用.png C语言函数调用.png从上面的图中可以看出,OC在方法的调用上相对于C语言多了一层SEL的映射,称之为发送消息。
当然,我们也可以直接拿到一个方法的IMP指针,直接进行IMP的调用,但为什么要增加一层SEL到IMP的映射呢?
和C语言一样,OC也是编译性语言,编译性语言的弊端就是大部分工作在编译期间已经确定好了,无法在运行时动态地改变。而OC中的runtime改变了这一点。
在方法的调用上,OC并不直接使用IMP指针,而是增加了一层SEL到IMP的映射,这就为运行时的动态添加了诸多可能性。如果我们单纯地使用IMP指针进行方法的调用,在编译期间已经确定了我将调用哪个IMP,运行时也很难改变;而添加了一层SEL层的映射,在编译期间我确定了调用的SEL,SEL确定IMP,实际上间接调用了IMP,而在运行期间,我可以改变SEL到IMP的映射关系,使得A方法可以进行B的调用,大大增加了运行时的动态特性。
19.08.01
swift的值类型和OC中的对象类型
swift以结构体的方式构建了基础类型,这点和OC的对象类型有本质的差别,那么诸如NSArray和Array这些类型在使用上就会有很多的不同:值和引用的问题。
为了方便拿数组类型说事。
OC中的容器类型有深拷贝和浅拷贝一说,如果对这点没有什么疑惑,那么对swift中的值类型赋值也没有太大问题。
Array是结构体类型,又是容器类,它的赋值相当于一次copy,至于是深拷贝还是浅拷贝,要看是用var来修饰还是let来修饰,前者可变而后者不可变,而对于容器类,无论是深拷贝还是浅拷贝,都是单层相对于容器来说的,容器内部的元素还是之前的元素,并没有单独开辟空间去生成新对象。
那么这就会有一些问题:OC中我们可以引用一个可变数组去做增删,对这个数组的操作是对所有指向该对象的元素同步的,因为访问的同一内存区域,像下面这样:
NSMutableArray *qq = [@[@"dd"] mutableCopy];
NSMutableArray *cc = qq;
[cc addObject:@"123"];
NSLog(@"%@ -- %@",qq,cc);
NSLog(@"%p -- %p",qq,cc);
2019-08-01 Use[50515:1529091] (
dd,
123
) -- (
dd,
123
)
2019-08-01 Use[50515:1529091] 0x6000024d4570 -- 0x6000024d4570
是一个同步的操作,而对于swift就不一样了,由于容器变化了,我们对其中一个容器进行增删对另外一个容器就不会产生影响,像下面这样:
let pp = people.init()
let stu = student.init()
stu.name = "abc"
pp.ttArr.append(stu)
pp.ttArr.append(student.init())
var tempArr = pp.ttArr
tempArr.append(student.init())
tempArr[0].name = "123"
print(tempArr)
print(pp.ttArr.self)
print(pp.ttArr[0].name)
print(tempArr[0].name)
[<RotaryTableAnimationDemo.student: 0x600001585e40>, <RotaryTableAnimationDemo.student: 0x600001585f80>, <RotaryTableAnimationDemo.student: 0x600001585f20>]
[<RotaryTableAnimationDemo.student: 0x600001585e40>, <RotaryTableAnimationDemo.student: 0x600001585f80>]
123
123
可以看到tempArr比pp.ttArr多了一个元素,说明两者已经是不同的容器。值得注意的是我们在对tempArr数组中第一个元素进行修改的时候,pp.ttArr第一个元素也产生了相应的变化,说明容器内部的元素还是引用的方式。
这些问题可能会出现在多个页面共用同一数据源的情况,值得注意一下。
网友评论