ios 初级开发_我作为初级ios开发人员了解软件体系结构的旅程

ios 初级开发

A beginner friendly approach to software architecture.

初学者友好的软件体系结构方法。

In this article I describe part of my journey as a junior iOS developer and some of the issues I faced and the solutions I came up with after some research.

在本文中,我描述了作为一名初级iOS开发人员的过程中的一部分旅程,以及我经过研究后遇到的一些问题和解决方案。

背景 (Background)

I began my journey as an iOS developer back in 2015 in a company that developed smart-watches and had a companion application to manage the watch both for Android and iOS. The project I inherited from “senior” iOS developers was built using Objective-C and had at least 200 files, most of them were UIViewController implementations for the multiple screens the app had. Build times were awful and the project had a really hard time scaling, objects had a lot of responsibilities and requests from upper management to add new features took way too much time and often seemed impossible. Looking for help in the iOS dev community I found that what I was facing at the time was very common among junior devs and it had a name, MassiveViewController.

我在2015年作为一家iOS开发人员开始了自己的旅程,当时该公司开发了智能手表,并拥有一个配套应用程序来管理Android和iOS手表。 我从“高级” iOS开发人员继承的项目是使用Objective-C构建的,并且至少具有200个文件,其中大多数是该应用程序具有多个屏幕的UIViewController实现。 构建时间很糟糕,项目很难扩展,对象承担很多责任,高层管理人员添加新功能的请求花费了太多时间,而且通常看起来是不可能的。 在iOS开发人员社区中寻求帮助时,我发现我当时面对的情况在初级开发人员中非常普遍,并且它的名称为MassiveViewController。

Apple encourages developers to use MVC in iOS apps, by its deep integration in UIKit. Following Apple’s MVC, junior developers wrongly put everything in the UIViewController like view layout, view updates, model manipulation, data access, networking, etc.

苹果通过与UIKit的深度集成,鼓励开发人员在iOS应用中使用MVC。 遵循Apple的MVC,初级开发人员错误地将所有内容放入UIViewController中,例如视图布局,视图更新,模型操纵,数据访问,联网等。

Looking for a solution, I became familiar with popular architectural patterns in the presentation layer that vary from MVC such as MVP and MVVM. As many junior devs in the community, I thought that the MV* variations mentioned before would fix my problem. And as other junior developers I made the mistake of trying to fit every object in the project in one of the categories that compose the acronym of the pattern of choice, i.e using MVVM, objects either were a Model, a ViewModel or a View.

在寻找解决方案时,我熟悉了表示层中流行的架构模式,这些模式与MVC(例如MVP和MVVM)不同。 与社区中的许多初级开发人员一样,我认为前面提到的MV *版本可以解决我的问题。 和其他初级开发人员一样,我犯了一个错误,即试图将项目中的每个对象放入组成选择模式首字母缩写的类别之一中,即使用MVVM,对象是模型,视图模型或视图。

Other mistake I made was to give objects too many responsibilities, for instance ViewModels ended up being in charge of networking, data access, model manipulation, etc, just like using a UIViewController with MVC. Later I learnt that not all objects would fit it in one of those categories and that responsibilities should be limited.

我犯的另一个错误是赋予对象太多的责任,例如ViewModels最终负责网络,数据访问,模型操作等,就像将UIViewController与MVC一起使用一样。 后来我了解到,并非所有对象都适合这些类别之一,并且应该限制责任。

So after learning from my mistakes and started using the patterns the right way I realized that even though the patterns helped my code to be more readable and objects in the project had fewer responsibilities than before, they didn't seemed to really fix the core issue, maintainability was still difficult and adding new features a nightmare. This is because the problem wasn’t in how the UI and Presentation were talking to each other, but in how the whole software was structured, in other words the problem I was facing was with the software architecture.

因此,从错误中吸取教训并以正确的方式开始使用模式后,我意识到,即使这些模式使我的代码更具可读性,并且项目中的对象比以前少了责任,但它们似乎并没有真正解决核心问题,可维护性仍然很困难,并且增加了新功能。 这是因为问题不在于UI和Presentation如何彼此交谈,而在于整个软件的结构方式,换句话说,我所面临的问题是软件体系结构

一些研究 (Some Research)

那么什么是MVC? (So What is MVC?)

Model View Controller (MVC) was introduced back in 1979 by Trygve Reenskaug in the Model View Controller Report. Reenskaug defines MVC in this article as follows.

模型视图控制器(MVC)于1979年由Trygve Reenskaug在“模型视图控制器报告”中引入。 Reenskaug在本文中对MVC的定义如下。

MVC was conceived as a general solution to the problem of users controlling a large and complex data set.

MVC被认为是解决用户控制大型和复杂数据集问题的通用解决方案。

The essential purpose of MVC is to bridge the gap between the human user’s mental model and the digital model that exists in the computer. The ideal MVC solution supports the user illusion of seeing and manipulating the domain information directly. The structure is useful if the user needs to see the same model element simultaneously in different contexts and/or from different viewpoints. The figure below illustrates the idea.

MVC的基本目的是弥合人类用户的心理模型与计算机中存在的数字模型之间的鸿沟。 理想的MVC解决方案支持用户直接查看和操作域信息的错觉。 如果用户需要在不同的上下文中和/或从不同的角度同时看到相同的模型元素,则此结构很有用。 下图说明了这个想法。

Image for post
Fig.1 Trygve Reenskaug’s MVC — Diagram by Trygve Reenskaug
图1 Trygve Reenskaug的MVC — Trygve Reenskaug的图表

If we analyze the diagram above MVC refers to the interaction between the user, an input tool, a process and an output. MVC seems like a very generic concept, it’s so generic that it can apply to almost any architecture that isn’t total spaghetti code. But nowadays applications are not only composed of a view like a text-editor, a model like a text, and a controller to manage the interaction. Applications now hold complex behaviors and processes, need remote data access, analytics and what not.

如果我们分析上面的图,MVC是指用户,输入工具,过程和输出之间的交互。 MVC似乎是一个非常通用的概念,它是如此通用,几乎可以应用于任何不是全部意大利面条式代码的体系结构。 但是如今,应用程序不仅由诸如文本编辑器的视图,诸如文本的模型以及用于管理交互的控制器组成。 应用程序现在具有复杂的行为和流程,需要远程数据访问,分析以及其他功能。

Essentially MVC refers to what today we know as the presentation part of an application. Therefore MVC, MVP and MVVM only solve the way we structure the presentation of an application, each in a different way.

本质上,MVC指的是今天我们所知道的应用程序的表示部分。 因此,MVC,MVP和MVVM仅以不同的方式解决了构建应用程序表示的方式。

更多研究 (More Research)

I knew that there had to be a way to design software that wasn’t so hard to maintain. So I dived into lots of software architecture literature, Clean-Architecture by Uncle Bob, Hexagonal Architecture, Layered Architecture and Microservices. Throughout my readings I can conclude that they are essentially all the same. Bear with me here, each architecture is different from each other and serve different purposes. This is in the surface though, deep down they intend the same goal, which is to separate responsibilities and establish a standard way of communication between its components, improve maintainability and scalability and reduce development time.

我知道必须有一种设计软件的方法,它并不很难维护。 因此,我深入研究了许多软件体系结构文献,包括Bob叔叔的Clean-Architecture,六角体系结构,分层体系结构和微服务。 在阅读过程中,我可以得出结论,它们基本上是相同的。 在这里忍受我,每种体系结构互不相同,并且服务于不同的目的。 尽管从表面上看,他们的内在目的是相同的目标,即分离职责并建立组件之间的标准通信方式,提高可维护性和可伸缩性并减少开发时间。

那么,如何从MassiveViewController转到易于维护的可扩展项目project? (So, how do I go from MassiveViewController to a scalable easy to maintain project 🚀?)

After all my research, I had some knowledge to build my own implementation of a software architecture and solve my problem. I found that a clean-layered- modular oriented architecture was the way to go, so I did that.

经过所有的研究,我掌握了一些知识来构建自己的软件体系结构实现并解决了问题。 我发现必须采用干净的面向分层模块的架构,所以我做到了。

The architecture I came up with focuses on building software by composing multiple independent reusable modules, a practice that many developers use.

我提出的体系结构专注于通过组合多个独立的可重用模块来构建软件,这是许多开发人员使用的一种做法。

好处 (Benefits)

This approach lets developers built software that is easy to test, maintain and scale. On the business side, this architecture helps developers deliver applications in small chunks which increases speed of development since multiple developers can build different parts of the software simultaneously, it even allows for software to be developed with no need to wait for the UI to be designed. Ultimately this means better productivity which translates to our clients using higher quality products, better costumer retention and maybe even increased profitability.

这种方法使开发人员可以构建易于测试,维护和扩展的软件。 在业务方面,此架构可帮助开发人员以小块形式交付应用程序,这可以提高开发速度,因为多个开发人员可以同时构建软件的不同部分,它甚至允许开发软件而无需等待UI的设计。 。 最终,这意味着更高的生产率,这可以转化为使用更高质量产品的客户,更好的客户保留率,甚至可能提高利润率。

潜水之前 (Before diving in)

This is not a silver bullet. Every software behaves differently and is designed to solve a specific need, hence each software must have its architecture designed to fit its purpose. Nonetheless this simple architecture which is based on widely popular architectural patterns, should fit most applications.

这不是灵丹妙药。 每个软件的行为各不相同,旨在满足特定需求,因此,每个软件都必须具有适合其目的的体系结构。 但是,这种基于广泛流行的架构模式的简单架构应该适合大多数应用程序。

结构体 (Structure)

Most applications are composed of multiple features that use a database, some sort of storage and third party services.

大多数应用程序由使用数据库,某种存储和第三方服务的多种功能组成。

特征 (Features)

A Feature refers to a specific set of tasks and use cases of an application, i.e Messages in a social networking app. Every Feature must live in its own module (think of a sandbox) so it can be replaced by other implementations that perform the same required tasks, for instance replace the Messages Feature with a new version with new UI and fancy animations without breaking the whole app.

功能是指应用程序(即社交网络应用程序中的Message)的一组特定任务和用例。 每个功能都必须存在于其自己的模块中(就像沙盒一样),以便可以由执行相同必需任务的其他实现替换它,例如,用具有新UI和精美动画的新版本替换Message s Feature ,而不会破坏整体应用程式。

分解功能 (Breaking down a Feature)

A Feature like Messages is composed of small pieces that do a specific task, these pieces are what users actually interact with.

诸如消息之类的功能由执行特定任务的小部分组成,这些部分是用户实际与之交互的部分。

Taking the previous Messages example into consideration, let's analyze a real world app like iMessage.

考虑到前面的Messages示例,让我们分析一个像iMessage这样的真实应用程序。

Image for post
Fig.2 — iMessage in iOS7 Images by Apple Inc.
图2-Apple Inc.在iOS7图像中的iMessage

If we break it down we will find that the messaging feature of an app is probably composed by some of the next parts:

如果将其分解,我们将发现应用程序的消息传递功能可能由以下几个部分组成:

  • ChatList

    聊天清单
  • NewChat

    新聊
  • Chat

    聊天室

Through this exercise we realize that the Messages Feature is only a concept that encapsulates a lot of parts that execute specific tasks.

通过本练习,我们意识到消息功能只是一个概念,它封装了许多执行特定任务的部分。

Image for post
Fig.3 Messages Feature
图3消息功能

Some Features may also contain Sub-Features. Sub-Features are parts of a Feature that may render too complex to be implemented in a single module.

某些功能可能还包含子功能。 子功能是功能的一部分,可能过于复杂而无法在单个模块中实现。

Since Sub-Features are Features, they also live in their own module. Take the Chat component in iMessage as an example, entering in a Chat we might find the following.

由于子功能是功能,因此它们也存在于自己的模块中。 以iMessage中的“聊天”组件为例,输入“聊天”可能会发现以下内容。

Image for post
Fig. 4 — iMessage chat in iOS7 Images by Apple Inc.
图4 – Apple Inc.在iOS7 Images中的iMessage聊天

As we can see it isn’t just a list of messages, it contains tasks to send voice messages, video, images and what not. The Chat component seems large and we may consider it as a Sub-Feature, thus it may also contain components like MessagesList and SearchMessage and other Sub-Features such as Camera which might be used to take pictures and then send them. So the Chat Sub-Feature might render as follows.

我们可以看到,它不仅是消息列表,还包含发送语音消息,视频,图像等的任务。 聊天组件似乎很大,我们可以将其视为子功能,因此它可能还包含MessagesList和SearchMessage等组件以及其他子功能(例如Camera),可用于拍照然后发送。 因此,聊天子功能可能会呈现如下。

Image for post
Fig.5 Chat Sub-Feature
图5聊天子功能

架构 (The architecture)

Software architecture is all about communication & responsibilities.

软件体系结构是关于沟通和责任的。

Now that we understand how a feature is composed we may start thinking of a more generic diagram to represent the architecture. In this section we will see each module in detail with an example.

现在我们了解了功能的组成方式,我们可以开始考虑使用更通用的图来表示体系结构。 在本节中,我们将通过示例详细介绍每个模块。

域:业务逻辑 (Domain: Business Logic)

Business logic such as Use Cases and Models are defined all together in a separate very low level single module named Domain.

诸如用例和模型之类的业务逻辑在一个单独的名为Domain的非常底层的单独模块中一起定义。

Use Cases should contain the business logic actual implementation, here you should manipulate your models, access data or storage all through interfaces defined in the Domain module. The Domain module must only depend on the chosen programming language’s base framework and some interfaces needed to perform its tasks. For example in case we were using Swift, the Domain module should only depend on Foundation, though there’s one exception.

用例应该包含业务逻辑的实际实现,在这里您应该通过域模块中定义的接口来操纵模型,访问数据或存储。 域模块必须仅取决于所选编程语言的基本框架以及执行其任务所需的某些接口。 例如,在我们使用Swift的情况下,尽管有一个例外,但Domain模块应该仅取决于Foundation。

The exception

例外

A Use Case works like a black box, it receives an input and then manipulates models and other objects to perform a specific task. When finished, it returns its outputs to the interested parties. When asynchronously returning outputs callbacks and closures may be easier to use but be careful if you end up using nested closures as they could cause retain-cycles (if you use classes for your Use Cases), retain-cycles affect your app’s performance and may even cause it to crash. Which is why we prefer to use functional reactive programming and the Observer design pattern. Therefore we recommend that ‘execute’ methods should publish results to observers via a subscription. You could use KVO from Cocoa or frameworks such as RxSwift, Combine or whatever framework for FRP you like. So in this case Domain can also depend on Combine or the framework you choose.

用例就像黑盒子一样工作,它接收输入,然后操纵模型和其他对象来执行特定任务。 完成后,它将其输出返回给感兴趣的各方。 当异步返回输出回调和闭包可能更易于使用,但如果最终使用嵌套闭包,则要小心,因为它们可能会导致保留周期(如果您将用例用于类),则保留周期会影响应用程序的性能,甚至可能导致它崩溃。 这就是为什么我们更喜欢使用函数式React式编程和Observer设计模式的原因。 因此,我们建议“执行”方法应通过订阅 结果发布观察者 。 您可以使用来自Cocoa的KVO或RxSwift,Combin等框架或任何您喜欢的FRP框架。 因此,在这种情况下,Domain也可以取决于Combine或您选择的框架。

Image for post
Fig.6 Domain Module
图6域模块

介面 (Interfaces)

Interfaces, or protocols in Swift, serve as blueprints or contracts that specify a behavior for objects to implement. This contracts allow objects to communicate with other objects that expect this interfaces as dependencies without creating tight coupling, this also helps to avoid inheritance when using classes.

Swift中的接口或协议用作指定要实现的对象行为的蓝图或合同。 该协定允许对象与希望将此接口作为依赖项的其他对象进行通信,而无需创建紧密耦合,这也有助于避免在使用类时进行继承。

In the Domain module we use interfaces to communicate UseCases with objects outside the module (to avoid module dependency). Objects we may want to communicate with include objects responsable for data access and maybe some other objets for different tasks such as analytics and what not.

在域模块中,我们使用接口将UseCases与模块外部的对象进行通信(以避免模块依赖性)。 我们可能要与之通信的对象包括负责数据访问的对象,以及可能用于诸如分析之类的不同任务的其他对象。

(Example)

Following the iMessage example we may have a SendTextMessageUseCase which only job would be to send a message with text. We may write it as follows:

在iMessage示例之后,我们可能会有一个SendTextMessageUseCase,唯一的工作就是发送带有文本的消息。 我们可以这样写:

This is a very simple Use Case example that adds the provided Message to a database through an object that conforms to MessageRepository protocol. In this example we use a Model, a Use Case and a Repository. The idea behind the Use Case is that you should write your business logic inside the execute method and return whatever output it produces to the caller once it finishes. This way we isolate business logic from UIViewControllers or other classes which lets us reuse code in other modules or even other applications.

这是一个非常简单的用例示例,该示例通过符合MessageRepository协议的对象将提供的Message添加到数据库中。 在此示例中,我们使用模型,用例和存储库。 用例背后的想法是,您应该在execute方法内编写业务逻辑,并在完成后将其产生的任何输出返回给调用方。 这样,我们将业务逻辑与UIViewControllers或其他类隔离开来,这使我们可以在其他模块甚至其他应用程序中重用代码。

特征 (Feature)

An app will have multiple Feature modules. As mentioned before a Feature is composed of small pieces in charge of performing a specific set of tasks. The Feature will grab its logic from the Domain module, so all it should contain are UI & Presentation related objects.

一个应用程序将具有多个功能模块。 如前所述,功能由负责执行一组特定任务的小部分组成。 该功能将从域模块中获取其逻辑,因此它应包含的都是与UI和Presentation相关的对象。

In UIKit projects:

在UIKit项目中:

  • UIViewControllers

    UIViewControllers
  • UIViews

    UIViews
  • UINavigationControllers

    UINavigationControllers
  • Xibs

    锡伯斯
  • Delegates

    代表们
  • Routers **

    路由器**
  • ViewModels / Presenters *

    ViewModels / Presenters *

In SwiftUI projects:

在SwiftUI项目中:

  • Views

    观看次数
  • ViewModels

    视图模型
  • Routers **

    路由器**

*This should be present on your Feature module whether you use MVVM or MVP. Though if you prefer to use MVC there’s no need to add this files, you can use ViewControllers to stitch your logic (Domain), networking & database operations (Data) with your UI&Presentation.

*无论使用MVVM还是MVP,此功能都应显示在功能模块上。 尽管如果您更喜欢使用MVC,则无需添加此文件,但是可以使用ViewControllers通过UI&Presentation来缝合逻辑(域),网络和数据库操作(数据)。

** Depends on how you manage your routing logic.

**取决于您如何管理路由逻辑。

Image for post
Fig.7 Feature Module
图7功能模块

This is a very simple module since it only holds UI&Presentation related files. In the example shown we used MVVM, the Model part comes from the Domain which the ViewModel takes from, the View is represented by the ViewController (which receives inputs from its underlaying view and manages its lifecycle, UIKit only) and at last the ViewModel which manipulates Use Cases from Domain on ViewController’s disposal.

这是一个非常简单的模块,因为它仅包含与UI&Presentation相关的文件。 在所示的示例中,我们使用了MVVM,Model部分来自ViewModel所来自的Domain,View由ViewController表示(ViewController从其底层视图接收输入并管理其生命周期,仅UIKit),最后是ViewModel在ViewController的处置下,从Domain操纵用例。

Routing and navigation is up to you, you may choose to use a Router or not, since it belongs to UI & Presentation how we implement it doesn’t really affect the architecture, more on this later.

路由和导航由您决定,您可以选择是否使用路由器,因为它属于UI&Presentation,我们如何实现它并不会真正影响体系结构,稍后将对此进行详细介绍。

Keep in mind that the green box is only a representation, it may contain a lot of Single Component boxes since each of those represent a ‘part’ of the Feature like ChatsList, MessagesList or Camera.

请记住,绿色框只是一种表示形式,它可能包含很多“单个组件”框,因为每个“单个组件”框都代表“功能”的“一部分”,例如ChatsList,MessagesList或Camera。

数据与第三方 (Data & Third Parties)

Data module contains DataModels, DataModels exist in a 1:1 relationship with Models in Domain, Mappers help DataModels be mapped into Models. Repositories must only return Models. Under the Bridge Pattern, Repositories are defined as objects that encapsulate data access.

数据模块包含数据模型,数据模型与域中的模型以1:1关系存在,映射器帮助数据模型映射到模型中。 存储库只能返回模型。 在桥接模式下,存储库定义为封装数据访问的对象。

Interfaces (protocols) describe a contract between objects to communicate. In simpler words protocols specify all the properties and methods a type must implement to be able to communicate with its dependees in any module. In this case as data interfaces will also be used in the Domain module by UseCases, they must be declared in the Domain module to ensure independency between modules.

接口 (协议)描述了对象之间进行通信的契约。 用简单的话来说,协议指定类型必须实现的所有属性和方法,以使其能够与任何模块中的依赖者进行通信。 在这种情况下,UseCases还将在域模块中使用数据接口,因此必须在域模块中声明它们,以确保模块之间的独立性。

For example if Type A implements Interface S and Type B also implements S, this means that Type A and B should behave the same in eyes of the Domain module and this also means that both implementations are interchangeable.

例如,如果类型A实现接口S,类型B也实现S,则这意味着类型A和B在域模块的作用下应该相同,这也意味着这两种实现是可互换的。

Image for post
Fig.8 Data module interacting with other modules
图8数据模块与其他模块的交互

Interfaces in this example:

本示例中的接口:

  • <Repository>: Defines a set of methods a type must implement to execute data related tasks required by a set of Use Cases. We should use one repositories for each model as a rule, having a single repository using generics gets complicated and it’s not recommend since each model may need different operations.

    <存储库>:定义类型必须执行的一组方法,以执行一组用例所需的数据相关任务。 通常,我们应该为每个模型使用一个存储库,使用泛型的单个存储库会很复杂,因此不建议这样做,因为每个模型可能需要不同的操作。
  • <ThirdPartyService>: Defines a set of methods a type must implement to use a third party service. Setting an interface for this means that we can swap the framework or library we use and don’t affect the app at all.

    <ThirdPartyService>:定义类型必须使用一组方法才能使用第三方服务。 为此设置接口意味着我们可以交换我们使用的框架或库,而完全不影响应用程序。

Keep in mind that every module is plug and play. This allows modules to be shared between other modules or even across applications.

请记住,每个模块都是即插即用的。 这允许模块在其他模块之间甚至在应用程序之间共享。

模块提供者 (Module Provider)

The Module Provider switches between a Feature module’s root navigation controller, on UIKit projects and rootView on SwiftUI projects. The switching is done through the activeModule attribute of type Module of the ModuleProvider class. The Module is just an enum with all the available module names. The switch could be done through a singleton or via NotificationCenter, this means that any module can tell the ModuleProvider that it’s time to switch to a new module or to dismiss itself to the root module.

模块提供程序在UIKit项目上的功能模块的根导航控制器和SwiftUI项目上的rootView之间切换。 切换是通过ModuleProvider类的Module类型的activeModule属性完成的。 该模块只是具有所有可用模块名称的枚举。 切换可以通过单例完成,也可以通过NotificationCenter进行,这意味着任何模块都可以告知ModuleProvider,现在该切换到新模块或将自己退出到根模块了。

It’s up to you to choose how to provide AppDelegate or SceneDelegate with the appropriate rootViewController or rootView respectively through the ModuleProvider, to avoid toying with Combine, DI and Singletons (more on that in a following post) the following implementation does the job.

由您自己决定如何通过ModuleProvider为AppDelegate或SceneDelegate分别提供适当的rootViewController或rootView,以避免通过使用Combine,DI和Singletons(在下一篇文章中有更多介绍)来完成以下实现。

Fig.9 Module Provider example
图9模块提供者示例

With that implementation you can send the notification to change a module from any ViewModel, Presenter or ViewController you need.

通过该实现,您可以发送通知以从所需的任何ViewModel,Presenter或ViewController更改模块。

整个图片 (The whole picture)

Image for post
Fig.10 Dependency diagram of a the whole architecture
图10整个架构的依赖图

翻译自: https://medium.com/swlh/my-journey-to-understanding-software-architecture-as-a-junior-ios-dev-5a819cc6716f

ios 初级开发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值