重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

SwiftUI的知识点有哪些-创新互联

这篇文章主要讲解了“SwiftUI的知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SwiftUI的知识点有哪些”吧!

成都创新互联公司致力于互联网品牌建设与网络营销,包括成都网站设计、成都网站制作、SEO优化、网络推广、整站优化营销策划推广、电子商务、移动互联网营销等。成都创新互联公司为不同类型的客户提供良好的互联网应用定制及解决方案,成都创新互联公司核心团队10多年专注互联网开发,积累了丰富的网站经验,为广大企业客户提供一站式企业网站建设服务,在网站建设行业内树立了良好口碑。

一、背景

苹果于2019年度WWDC全球开发者大会上,发布了基于Swift建立的声明式框架–SwiftUI,其可以用于watchOS、tvOS、macOS等苹果旗下产品的应用开发,统一了苹果平台的UI框架。

正如官网所言Better apps. Less code:用更少的代码构建更好的应用。目前想要体验SwiftUI,需要以下的准备:Xcode 11 beta和macOS Mojave or Higher,如果想要体验实时预览和完整的Xcode 11功能,需要macOS 10.15 beta。

本文主要从以下三个方面讲述SwiftUI的特性:

  • 从代码层面理解Swift 5.1新语法的底层实现;

  • 从数据流方面阐述SwiftUI的黑魔法;

  • 从布局原理层面阐述SwiftUI组件化的优势;

二、SwiftUI的特性

本节对Opaque Result Type, PropertyDelegate, FunctionBuilder三个语法新特性进行讲解,结合部分伪代码和数据流分析,由浅入深地理解,其在SwiftUI中的作用。

2.1 Opaque Result Type

新建一个SwiftUI的新项目,会出现如下代码:一个Text展示在body中。

struct ContentView : View { 
      var body: some View { 
            Text("Hello World") 
      } }

对于some View的出现,大家可能会觉得很突兀。一般情况下,闭包中返回的类型应该是用来指定body的类型,如下代码所示,如果闭包中只有一个Text,那么body的类型应该就是Text。

struct ContentView : View { 
      var body: Text { 
            Text("Hello World") 
      } }

然而,很多时候在UI布局中是确定不了闭包中的具体类型,有可能是Text、Button、List等,为了解决这一问题,就产生了Opaque Result Type。

其实View是SwiftUI一个核心的协议,代表了闭包中元素描述。如下代码所示,其是通过一个associatedtype修饰的,带有这种修饰的协议不能作为类型来使用,只能作为类型约束来使用。

通过Some View的修饰,其向编译器保证:每次闭包中返回的一定是一个确定,而且遵守View协议的类型,不要去关心到底是哪种类型。这样的设计,为开发者提供了一个灵活的开发模式,抹掉了具体的类型,不需要修改公共API来确定每次闭包的返回类型,也降低了代码书写难度。

public protocol View : _View { 
       associatedtype Body : View 
       var body: Self.Body { get } }

2.2 PropertyDelegate

复杂的UI结构一直是前端布局的痛点,每次用户交互或者数据发生改变,都需要及时更新UI,否则会引起某些显示问题。但是,在SwiftUI里面,视图中声明的任何状态、内容和布局,源头一旦发生改变,会自动更新视图,因此,只需要一次布局。在属性前面加上@State关键词,即可实现每次数据改动,UI动态更新的效果。

@propertyDelegate public struct State : DynamicViewProperty, BindingConvertible

上述代码中,一个@State关键词继承了DynamicViewProperty和BindingConvertible,BindingConvertible是对属性值的绑定,DynamicViewProperty是动态绑定了View和属性。

也就是说,声明一个属性时,SwiftUI会将当前属性的状态与对应视图的绑定,当属性的状态发生改变的时候,当前视图会销毁以前的状态并及时更新,下面具体分析一下这个过程。一般情况下实现一个String属性的初始化,代码如下:

public struct MyValue { 
    var myValueStorage: String? = nil
    public var myValue: String { 
        get { 
            myValue = myValueStorage 
            return myValueStorage 
        } 
        set { 
           myValueStorage = newValue 
        }
    } }

如果代码中有很多这样的属性,而且对某些属性进行特定的处理,上面的写法无疑会产生很多冗余。属性代理(propertyDelegate)的出现就是解决这个问题的,属性代理是一个泛型类型,不同类型的属性都能够通过该属性代理进行特定的处理:

@propertyDelegate public struct LateInitialized {
  private var storage: Value?
  
  public init() {
    storage = nil  }
  
  public var value: Value {
    get{
      guard let value = storage 
      createDependency(view, value) // 建立视图与数据依赖关系
      return value
    }
    set {
      if(storage != newValue){
        storage = newValue        notify(to: swiftui) // 通知 SwiftUI 数据有变化
      }
    }
  }}

SwiftUI的知识点有哪些

上述代码的功能如上图所示。通过@propertyDelegate的修饰,能够解决不同类型的value进行特定的处理;上述包装的方法,能够建立视图与数据之间的关系,并且会判断在属性值发生变化的情况下,通知SwiftUI刷新视图,编译器能够为String类型的myValue生成如下的代码,经过修饰后的代码看起来很简洁。

public struct MyValue {
  var $myValue: LateInitialized = LateInitialized()
  public var myValue: String {
      get { $myValue }
      set { $myValue.value = newValue}
  }}

接下来,我们看一下@State的源码:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)@propertyDelegate public struct State : DynamicViewProperty, BindingConvertible {
    /// Initialize with the provided initial value.
    public init(initialValue value: Value)
    /// The current state value.
    public var value: Value { get nonmutating set }
    /// Returns a binding referencing the state value.
    public var binding: Binding { get }
    /// Produces the binding referencing this state value
    public var delegateValue: Binding { get }
    /// Produces the binding referencing this state value
    /// TODO: old name for storageValue, to be removed
    public var storageValue: Binding { get }}@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)extension State where Value : ExpressibleByNilLiteral {
    /// Initialize with a nil initial value.
    @inlinable public init()}

Swift 5.1的新特性Property Wrappers(一种属性装饰语法糖)来修饰State,内部实现的大概就是在属性Get、Set的时候,将部分可复用的代码包装起来,上文中说的“属性代理是一个泛型类型”正能够高效的实现这部分功能。

@State内部是在Get的时候建立数据源与视图的关系,并且返回当前的数据引用,使视图能够获取,在Set方法中会监听数据发生变化、会通知SwiftUI重新获取视图body,再通过Function Builders方法重构UI,绘制界面,在绘制过程中会自动比较视图中各个属性是否有变化,如果发生变化,便会更新对应的视图,避免全局绘制,资源浪费。

通过这种编程模式,SwiftUI帮助开发者建立了各种视图和数据的连接,并且处理两者之间的关系,开发者仅需要关注业务逻辑,其官方的数据结构图如下:

SwiftUI的知识点有哪些

用户交互过程中,会产生一个用户的action,从上图可以看出,在SwiftUI中数据的流转过程如下:

  • 该行为触发数据改变,并通过@State数据源进行包装;

  • @State检测到数据变化,触发视图重绘;

  • SwiftUI内部按上述所说的逻辑,判断对应视图是否需要更新UI,最终再次呈现给用户,等待交互;

以上就是SwiftUI的交互流程,其每一个节点之间的数据流转都是单向、独立的,无论应用程序的逻辑变得多么复杂,该模式与Flux和Redux架构的数据模式相类似。

内部由无数这样的单向数据流组合而成,每个数据流都遵循相应的规范,这样开发者在排查问题的时候,不需要再去找所有与该数据相关的界面进行排查,只需要找到相应逻辑的数据流,分析数据在流程中运转是否正常即可。

不同场景中,SwiftUI提供了不同的关键词,其实现原理上如上文所示:

  • @State - 视图和数据存在依赖,数据变化要同步到视图;

  • @Binding - 父子视图直接有数据的依赖,数据变化要同步到父子视图;

  • @BindableObject - 外部数据结构与SwiftUI建立数据存在依赖;

  • @EnvironmentObject - 跨组件快速访问全局数据源;

以上特性的实现是基于Swift的Combine框架,下面简单介绍一下。该框架有两个非常重要的概念,观察者模式和响应式编程。

观察者模式是描述一对多关系:一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。这两类对象分别被称为被观察目标和观察者,一个观察目标可以对应多个观察者,观察者可以订阅它们感兴趣的内容,这也就是文中关键词@State的实现来源,将属性作为观察目标,观察者是存在该属性的多个View。

响应式编程的核心是面向异步数据流和变化的,响应式编程将所有事件转成为异步的数据流,更加方便的对这些数据流进行组合变换,最终只需要监听数据流的变化并做出处理即可,因此在SwiftUI中处理用户交互和响应等非常简洁。

2.3 FunctionBuilder

在认识FunctionBuilder之前,必须先了解一下ViewBuilder,其是用 @_functionBuilder来修饰的,编译器会使用。并且对它所包含的方法有一定要求,其隐藏在各个容器类型的最后一个闭包参数中。下面具体介绍所谓的“要求”。

在组合视图中,闭包中会处理大量的UI组件,FunctionBuilder是通过闭包建立样式,将闭包中的UI描述传递给专门的构造器,提供了类似DSL的开发模式。如下实现一个简单的View:

struct RowCell : View {
    let image : UIImage    let title : String    let tip : String
    
    var body: some View {
        HStack{
            Image(uiImage: image)
            Text(title)
            Text(tip)
        }
    }}

查看HStack的初始化代码,如下所示:其最后的content是用ViewBuilder进行修饰的,也就是通过functionBuilder对闭包表达式进行了特殊处理,最终构造出视图。

init(alignment: VerticalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)

如果没有FunctionBuilder这一新特性,那么开发者必须对容器视图进行管理,以HStack为例(如下代码所示)。若存在大量的表达式,无疑会让开发者感觉到头疼,而且代码也会很杂乱,结构也不够清晰。

struct RowCell : View {
    let image : UIImage
    let title : String
    let tip : String
    
    var body: some View {
        var builder = HStackBuilder()
        builder.add(Image(uiImage: image))
        builder.add(Text(title))
        builder.add(Text(tip))
        return builder.build()
    }}

用@_functionBuilder修饰的内容,均会实现一个构造器,构造器的功能如上述代码所示。构建器声明几种buildBlock方法用来构造视图,这几种方法能够满足各种各样的闭包表达式。下面是SwiftUI的ViewBuilder几种方法:

Building Blocksstatic func buildBlock() -> EmptyView//Builds an empty view from a block containing no statements.static func buildBlock(Content) -> Content//Passes a single view written as a child view through unmodified.static func buildBlock(C0, C1) -> TupleView<(C0, C1)>static func buildBlock(C0, C1, C2) -> TupleView<(C0, C1, C2)>static func buildBlock(C0, C1, C2, C3) -> TupleView<(C0, C1, C2, C3)>...

上文被ViewBuilder修饰的content,content在调用的时候,会按照上述合适的buildBlock进行构建视图,将闭包中出现的Text或者其他的组件build成一个TupleView,并且返回。

但是,@_functionBuilder也存在一定局限性,ViewBuilder的buildBlock最多传入十个参数,也就是布局中最多只能有十个View;如果超过十个View,可以考虑使用TupleView来用多元的方式合并View。

作为SwiftUI的新特点之一,FunctionBuilder倾向于目前流行的编程方式,开发者能够使用基于DSL的架构,像SwiftUI,而不用去考虑具体的实现细节,因为构建器实现的就是一个DSL本身。

三、Components

本节通过DSL视图的分析,分析SwfitUI在布局上的特点,以及利用该特点在组件化过程中的优势。

目前,组件化编程是主流的开发方式,SwfitUI带来了全新的功能–可以构建可重用的组件,采用了声明式编程思想。将单一、简单的响应视图组合到繁琐、复杂的视图中去,而且在Apple的任何平台上都能使用该组件,达到了跨平台(仅限苹果设备)的效果。按照用途大概能够分为基础组件、布局组件和功能组件。

更多的组件详见  example link。

下面以一个Button为例子:

struct ContentView : View {
    var body: some View {
        Button(action: {
            // did tap
        },label: {Text("Click me")}
        )
        .foregroundColor(Color.white)
        .cornerRadius(5)
        .padding(20)
        .background(Color.blue)
    }}

其中包含了一个Button,其父视图是一个ContenView,其实ContenView还会被一个RootView包含起来,RootView是SwiftUI在Window上创建出来了。通过简单的几行代码,设置了按钮的点击事件,样式和文案。

其视图DSL结构如下图所示,SwiftUI会直接读取 DSL内部描述信息并收集起来,然后转换成基本的图形单元,最终交给底层Metal或OpenGL渲染出来。

通过该结构发现,与UIKit的布局结构有很大的不同,像按钮的一些属性background、padding、cornerRadius等不应该出现在视图主结构中,应该出现在Button视图的结构中。

因为,在 SwiftUI中这些属性的设置在内部都会用一个View来承载,然后在布局的时候就会按照上面示例的布局流程,一层层View的计算布局下来,这样做的优点是:方便底层在设计渲染函数时更容易做到monomorphic call,省去无用的分支判断,提高效率。

SwiftUI的知识点有哪些

同时SwiftUI中也是支持frame设定,但也不会像UIKit中那样作用于当前元素,在内部也是形成一个虚拟的View来承载frame设定,在布局过程中进行frame计算最终显示出想要的结果。

总之在SwiftUI中给一个View设置属性,已经不是为当前元素提供约束,而是用一系列容器来包含当前元素,为后续布局计算做准备。

SwiftUI的界面不再像UIKit那样,用ViewController 承载各种UIVew控件,而是一切皆View,所以可以把View切分成各种细致化的组件,然后通过组合的方式拼装成最终的界面,这种视图的拼装方式提高了界面开发的灵活性和复用性。因此,视图组件化是SwiftUI很大的亮点。

四、See it live in Xcode

SwiftUI的Preview是Apple的一大突破,类似RN、Flutter的Hot Reloading。Apple选择了直接在macOS上进行渲染,不过需要搭载有SwiftUI.framework的macOS 10.15才能够看到Xcode Previews界面。

Xcode将对代码进行静态分析 (得益于SwiftSyntax框架),找到所有遵守PreviewProvider 协议的类型进行预览渲染。在Xcode 11中提供了实时预览和静态预览两项功能,实时预览:代码的修改能够实时呈现在Xcode的预览窗口中;此外,Xcdoe还提供了快捷功能,通过command+鼠标点击组件,可以快速、方便地添加组件和设置组件属性。

感谢各位的阅读,以上就是“SwiftUI的知识点有哪些”的内容了,经过本文的学习后,相信大家对SwiftUI的知识点有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!


新闻标题:SwiftUI的知识点有哪些-创新互联
本文网址:http://cqcxhl.cn/article/iesjo.html

其他资讯

在线咨询
服务热线
服务热线:028-86922220
TOP