如何在 Swift 中正确声明变量?

Posted

技术标签:

【中文标题】如何在 Swift 中正确声明变量?【英文标题】:How properly declare a variable in Swift? 【发布时间】:2017-12-16 23:43:47 【问题描述】:

我发现这些在 Swift 中声明变量的不同方式非常有趣:

// METHOD 1
var dogName: String = "Charlie"

// METHOD 2
var dogName: String 
    return "Charlie"


// METHOD 3
let dogName = 
    return "Charlie"


// METHOD 4
var dogName: String = 
    return "Charlie"
()

显然方法 3 声明了一个 let 并且我们知道其中的区别;但为什么 Swift 允许方法 4?

这四种方法有什么区别?

我很困惑,尤其是方法 2 和 4。此外,为什么方法 3 与方法 4 相比失去了最后的括号?

【问题讨论】:

【参考方案1】:

方法 1 是字符串的标准变量声明。它有一个 setter 和一个 getter

var dogName: String = "Charlie"

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

方法2是String类型的计算属性,是只读的

var dogName: String 
    return "Charlie"


print(dogName) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

方法3是()->String类型的只读属性,所以基本上是一个lambda函数。

let dogName = 
    return "Charlie"


print(dogName) -> "(Function)"
print(dogName()) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

方法 4 是一个闭包,将在包含对象初始化时执行。因为它是 var,你可以用另一个值替换它

var dogName: String = 
    return "Charlie"
()

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

话虽如此,因为方法 4 是一个闭包,你可以在其中执行其他命令。这是一个示例,您可以使用此构造来初始化 UILabel:

var dogNameLabel: UILabel = 
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
    label.text = "Charlie"
    return label
()

【讨论】:

好吧,您对方法 4 的看法并不完全正确。闭包将在对象初始化时执行,在该对象中声明属性,而不仅仅是在访问它时。因此,它就像从该闭包的结果分配的默认属性值。但你是对的,你以后可以用其他任何东西替换它。 @OlegDanu 你完全正确,我相应地更新了答案。谢谢! 计算属性并不总是只读的,顺便说一句。您可以在计算属性上定义 getset 以使其可读写。然而,当如上所示声明时,它们隐含地只有一个 getter,这实际上使它们成为只读的。见developer.apple.com/library/content/documentation/Swift/… @ItaiFerber 是的,您是对的,计算属性并不总是只读的,感谢您指出这一点。我删除了 Method2 描述的错误部分。 @Sulthan 这不是真的。方法 3 是一个 lambda,您需要调用它才能获取该值。您可以通过let x = return 5 ; print(type(of: x)) 确认这一点【参考方案2】:

为了理解这些差异,让我们使用一个更具描述性的示例

这是一堂课Foo

class Foo 

    var className = "Foo"

    var dogName1 : String  return "Charlie " + className 

    let dogName2 = 
        return "My name is Charlie"
    

    var dogName3 : String = 
        var string = "My"
        string += " name"
        string += " is"
        string += " Charlie"
        print(string)
        return string
    ()


现在让我们创建一个实例

let foo = Foo()

方法一是存储属性className,带有setter和getter

let name = foo.className
foo.className = "Bar"
print(foo.className) // "Bar"

方法 2 是计算属性 dogName1 仅带有一个 getter。它可用于动态计算值。

print(foo.dogName1) // "Charlie Bar"

方法3dogName2类型() -> String的闭包。它可以分配给一个变量,然后再执行

let dogName = foo.dogName2 // assigns the closure but does not return the string.
print(dogName()) // "My name is Charlie"

方法4是变量dogName3,当实例Foo()被初始化时,它立即一次执行其闭包(由print行证明)

print(foo.dogName3) // "My name is Charlie" but does not execute the closure and print `string` again 

甚至还有一个方法5:如果将dogName3声明为lazy

lazy var dogName3 : String = 

在第一次访问变量之前,不会执行闭包。优点是您甚至可以在闭包中使用self,这在方法4中是不可能的。

【讨论】:

【参考方案3】:

我设置了一个快速测试,将所有内容重命名为 dogName1dogName2dogName3dogName4。然后我添加了代码以尝试将每个名称更改为“史努比”。

#2 和#3 没有构建,因为编译器知道它们都是只读的。 (#2,尽管被声明为var,但设置为始终返回“Charlie”。

在注释掉这两行之后,我设置了两个断点——在初始化之后,一个在尝试更新之后。

最后我尝试为每个人做一个print

断点 #1: #1 和 #4 设置为“Charlie”,#2 不存在(因为它没有初始化),而 #3 显示为已初始化但没有值(因为它还没有被调用。是的,最后的 () 初始化了内存中的一些东西。

断点 #2: #1 和 #4 已更新为“史努比”。

print的结果:#1 和#4 是“Snoopy”,#2 是“Charlie”,#3 是“(函数)”。

结论:#1 和#4 没有区别。每个都被声明为var,并且默认为“Charlie”。 #2,由于let 是只读的,并且将始终返回“Charlie”。 #3?如果您尝试更改它,它会创建一个实例并且不会构建 - 但我不知道如何使用它。

如果有人对#3 有更多补充,我会更新这个答案。

【讨论】:

【参考方案4】:

方法一很明显。

在方法 2 中,您所做的是为给定变量定义了一个 getter。

方法 3 中的 dogName 类型是 () -> String 而不是 String。你在那里初始化的是一个命名的 lambda。

在方法 4 中,您使用返回字符串的匿名(未命名)lambda 函数初始化变量。

3 和 4 之间的区别在于,在第 4 次中,您使用 () 调用该函数,因此您获得 String,而在之前您没有,所以它是一个函数。

【讨论】:

以上是关于如何在 Swift 中正确声明变量?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swift 的扩展中声明 NSLayoutConstraint 类型的变量

Swift Extension 计算变量在协议中声明之前无法正确读取

如何检查在 swift 中声明的 UnsafeMutablePointer<ObjCBool​​> 类型的变量的值? [复制]

Unity:列表变量数组。如何在 C# 中正确声明它

如何以正确的方式在 pl sql 中声明变量?

我应该如何在 Swift 中的标签中获取变量?