2019年WWDC大会上,苹果在压轴环节向大众宣布了基于Swift语言构建的全新UI框架——SwiftUI,开发者可通过它快速为所有的Apple平台创建美观、动态的应用程序。推荐大量使用struct代替类。
SwiftUI 就是⼀种声明式的构建界面的用户接口工具包。
SwiftUI使用声明式的语法构建UI,我们只需要向系统声明UI的View样式,以及View如何转换状态,其他的过程都交给系统去处理。
SwiftUI的界面不再像UIKit那样,用ViewController 承载各种UIVew控件,而是一切皆View,所以可以把View切分成各种细致化的组件,然后通过组合的方式拼装成最终的界面,这种视图的拼装方式提高了界面开发的灵活性和复用性。因此,视图组件化是SwiftUI很大的亮点。
SwiftUI是声明式的构建方式,我们只需要声明好界面系统会自动转换状态,搭建界面更加的简单
声明式语法和指令式语法的区别:
声明式的我们需要提前声明好每个view的各种状态,以及状态转变的条件。后续界面和用户在互动时,系统会帮我们自动进行状态切换。
指令式的我们需要给每个view先设置好默认状态,后续界面和用户在互动时,需要通过指令不停的去转变view的状态
因此声明式的UI是提前声明好各种状态,系统会自动帮我们进行状态切换。指令式的UI是通过我们设定的指令来转换状态
比如界面调整、用户交互、机型适配,UIKit都需要手动调整view,对于SwiftUI我们只需要声明好我们想要的样式,系统会帮我们去调整view。
可以这么说,SwiftUI相比于UIKit更加的抽象化了
统一苹果终端
在 SwiftUI 出现之前,苹果不同的设备之前的开发框架并不互通,增加开发者所需消耗的时间精力,也不利于构建跨平台的软件体验
SwiftUI具有了跨平台性,苹果的平台都可以使用,iOS、macOS、tvOS、watchOS
降低界面开发难度
UIKit 的基本思想要求ViewController 承担绝⼤部分职责,它需要协调 model,view 以及⽤⼾交互。这带来了巨⼤的sideeffect 以及⼤量的状态
SwiftUI是声明式的构建方式,我们只需要声明好界面系统会自动转换状态,搭建界面更加的简单
更加高效
默认使用Metal渲染,性能非常高,比UIKit要好
更扁平化的内联数据结构去分配内存,值类型。占用内存很少(所以在轻应用的开发更适合使用SwiftUI)
代码量相比UIKit要更少,效率更高
更好的配合Swift语言
SwiftUI 使用了大量 Swift 的语言特性
声明式语法
与UIKit布局相比,更加的抽象化,只需要向系统声明界面样式以及样式变化条件,其他的系统会帮我们实现,不需要我们自己去调整视图
链式调用属性
链式调用是 Swift 语言的一种特性,就是使用函数式编程,可以像链条那样不断地调用函数,中间不需要断开。使用这种方式可以大大减少代码量。
除了系统提供的属性可以使用之外,开发者也可以进行自定义
例如将不同字体、字号、行间距、颜色等属性统合起来,可以组合成为一个叫「标题」的文字属性。之后凡是需要将某一行文字设置成标题,直接添加这个自定义的属性即可,使用这种方式进行开发无疑能够极大的避免无意义的重复工作,更快的搭建应用界面框架。
界面元素的组件化
UIKit耦合了很多的操作逻辑,很难进行移植,更遑论组件化了
而SwiftUI仅仅声明界面样式,所以是可以将复杂视图的拆分出来组件化
甚至还可以在其他平台使用,以此跨平台
一般我个人会将视图组件区分为基础组件、布局组件和功能组件
与UIKit互相兼容
把 UIKit 中已有的部分进行封装,提供给 SwiftUI 使用。开发者需要做的仅仅是遵循UIViewRepresentable协议即可
并且在已有的项目中,也可以仅用 SwiftUI 制作一部分的 UI 界面
两种代码的风格是截然不同的,但在使用上却基本没有性能的损失。在最终的运行效果上,用户也无法分辨出两种界面框架的不同。
真实数据源(Source of truth)(重点)
SwiftUI中的数据源一定会是真实的,也就是准确的
在OC中,一个view的状态由多种因素导致的,不同的来源,不同的逻辑操作(因此需要考虑及时更新界面)
因此在Swift中,提供了单一数据源的说法
只要在属性声明时加上 @State 等关键词,就可以将该属性和界面元素联系起来,在每次数据改动后,都有机会决定是否更新视图。
系统将所有的属性都集中到一起进行管理和计算,也不再需要手写刷新的逻辑。
因为在 SwiftUI 中,页面渲染前会将开发者描述的界面状态储存为结构体,更新界面就是将之前状态的结构体销毁,然后生成新的状态。
而在绘制界面的过程中,会自动比较视图中各个属性是否有变化,如果发生变化,便会更新对应的视图,避免全局绘制和资源浪费。
使用这种方式,读和写都集中在一处,开发者就能够更好地设计数据结构,比较方便的增减类型和排查问题。而不用再考虑线程、原子状态、寻找最新数据等各种细节,再决定通知相关的界面进行刷新。
UIKit 的基本思想要求ViewController 承担绝⼤部分职责,它需要协调 model,view 以及⽤⼾交互。这带来了巨⼤的sideeffect 以及⼤量的状态
SwiftUI不仅为Apple的平台带来了一种新的构建UI的方式,还有全新的Swift编码风格;
可以推断出:SwiftUI会出现很多组件库,方便前端开发;
支持热更新,这一点可能让更多的开发者拥抱SwiftUI;
虽然SwiftUI优点很多,但是其使用的门槛很高,只能在iOS 13以上的系统使用;仅这点,很多公司和开发者望而却步,目前主流应用最低支持iOS 9,至少3年之内,SwiftUI只能作为一个理论的知识储备,所以其还有很长的路要走;
说了它的优点,再说下它的缺点:
1.由于大量Struct来实现页面,甚至事件处理也写在Struct里面。要知道在Struct里是无法打印日志,这样造成无法建立强大的日志跟踪功能。一个好的软件一定要一个强大的日志跟踪系统。上次我开启了苹果的iOS内存日志功能,发现两个页面切换一次打印的日志就达到50兆–100兆的日志。可见苹果的内存管理好,是靠多少人的不断努力和打印跟踪内存分配和使用,只是对我们应用层开发者屏蔽了大量底层细节。自己的方便是建立在他人辛苦上的,没有无缘无故的方便。
2.由于大量Struct来实现页面,它没有deinit(类似oc的dealloc函数)。这样造成无法跟踪页面和view等的内存释放跟踪,定位是否有内存泄漏。无法使用采用hook技术的MLeaksFinder库检查是否内存泄漏。当然部分Struct可以用final class代替,但是和它提倡的采用Struc减少内存相悖。实际上采用SwiftUI大都采用Struct
3.由于大量Struct来实现页面,闪退时无法打印出页面的类名,日志跟踪时也显示不出页面的类名。点击UI View Hierarchy,打印的页面View是看不懂的内容,如:TtGC7SwiftUI14_UIHostingViewVS_7AnyView: 0x109d12880。不是正确的类名。并且页面层级稍微多两层,点击UI View Hierarchy就可能出不出来UI View Hierarchy了。这很不利于我们定位当前页面是那个类。
看到无法打印当前在那个页面的view,无法根据打印内存在工程中搜索到对应的view,不利于熟悉代码。
这个是开启僵尸进程后看到的闪退日志,可以看到无法看到那个类闪退了。
这个是oc的页面层级结构,可以看到各个组件层级很清晰,页面类名显示的很清晰,一个不熟悉代码的人很容易找到对应页面类,找到对应页面,很容易熟悉代码。
4.由于SwiftUI采用的是盒子的设计概念。手机加载时由于内存问题,通常用表格实现下拉内容多的页面,采用表格复用减少内存加载。而SwiftUI在用VStack实现下拉时可能遇到上下滑动时,页面元素乱飞的不可思议场景。不知道如何解决。
struct ChannelSingleView: View { @EnvironmentObject var server: ServerManager @EnvironmentObject var geoHallModel: GeoHallViewModel @EnvironmentObject var messageViewModel: MessagePageViewModel var channel: ServerResponse.ChannelModel var idx: Int var angle: Double { if let id = channel.id { return geoHallModel.channelAngleDict[id] ?? 0.0 } return 0.0 } private var isSelected: Bool { return channel.id == geoHallModel.currChannel?.id } var body: some View { VStack(alignment: .center, spacing: 0) { channelAvatar .rotation3DEffect(.degrees(angle), axis: (x: 0, y: 1, z: 0.2)) if let name = channel.name { Text(name) .styleText(.size12, .black) } } .frame(width: 51 * .pointRatio) .overlay { Color.bgcGray.opacity(isSelected ? 0 : 0.7) } .animation(.default, value: channel.top) .id(channel.id) .onTapGesture { if !isSelected { let canChangeTab = CustomTempVars.shared.canChangeTab if canChangeTab { CustomTempVars.shared.canChangeTab = false Task { var channel = channel channel.roomID = await server.getChannelRoom(channel.id ?? "") geoHallModel.switchChannel(channel) } withAnimation(.interpolatingSpring(stiffness: 20, damping: 5)) { if let id = channel.id { geoHallModel.channelAngleDict[id]! += 360 } } } } geoHallModel.clearReadDot(channel) } .ignoresSafeArea() .padding(.trailing, .size05) .padding(.leading, .size05) // .padding(.trailing, 9 * .pointRatio) .padding(.top, .safeAreaTop + .size05) .padding(.bottom, .size05) // .padding(.leading, 9 * .pointRatio) } } 有的页面存在向下滑后或向上滑动页面元素乱跳的一种场景 5.因为SwiftUI是一切皆view,大量使用Struct,当实现所有页面默认失败页面和无数据时,oc是采用ViewController基类快速实现,不知道SwiftUI实现。 6.当页面发送网络请求时,一般需要一个蒙层和动画,防止用户重复触发请求导致各种异常。oc是采用第三方控件在keywindow加入蒙层和动画的方式实现。SwiftUI可能有,我是不知道。这就是需要技术栈的积累问题。 7.当A页面进入B页面,B页面查询数据时,报错需要弹出toast弹窗,并退出B页面到A页面。一般toast弹窗是现实在keywindow上的,但是kewindow是B页面,而由于自动返回A页面,B页面销毁了,就看不到这个toast弹窗了。使用MBProgressHUD时就会遇到该问题。有的app为了解决这个问题人为延迟推出B页面,这样降低用户的体验流畅度,不完美。另一用采用SVProgressHUD来实现,它是异步时弹窗,显示调用弹窗和实际显示弹窗有一个时间差,正好在B页面调用弹窗,在回到A页面时,弹窗出来,所以能显示出来。我们遇到过一个问题,一个请求后台在时机毫秒内返回,蒙层弹窗还没有出来请求回来了,并且取消弹窗,实际上SVProgressHUD还没有出来,导致后面没有取消处理了,一直在哪里转圈了。最佳解决方案是创建一个优先级高于当前级别的window显示蒙层和动画。不知道SwiftUI处理这些细节问题。 8.SwiftUI提倡view组件化,可能产生view处理过多,不利于专类做专门的事情的原则。要平衡view的复杂性和复用性,不推荐过度的复用。 一个完美的产品不是完成基本功就完事,要完美处理各种细节。SwiftUI在技术栈积累不够和各种组件前不建议实际应用。作为知识储备是必要,立即实施风险太大,没有必要。我们很欣赏SwiftUI简洁性和快速开发,很多细节需要我们解决。方便和功能强大是相悖的。正如java和C/C++的关系。使用java基本不关注指针使用和内存使用,方便好学。C/C++需要处理烦人的指针和内存使用问题,正因为它更接近底层,所以能实现java实现不了的问题和响应比java快30%。一个好的软件不取决于采用那种语言,而是取决于开发软件的人。SwiftUI还任重道远,不要过于追求新技术。 由于Struct内不能直接打印日志和立体图是乱码决定了SwiftUI是一个错误的产品,基本上相当于以前的storyboard。只是相当于以前直接拖入控件变成代码实现UI。一个好的软件缺少不了强大的日志系统,只靠接口和效果,单步跟踪,没有日志的软件,很难保证软件质量,更无法维护。我在很多公司见到许多app直接从来不打印日志,真不知道他们怎么开发出app的,这样的app质量可想而知,后期维护简直是噩梦。SwiftUI是一切皆view,大量使用Struct基本上是从设计上摒弃日志,真不知道设计他的人是聪明还是傻!为了节省一点内存,而放弃可维护性,得不偿失。以前全使用storyboard和xid的人有多少,用代码实现ui的又有多少。现在又换成这样不能打印日志和立体图的SwiftUI,简直是技术退步。Swift语言是语言的进步,他是强类型的语言,会自动约束程序员的语法错误。而object c是弱类型的语言,很多语法错误不检查,过于啰嗦,全靠程序员的严谨性和代码质量。弱类型也存在优点:在高质量的程序员手里灵活性很好。就像c语言和c++语言的进步一样。c++在某些应用中无法代替c 语言。新公司员工没有大公司的架构建议采用swfit开发,不建议采用SwiftUI技术,采用代码实现ui就可以。若是大公司对质量控制很好,有很好的object c的mvvm架构,建议采用mvvm架构。不建议为了追求新技术放弃优秀的object c的mvvm架构(带app网关,ReactiveObjC,页面路由)。咨询过许多大厂的框架,他们仍旧采用object c语言开发的居多,采用mvvm架构(带app网关,ReactiveObjC,页面路由)。swfit比object c语言更先进,但是限制也更多。好的软件,主要取决于开发软件的人和框架,而不取决语言本身。不是使用的先进的语言开发的软件就好。有人说大厂很多都是网页,开发的软件太多,无法转swfit。这样是一方面的原因,但是不是全部。主要是大厂的框架足够优秀,代码质量要求严格,灵活性好。不要认为SwiftUI有Swift这几个字就认为它是Swift更高级的形式,其实他只相当于原来的storyboard,是控制器和view的组合体。我不推荐使用SwiftUI。使用SwiftUI还是使用object c取决公司的技术积累,是否有十分好的架构。若是啥架构都没有,建议采用Swift;若有很好的大公司object c建构,建议采用object c架构。再次说不要采用SwiftUI技术。