阅读更多
北京时间2月10日,苹果在面向开发者推送iOS 8.3 Beta的同时,还发布了版本号为6D520o的Xcode 6.3 Beta,其中便包含了iOS 8.3 Beta和OS X v10.10 SDK,并进一步提升了Swift与Objective-C代码的交互性,而Swift业已更新至1.2版本。



Xcode 6.3 Beta Release Notes看出,Xcode 6.3 Beta包含了很多颇为值得开发者期待的改变,共计50多处改动,同时修改了Objective-C的语法,足见苹果对Swift语言的重视。而其代码迁移工具可以帮助开发者将其代码从Swift 1.1(Xcode 6.1)升级至Swift 1.2(Xcode 6.3),具体执行编辑菜单(Edit)->转换(Convert)-至(To)Swift1.2即可。 具体更新如下:
Swift语言的增强
  • Swift现在支持目标增量编译,例如当一个文件改变时不会重新编译Target中的每一个文件。这个基于固有依赖分析。所以你依然会看到有很多文件在必要情况下被重编。如果你发现需要重编但没有重编的情况,请报一个Bug出来。清理Target后再编,会按照往常的流程进行。
  • 增加了一个新的Set数据类型,它提供了元素唯一化,且有完整语义的通用数据类型集合。它和NSSet类型桥接,提供和Array和Dictionary相类似的功能。
  • if let语句现在被扩展为可以支持多条条件判断:

if let a = foo(), b = bar() where a < b,
let c = baz() {
 }

它允许你测试多种选择,并且包含一个bool判断。当然这种情况不包含嵌套判断。

  • let常量现在生成时不需要立即初始化,新的规则是let常量必须在被首次使用前初始化即可(和var一样)。或者说它只能被初始化,也就是说在初始化后它不能再被改变或者重新赋值,可用的模式如下:

let x: SomeThing
 if condition {
 x = foo()
 } else {
 x = bar()
 }
 use(x)

这个正常的来说需要var变量用法,尽管这里没有任何修改的操作。

  • "Static"静态方法和属性现在允许在class中使用(作为“class final”的别名)。你现在可以在类中声明一个静态存储属性,它享有全局存储空间和首次使用再初始化的惰性构造功能。协议Protocal现在会声明一个static的类型要求而不是声明一个class的要求。
  • 对于表达式闭包的类型引用有了几点改进:

1.含有单返回语句的闭包现在类型检查时以单表达式闭包处理。
2.匿名的且含有非空返回类型的单表达式现在可以用在Void上下文中。
3.多表达式的闭包类型的情况可能无法被类型推断出来,这归功于缺乏返回类型的情况能被正确的推断出来。
  • Swift中的枚举类型现在可以通过@objc关键字导出到Objective-C中。@objc的枚举类型必须定义一个整型的原始类型,并且该枚举不能泛型化或者不能使用关联值。由于Objective-C中的枚举类型没有命名空间,所以导出到Objective-C中的枚举类型以枚举名字和case项目名字的组合的方式使用。 比如在Swift中的声明:

@objc
 enum Bear: Int {
 case Black, Grizzly, Polar
 }<br>

导出到Objective-C:
 typedef NS_ENUM(NSInteger, Bear) {
 BearBlack, BearGrizzly, BearPolar
 };

  • Objective-C语言的扩展语法现在可以判断出Objective-C API中指针或者block的是否为空,同时允许不带ImplicitlyUnwrappedOptional协议地导出Objective-C API函数。
  • Swift现在可以部分支持导入C的联合类型,包括unions、bitfileds、SIMD vector类型以及其他Swift的不支持的C特性。这些不被支持的元素不能在Swift中的直接访问,但是在Swift中,Objective-C或者C可以以参数或者返回类型的方式使用。这包括Foundation NSDecimal类型、GLKit GLKVector和GLKMatrix类型,以及其他一些类型。
  • 被导入的C结构体现在在Swift中有一个默认的构造器,它会将结构体中的所有的元素初始化为0,例如:

import Darwin
 var devNullStat = stat()
 stat("/dev/null", &devNullStat)

如果一个结构体的元素不能被正确的初始化为0(比如被标记为新的_nonnull标示符时),这个默认的构造器将会终止。
String的索引类型间新的转换API现在可以用了,如String、
  • String.UnicodeScalarView、String.UTF16View以及String.UTF8View, 同时每个String View转换为String的函数也可使用。
  • 类型值在println函数或者字符串内插算法中现在可以打印完整的类型名称了:

toString(Int.self) // 打印 “Swift.Int"
 println([Float].self) // 打印 "Swift.Array<Swift.Float>”
 println((Int, String).self) // 打印 "(Swift.Int, Swift.String)"

  • 一个新的“@noescape”属性可以用在函数的闭包参数上,这意味着这个参数是唯一可被调用的(或者用在函数调用时以参数的方式出现),其意思是它的生命周期比函数调用的周期短,这有助于一些小小的性能优化,但最重要的是它屏蔽了闭包中对self.的需求。这使得函数的控制流比其他更加透明。在未来的beta版本中,标准库函数将普遍采用这种特性,比如autoreleasepool():

func autoreleasepool(@noescape code: () -> ()) {
   pushAutoreleasePool()
   code()
   popAutoreleasePool()
 }

  • 相比Swift 1.1,Swift 1.2在很多方面的性能上有本质的提高,比如多维数组算法更快,未优化的代码更加快速。
  • 表达式类型的错误诊断有了很大的提高。
  • 很多通用表达式的检查效率有很大提高,这个有助于降低编译时间和减少“expression too complex”的错误。

Swift语言的改变

  • “确保转换”和“可失败转换”的概念现在被分为两个操作符。可失败转换现在使用as!运算符,这个!感叹号可以让代码的读者更清晰的明白本次转换可能失败并触发一个运行时错误。“as”操作符会保持向上转换(比如“someDerivedValue转换为Base”)或者类型标注(“0 转换为Int8”),它保证了转换不会失败。
  • 结构体和类构造器中的let不可变属性现在被规范为更加标准的通用模型:lets类型初始化后将永不会被改变或重新赋值。以前的实现是,可以在构造器中任意修改,而现在它们只允许被初始化和提供值操作。如果一个属性在声明时已经赋值,那么它会被所有的构造器认为已经含有初始值。
  • 从桥接Objective-C类 (NSString/NSArray/NSDictionary)到它Swift中值类型的隐式转化被移除。这将是Swift的类型系统更加简单和可预测。这意味着:

 import Foundation
 func log(s: String) { println(x) }
 let ns: NSString = "some NSString" // Okay
 log(ns) // 错误
 // "'NSString' 不能转换为 'String'"

为了完成桥接转换,需要用显式转化符标注:
log(ns as String) // succeeds

从Swift类型到Objective-C类型的桥接隐式转换依然被允许,比如:
func nsLog(ns: NSString) { println(ns) }
 let s: String = “some String”
 nsLog(s) // okay: implicit conversion from String to NSString is still permitted

  • @autoclosure现在标注在参数上,而不是标注在参数的类型上。比如:

//以前我们这样写:
 func assert(predicate : @autoclosure () -> Bool) {… }
//现在需要这样写:
 func assert(@autoclosure predicate : () -> Bool) {… }

  • 使用在函数参数上的 @autoclosure属性现在含有@noescape新属性的功能,这个改进限制了@autoclosure作为控制流程以及惰性计算的能力。
  • 柯里化函数现在可以指定参数标签了:

 func curryUnnamed(a: Int)(_ b: Int) { return a + b }
 curryUnnamed(1)(2)
 func curryNamed(first a: Int)(second b: Int) -> Int { return a + b }
 curryNamed(first: 1)(second: 2)

  • Swift现在可以检测在Swift类型系统中覆盖和重写的差异以及通过Objective-C运行时可见的影响。比如,下面Objective-C类中对属性的setter和类扩展中对方法的“setProperty”它们之间的冲突现在可以被诊断:

class A : NSObject {
 var property: String = "Hello" // 注意: Objective-C 方法 'setProperty:’
 // 以前这里“属性”这里是通过setter声明
 }
 extension A {
 func setProperty(str: String) { } // 错误:方法"setProperty"
 // 重复声明了Objective-C方法
 //'setProperty:'
 }

同样地检查在Objective-C中重写:
 class B : NSObject {
 func method(arg: String) { } // 注意:重写操作
 // 这里含有类型:'(String) -> ()'
 }
 class C : B {
 func method(arg: [String]) { } // 错误: 重写的选择器方法含有不匹配的类型'([String]) -> ()'
 }

和协议的适配性一样:
class MyDelegate : NSObject, NSURLSessionDelegate {
 func URLSession(session: NSURLSession, didBecomeInvalidWithError: Bool){ }
 // 错误:Objective-C 方法 'URLSession:didBecomeInvalidWithError:'
 //由方法提供: 'URLSession(_:didBecomeInvalidWithError:)'
 // 和可选类型的需求方法相冲突:
 // 'URLSession(_:didBecomeInvalidWithError:)' 在协议
 // 'NSURLSessionDelegate'
 }

Swift语言Bug修复

  • 动态转换符(“as!”, “as?“和“is”)现在可以用在Swift的协议类型上,只要该协议类型没有关联类型。
  • 在Playground增加的一致性需求现在可以按照预期工作了,比如:

struct Point {
 var x, y: Double
 }
 extension Point : Printable {
 var description: String {
 return "(\(x), \(y))"
 }
 }
 var p1 = Point(x: 1.5, y: 2.5)
 println(p1) // prints "(1.5, 2.5)”

  • 导入的没有文档化的NS_ENUM类型,比如UIViewAnimationCurve,现在可以通过init(rawValue:) 构造器从它的原始整型类型转换出来而不会重设为nil,为解决这个问题而用替代方法unsafeBitCast编写的代码现在可以使用原始值构造器编写了。比如:

 let animationCurve =
unsafeBitCast(userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue,
 UIViewAnimationCurve.self)

现在可以写为:
 let animationCurve = UIViewAnimationCurve(rawValue:
 userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue)!

  • 在枚举类型中负浮点数可以用作原始值了。
  • 指向Objective-C类,或者Swift中继承自Objective-C对象的无主引用,在该无主引用指向的对象释放后无主引用被重新赋值时不会再Crash。
  • 含有观察访问器的变量或者属性如果它可以从初始值表达式中推断出类型就无需显式指定类型。
  • NSClassFromString函数搜索失败时其结果和nil的比较现在工作正常。
  • 子类中的重写基类含有可选类型的方法时,如果涉及到可选类型的转换将不会导致Crash。

class Base {
 func foo(x: String) -> String? { return x }
 }
 class Derived: Base {
 override func foo(x: String?) -> String { return x! }
 }

关于Objective-C语言的增强

Objective-C API中可以表示参数,返回值,属性,变量等等的“nullability”属性。比如,下面是表达很多UITableView API的为空特性:
 -(void)registerNib:(nonnull UINib *)nib forCellReuseIdentifier:(nonnull
NSString *)identifier;
 -(nullable UITableViewCell *)cellForRowAtIndexPath:(nonnull
NSIndexPath)indexPath;
 @property (nonatomic, readwrite, retain, nullable) UIView *backgroundView;

这个nullability标示符影响了Objective-C API在Swift的可选类型值,nonnull标示符标示的类型将会以非可选的类型的导入,这个用来替代隐式解封可选类型如(e.g., UINib!)。而nullable标示符标示的类型则会以可选类型导入(如UITableViewCell?),所以下面的API在Swift中表现如下:
func registerNib(nib: UINib, forCellReuseIdentifier identifier: String)
func cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell?
var backgroundView: UIView?

  • 可空特性标示符也可以用在指针类型,包括C指针,block指针和C++成员指针,使用双下划线方式,比如:

void enumerateStrings(__nonnull CFStringRef (^ __nullable callback)(void));

这里,它自身的回调函数是nullable的,但是它的回调函数的返回类型为nonnull,所以这个API在Swift以如下方式使用:
func enumerateStrings(callback: (() -> CFString)?)

总的来说,可空特性标示符有三种,可以用双下划线(用在任何指针类型),或者没有下划线的(用在Objective-C属性,方法结果类型或者方法参数类型)。



  • 特别是在Objective-C API中,很多指针倾向于nonnull,因此Objective-C提供了“audited”域(通过新的#pragma),它会认为未被标注的指针为nonnull,比如下面的例子等同于上面第一个例子,但是它用的是“audited”域来简化语句表达:

#pragma clang assume_nonnull begin
 // …
 -(void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString
*)identifier;
 -(nullable UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath)indexPath;
 @property (nonatomic, readwrite, retain, nullable) UIView *backgroundView;
 // …
 #pragma clang assume_nonnull end

  • 为了保证代码的连续性,我们强烈建议你在所有的Objective-C头文件使用“audited”域来表述其api的可空性,同时避免null_unspecified情况,建议使用在将可空性引入到现有的头文件时采用该功能作为过渡工具。
  • Objective-C增加的nullability注解不会影响它的向后兼容性也不会影响代码的编译。比如nonnull在有些情况下依然可以以nil结束,诸如消息路由到一个为nil的接收器,但是,nullability注解只是提高Swift的编程体验,它会在Objective-C中产生一个新的警告,诸如朝一个nonnull的参数赋一个nil的话,这使得Objective-c API更加高效以及使用的正确。
  • Objective-C可以通过null_resettable来表达属性的空属性,该属性setter访问器允许将其设置为nil(设置该属性为默认值),但是它的getter访问器不会提供一个nil值(因为它提供了默认值),有一个这样的属性如UIView’s tintColor,如果没有tint颜色指定时它会提供一个默认的tint颜色值,如:

@property (nonatomic, retain, null_resettable) UIColor *tintColor;

这样的API在Swift使用隐式强制解封的方法使用:
 var tintColor: UIColor!

  • C指针类型的参数或者Block指针类型可以使用noescape新属性标志,它用来标明这个指针参数不会离开这个函数或者方法而使用。这种情况下,可以安全的传递一个局部变量地址,noescape block指针在Swift中将会被映射为@noescape参数:

void executeImmediately(__attribute__((noescape)) void (^callback)(void);

将被影射到Swift为:
 func executeImmediately(@noescape callback: () -> Void)

  • LLDB现在包含了一个printf()函数去计算C/C++/Objective-C表达式,这个将在arm64设备上提升表达式计算的体验,但是可能和用户在.lldbinit定义的表达式前缀冲突,如果你发现在表达式计算时出现错误,这可能就是root cause。
  • XCode 6.3将Apple LLVM编译器更新为6.1.0,这个新的编译器版本包含了对C++14标准的全部支持,包括大量的增强的警告诊断和新的优化,对于arm64架构的支持进行了有效的重构来支持ARM的实现, 这个将明显影响矩阵内联函数计算。
  • 为arm64 vfma/vfms内联函数预定的参数被移除,虽然这个改变不会产生一个编译时错误,但是它会中断代码运行时操作,我们需要明确这个变化来减少风险。默认的,编译器现在会对使用这种内联属性提供警告并维持固有的行为,在尽可能的情况下,你需要接受这个变化并且定义USE_CORRECT_VFMA_INTRINSICS宏为1告诉编译器接收警告,当然你也可以可以USE_CORRECT_VFMA_INTRINSICS宏为0来屏蔽警告并保持固有行为。但是请不要保留这样的代码太久,因为我们计划在未来的版本中移除对这种旧行为的支持。
  • 含有自动尺寸标志的视图以及包含在UITableView、UICollectionView或NSScrollView的视图在打开文档时不会再出现对齐错误。

作者简介:

王芳杰 目前就职于叠拓信息技术有限公司,担任叠拓NGN中国人力资源培养经理、叠拓NGN中国售前经理,《老码说编程之玩转Swift江湖》一书作者。
  • 大小: 16.5 KB
  • 大小: 11.1 KB
0
1
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Xcode6.3Beta发布,Swift1.2带来哪些新变化?

    苹果发布Xcode6.3Beta,更新Swift语言至1.2版本,修改OC语法,进一步提升Swift与OC代码交互性,其代码迁移工具可以帮助开发者实现代码升级。本文作者王芳杰在第一时间对ReleaseNotes进行了完整翻译。北京时间2月10日...

  • Swift1.2与Xcode6.3 beta

    Xcode6.3和Swift1.2都已经发布。这次发布增强了Swift编译器也给Swift增加了一些新的特性。详细内容可以看这里。这里主要关注比较重要的内容。 编译器的改进 Swift1.2的编译器更加的稳定,各方面性能都有所提升。...

  • Swift在1.2版本的变化

    从Xcode 6.3 Beta Release Notes看出,Xcode 6.3 Beta包含了很多颇为值得开发者期待的改变,共计50多处改动,同时修改了Objective-C的语法,足见苹果对Swift语言的重视。而其代码迁移工具可以帮助开发者将其代码从...

  • Swift 1.2新内容

    The final Swift 1.2 release is still a good way away, and you can’t submit apps built with Xcode 6.3 beta to the store. So again, this isn’t something you’ll probably want to use yet, but it’s ...

  • OS X10.10.3正式版和Xcode 6.3正式版下载

    4.09日,OS X10.10.3 正式版 IOS8.3 正式版和 Xcode 6.3 正式版在今天发布,这是 2015 年里面,IOS 系统和 Mac OS 系统,以及 IOS 和 Mac OS 专属开发者工具 Xcode 第一个重大版本更新。  OS X Yosemite ...

  • 关于 swift 编译好慢 compile slowly xcode6.3貌似有所改进

    xcode6.3beta2 编译项目速度确实快了许多 但是更改某些文件的时候还是会导致所有文件重新编译. 总之比之前好多了. 转载于:https://my.oschina.net/u/727843/blog/382638

  • swift 1.2 升级

    而其代码迁移工具可以帮助开发者将其代码从Swift 1.1(Xcode 6.1)升级至Swift 1.2(Xcode 6.3),具体执行编辑菜单(Edit)-&gt;转换(Convert)-至(To)Swift1.2即可。 转载于:...

  • Xcode各版本官方下载及百度云盘下载, Mac和IOS及Xcode版本历史

    官方下载, 用开发者账户登录,建议用Safari浏览器下载. 官方下载地址: https://developer.apple.com/xcode/downloads/   百度云盘下载地址: http://yun.baidu.com/share/home?...Xcode 7 beta 3:http

  • Xcode 6.3.1Mac版 V6.4.Beta3免费下载

    Xcode for mac是Mac OS系统以及IOS系统开发者专用于构建 Mac OS X 及 iOS 应用程序的完整工具集 - Xcode 5 的工具经过重新设计,它们的性能更优秀、使用更容易,能带给用户前所未有的助益。Xcode 5.1.1 具有全新的...

  • FileLoad_Swift_iOS:用 Swift 编码的 iOS 的简单文件加载

    在 Swift 1.2 (Xcode 6.3 Beta 4) 中编码的 iOS 的简单文件加载。 // Load data FileLoad.loadData(path:String, directory:NSSearchPathDirectory, subdirectory:String?) -&gt; NSData? FileLoad....

  • iOS系统和XCode各版本发布日期

    本人收集了iOS系统和XCode各版本发布日期,供大家参考发布日期 版本编号 更改2018年3月19日 iOS11.3 Beta6 推出iOS11.3.6测试版2018年3月13日 iOS11.3 Beta5 推出iOS11.3测试版2017年12月13日 iOS11.2 ...

  • Xcode及模拟器SDK下载

    版权声明:如需转载,请注明出处,谢谢!... 目录(?)[+] ...如果你嫌在 App Store 下载 Xcode 太慢,你也可以选择从网络上下载: Xcode下载(Beta版打的包是不能提交到App Store上的) 绝对官方源!!!绝对官

  • TestingInSwift:Swift 中的测试示例项目

    此代码需要 Swift 1.2 (Xcode 6.3 beta) 。 安装 克隆这个存储库。 这明确包括所有依赖项,以减少复制此存储库时配置错误的可能性。 话虽如此,这个存储库使用 。 需要进行一项更改,即为添加。 单击“获取 Beta ...

  • swift1.2新增和改动

    相比Swift 1.1,Swift 1.2在很多方面的性能上有本质的提高,比如多维数组算法更快,未优化的代码更加快速。 表达式类型的错误诊断有了很大的提高。 很多通用表达式的检查效率有很大提高,这个有助于降低编译...

  • SwiftRegex:简单的 Swift 正则表达式库

    Swift 正则表达式这个库为 Cocoa NSRegularExpression类提供了一个小而... 由于它使用 Swift Set类,因此该库需要 Swift 1.2 和 Xcode 6.3 beta; 它不会使用早期版本的 Xcode 进行编译。 SwiftRegex 在许可下获得。

  • spritekit-swift-physics-bouncing-balls:一个使用SpriteKit的物理系统和Swift的演示

    Xcode 6.3或更高版本iOS 8.1 SDK历史2015-07-06在Xcode 6.4上测试2015-05-21在Xcode 6.3.2上测试2015-05-14在Xcode 6.3.1上测试并修复了错误2014-10-23在Xcode 6和iOS 8.1 SDK上测试并修复了错误2014-09-10在Xcode 6...

  • Java swing + socket + mysql 五子棋网络对战游戏FiveChess.zip

    五子棋游戏想必大家都非常熟悉,游戏规则十分简单。游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【技术】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

  • 纯C语言实现的控制台有禁手五子棋(带AI)Five-to-five-Renju.zip

    五子棋游戏想必大家都非常熟悉,游戏规则十分简单。游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【技术】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

  • setuptools-57.1.0.tar.gz

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

  • setuptools-59.1.1.tar.gz

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

Global site tag (gtag.js) - Google Analytics