基本语法:
定义变量和常量: let 和 var
常量和变量的命名:
你可以使用任何你喜欢的字符作为常量和变量名,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let ?? = "dogcow"
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字
注意:如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
注意:
是指向的对象不可以再进行修改.但是可以通过指针获得对象后,修改对象内部的属性
在真实使用过程中,建议先定义常量,如果需要修改再修改为变量(更加安全)
// 注意:声明为常量不可以修改的意思是指针不可以再指向其他对象.但是可以通过指针拿到对象,修改其中的属性
// view : UIView = [[UIView alloc] init];
// Swift对象中不需要*
var view : UIView = UIView()
view = UIView()
let view1 :UIView = UIView(frame: CGRect.init(x: 10, y: 10, width: 100, height: 100))
//view1 = UIView()
view1.backgroundColor = UIColor.blueColor()
let button : UIButton = UIButton(type: UIButtonType.ContactAdd)
button.backgroundColor = UIColor.brownColor()
button.setTitle("anniu", forState: UIControlState.Normal)
button.center = CGPoint(x: 20, y: 20)
view1.addSubview(button)
可选类型:
在OC开发中,如果一个变量暂时不使用,可以赋值为0(基本属性类型)或者赋值为空(对象类型)
在swift开发中,nil也是一个特殊的类型.因为和真实的类型不匹配是不能赋值的(swift是强语言)
但是开发中赋值nil,在所难免.因此推出了可选类型
可选类型的写法:
// 写法一:定义可选类型
let string : Optional<String> = nil
// 写法二:定义可选类型,语法糖(常用)
let string : String? = nil
使用可选类型(optionals)来处理值可能缺失的情况;格式是在类型后面加?:
var num Int?
隐式解析可选类型:
使用隐式解析可选类型(optionals)来处理值在第一次赋值之后就确定之后一直有值;格式是在类型后面加!:
var num Int!
循环:for, for-in, do-while
使用这些关键字后面都必须跟大括号;
// 传统写法
for var i = 0; i < 10; i++ {
print(i)
}
for i in 1...5 {
print(i)
}
//如果不需要用到范围中的值,可以使用下划线_进行忽略
for _ in 1...5 {
print("as")
}
while和do while循环
while循环
while的判断句必须有正确的真假,没有非0即真
while后面的()可以省略
var a = 0
while a < 10 {
a++
}
do while循环
使用repeat关键字来代替了do
let b = 0
repeat {
print(b)
b++
} while b < 20
逻辑分支if, switch, guard
switch 语句在 swift 和 c 中的区别:
- 在C语言中:
1.如果 case 的结尾没有 break就会接着执行下一个 case 语句;
- 在 swift 中:
1.不需要在每个 case 后面增加 break, 执行完 case 对应的代码后默认会自动退出 switch 语句;
2.每个 case 语句后面都必须有可执行的语句;
3.case 后面可以跟多条件匹配和范围匹配;
4.case 可以匹配元组
5.case 可以支持多种数据类型(字符串,浮点型,)
6.switch语句可以使用 where 来增加判断条件
7.值绑定
注意: switch语句必须保证处理所有可能的情况,不然编译器就会直接报错,因此在这里的 default 一定要加上去;
let somePoint = (1, 1)
switch somePoint {case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")}
// 输出 "(1, 1) is inside the box"
值绑定
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of \(x)")case (0, let y):
print("on the y-axis with a y value of \(y)")case let (x, y):
print("somewhere else at (\(x), \(y))")}
// 输出 "on the x-axis with an x value of 2"
fallthrough :执行完当前的 case 会直接执行 fallthrough 后面的 case 语句或者 default 语句;
注意: fallthrough后面的 case 条件不能定义变量和常量标签;
let num = 0
switch num {
case 0:
fallthrough
case 1:
print("1")
default:
print("2")
}
if分支语句
和OC中if语句有一定的区别
判断句可以不加()
在Swift的判断句中必须有明确的真假
不再有非0即真
必须有明确的Bool值
Bool有两个取值:false/true
guard是Swift2.0新增的语法
它与if语句非常类似,它设计的目的是提高程序的可读性
guard语句必须带有else语句,它的语法如下:
当条件表达式为true时候跳过else语句中的内容,执行语句组内容
条件表达式为false时候执行else语句中的内容,跳转语句一般是return、break、continue和throw
guard 条件表达式 else {
// 条换语句
break
}
语句组
字符串:
OC和Swift中字符串的区别
在OC中字符串类型时NSString,在Swift中字符串类型是String
OC中字符串@"",Swift中字符串""
使用 String 的原因
String 是一个结构体,性能更高
NSString 是一个 OC 对象,性能略差
String 支持直接遍历
Swift 提供了 String 和 NSString 之间的无缝转换
字符串的基本使用:
// 字符串遍历
var str = "Hello, Swift"
for c in str.characters {
print(c)
}
//字符串拼接
let str1 = "Hello"
let str2 = "World"
let str3 = str1 + str2
//字符串和其他类型拼接
let name = " John"
let age = 18
let info = "my name is \(name), age is \(age)"
//字符串的格式化
let min = 3
let second = 4
let time = String(format: "%02d:%02d", arguments: [min, second])
字符串的截取
Swift中提供了特殊的截取方式
let greeting = "hello swift!"
greeting[greeting.startIndex]
// h
greeting[greeting.endIndex.predecessor()]
// !
greeting[greeting.startIndex.successor()]
// e
let index = greeting.startIndex.advancedBy(7)
greeting[index]
// w
//通过索引遍历字符串
for index in greeting.characters.indices {
print("\(greeting[index]) ", terminator: " ")
}// 打印输出 "G u t e n T a g !"
//插入删除字符串
var welcome = "hello"
welcome.insert("!", atIndex: welcome.endIndex)
// welcome 现在等于 "hello!"
welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor())
// welcome 现在等于 "hello there!"
welcome.removeAtIndex(welcome.endIndex.predecessor())
// welcome 现在等于 "hello there"
let range = welcome.endIndex.advancedBy(-6)..<welcome.endIndexwelcome.removeRange(range)
// welcome 现在等于 "hello"
简单的方式是将String转成NSString来使用
在标识符后加:as NSString即可
let myStr = "www.baidu.com"
var subStr = (myStr as NSString).substringFromIndex(4)
subStr = (myStr as NSString).substringToIndex(3)
subStr = (myStr as NSString).substringWithRange(NSRange(location: 4, length: 5))
数组,字典,集合
声明的数组,字典需要进行初始化才能使用,数组类型往往是在声明的同时进行初始化的
数组合并:只有相同类型的数组才能合并,不建议一个数组中存放多种类型的数据
数组的基本操作
var shoppingList = ["Eggs", "Milk"]
print("The shopping list contains \(shoppingList.count) items.")
// 输出 "The shopping list contains 2 items."(这个数组有2个项)
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list is not empty.")
}// 打印 "The shopping list is not empty."(shoppinglist 不是空的)
shoppingList.append("Flour")
// shoppingList 现在有3个数据项
shoppingList += ["Baking Powder"]
// shoppingList 现在有四项了
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 现在有七项了
var firstItem = shoppingList[0]
// 第一项是 "Eggs"
shoppingList[0] = "Six eggs"
// 其中的第一项现在是 "Six eggs" 而不是 "Eggs"
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList 现在有6项
shoppingList.insert("Maple Syrup", atIndex: 0)
// shoppingList 现在有7项
// "Maple Syrup" 现在是这个列表中的第一项
let mapleSyrup = shoppingList.removeAtIndex(0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项,而且不包括 Maple Syrup
// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup"
let apples = shoppingList.removeLast()
// 数组的最后一项被移除了
// shoppingList 现在只有5项,不包括 cheese
// apples 常量的值现在等于 "Apples" 字符串
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
for (index, value) in shoppingList.enumerate() {
print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas
字典和并:需要遍历一个字典,然后将遍历出的 key,value 添加到第二个字典中
集合的基本操作:
元组
元组:元组类型是由 N 个任意类型的数据组成;
例如:var point = (x:10, y:20)/var point = (10, 20)
取元组中的元素: point.x/point.y point.0/point.1
如果使用 let 定义一个元组是常量,是不能够修改的;
函数
函数的定义格式:
func <#name#>(<#parameters#>) -> <#return type#> {
<#function body#>
}
//func是关键字,多个参数列表之间可以用逗号(,)分隔,也可以没有参数
//使用箭头“->”指向返回值类型
//如果函数没有返回值,返回值为Void.并且“-> 返回值类型”部分可以省略
函数的使用注意
- 注意一: 外部参数和内部参数
在函数内部可以看到的参数,就是内部参数
在函数外面可以看到的参数,就是外部参数
默认情况下,从第二个参数开始,参数名称既是内部参数也是外部参数
如果第一个参数也想要有外部参数,可以设置标签:在变量名前加标签即可
如果不想要外部参数,可以在参数名称前加_
- 注意二: 默认参数
某些情况,如果没有传入具体的参数,可以使用默认参数 - 注意三: 可变参数
swift中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数
它们必须具有相同的类型
我们可以通过在参数类型名后面加入(...)的方式来指示这是可变参数
一个函数最多只能有一个可变参数。 - 注意四: 引用类型(指针的传递)
默认情况下,函数的参数是值传递.如果想改变外面的变量,则需要传递变量的地址
必须是变量,因为需要在内部改变其值
Swift提供的inout关键字就可以实现
对比下列两个函数
// 函数一:值传递
//注意:在 swift3.0中已经不推荐这样写
func swap(var a : Int, var b : Int) {
let temp = a;
a = b;
b = temp
}
var a = 10
var b = 20
swap(a, b: b)
print("a:\(a), b:\(b)")
// 函数二:指针的传递
func swap1(inout a : Int, inout b : Int) {
let temp = a
a = b
b = temp
print("a:\(a), b:\(b)")
}
swap1(&a, b: &b)
print("a:\(a), b:\(b)")
输入输出参数: inout
使用注意:
1.传递参数时,必须在实参的前面加上的&
2.不能传入常量或者字面量(比如1)作为参数值(因为它们都是不可以修改的)
3.输入输出参数不能有默认值
4.输入输出参数不能是可变参数
5.输入输出参数不饿能够在使用 let,var 修饰(inout 和 let/var 不能共存)
总结:
输入输出参数不能有默认值,而且可变参数不能用 inout 标记。如果你用 inout 标记一个参数,这个参数不能被 var 或者 let 标记。
输入输出参数可以实现函数的多返回值(其实让函数返回元组类型也能实现多返回值)
函数类型:
函数类型也是数据类型的一种,有形式参数和返回值类型;
///定义一个叫mathFunction的变量,类型是‘一个有两个Int 型的参数并返回一个Int 型的值的函函数”。并让这个新变量指向addTwoInts函数
var mathFunction: (Int, Int) -> Int = addTwoInts
函数类型作为参数类型
//包含三个参数mathFunction : (Int, Int) -> Int, a : Int, b : Int
func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
函数类型作为返回类型
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1}
}
//返回值是一个(Int) -> Int类型的函数
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
函数重载:
函数名相同,函数类型不同;
// 方法的重载:方法名称相同,但是参数不同,可以称之为方法的重载(了解)
func ride(num1: Int, _ num2 :Int , _ num3 :Int) -> Int {
return num1 * num2 * num3
}
方法:
方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。
方法就是函数,只是这个函数和某个类型相关联;
实例方法
实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供
与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致;
在实例方法中修改值类型
结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择 变异(mutating) 这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。方法还可以给它隐含的 self 属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y:1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印输出: "The point is now at (3.0, 4.0)"
注意:不能在结构体类型常量上调用可变方法,因为常量的属性不能被改变,即使想改变的是常量的变量属性也不行;
类型方法
实例方法是被类型的某个实例调用的方法。你也可以定义类型本身调用的方法,这种方法就叫做类型方法。声明结构体和枚举的类型方法,在方法的 func 关键字之前加上关键字 static 。类可能会用关键字 class 来允许子类重写父类的实现方法。
下标脚本
下标脚本 可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问集合(collection),列表(list)或序列(sequence的快捷方式,使用下标脚本的索引设置和获取值,不需要再调用实例的特定的赋值和访问方法。
格式
subscript(index: Int) -> Int {
get {
// 返回与入参匹配的Int类型的值
}
set(newValue) {
// 执行赋值操作
}
}
newValue 的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明 newValue 就算不写,在set代码块中依然可以使用默认的 newValue 这个变量来访问新赋的值。
下标脚本的使用场景:
根据使用场景不同下标脚本也具有不同的含义。通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。
断言
断言是一种实时监测条件是否为 true 的方法
使用场景:
1.监测数组的索引,不能太小或者太大,否则造成数组越界
2.监测传递给函数的参数,如果是无效参数,将不能够在函数中使用
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
使用注意:
断言会导致程序运行的终止,如果不管条件是否成立,都要继续往下执行代码,那就不要使用断言,断言可以保证程序在开发过程中被及时发现,但在发布的时候最好不用;
类的声明:
属性分为存储属性,计算属性以及类型属性;
- 存储属性:
1.就是存储在对象(实例)中的一个变量或者常量.(类似于其他面向对象语言中的成员变量);
2.延迟存储属性:当第一次使用时才进行初始化属性(使用 lazy 修饰),(必须是变量不能是常量);
3.可以给存储属性提供一个默认值,也可以在初始化方法中对其进行初始化
- 计算属性:
1.不是直接存储值,而是提供getter 和 setter方法的;
2.存储属性一般只提供getter方法
3.如果只提供getter,而不提供setter,则该计算属性为只读属性,并且可以省略get{} - 类属性:
1.直接使用类型名访问属性, 类属性使用static来修饰,类属性只能是计算属性
class Student : NSObject {
// 定义属性
// 存储属性
var age : Int = 0
var name : String?
var chineseScore : Double = 0.0
var mathScore : Double = 0.0
// 计算属性
var averageScore : Double {
get {
return (chineseScore + mathScore) / 2
}
// 没有意义.newValue是系统分配的变量名,内部存储着新值
set {
self.averageScore = newValue
}
}
// 类属性
static var corseCount : Int = 0
}
// 设置类属性的值
Student.corseCount = 3
// 取出类属性的值
print(Student.corseCount)
监听属性的改变
- 在OC中我们可以重写set方法来监听属性的改变
- Swift中可以通过属性观察者来监听和响应属性值的变化
通常是监听存储属性和类属性的改变.(对于计算属性,我们不需要定义属性观察者,因为我们可以在计算属性的setter中直接观察并响应这种值的变化) - 我们通过设置以下观察方法来定义观察者
willSet:在属性值被存储之前设置。此时新属性值作为一个常量参数被传入。该参数名默认为newValue,我们可以自己定义该参数名
didSet:在新属性值被存储后立即调用。与willSet相同,此时传入的是属性的旧值,默认参数名为oldValue
willSet与didSet只有在属性第一次被设置时才会调用,在初始化时,不会去调用这些监听方法
同时willSet, didSet和 set,get 不能共存
class Person : NSObject {
var name : String? {
// 可以给newValue自定义名称
willSet (new){ // 属性即将改变,还未改变时会调用的方法
// 在该方法中有一个默认的系统属性newValue,用于存储新值
print(name)
print(new)
}
// 可以给oldValue自定义名称
didSet (old) { // 属性值已经改变了,会调用的方法
// 在该方法中有一个默认的系统属性oldValue,用于存储旧值
print(name)
print(old)
}
}
var age : Int = 0
var height : Double = 0.0
}
继承:
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标脚本(subscript)提供自己定制的实现(implementation)。我们把这种行为叫重写(overriding)。如果要重写某个特性,你需要在重写定义的前面加上 关键字。
重写属性的细节:
1.无论继承来的是存储属性还是计算属性,都可以进行重写;
2.可以提供 getter,setter,willSet,didSet 进行属性重写;
3.可以将一个继承来的只读属性重写为一个读写属性;
4.不可以将一个继承来的读写属性重写为一个只读属性;
注意:重写存储属性需要提供 set 和 get 方法; final 修饰的属性,方法,下表脚本都不能被子类重写,被 final 秀恩是的类不能被继承;
类的构造函数
构造函数类似于OC中的初始化方法:init方法
默认情况下载创建一个类时,必然会调用一个构造函数
即便是没有编写任何构造函数,编译器也会提供一个默认的构造函数。
如果是继承自NSObject,可以对父类的构造函数进行重写
class Person: NSObject {
var name : String
var age : Int
//重写了NSObject(父类)的构造方法
override init() {
name = "John"
age = 0
}
}
// 创建一个Person对象
let p = Person()
自定义构造函数
class Person: NSObject {
var name : String
var age : Int
//注意:如果自定义了构造函数,会覆盖init()方法.即不在有默认的构造函数
init(name : String, age : Int) {
self.name = name
self.age = age
}
}
// 创建一个Person对象
let p = Person(name: "John", age: 18)
字典转模型(初始化时传入字典)
真实创建对象时,更多的是将字典转成模型
注意:
- 去字典中取出的是NSObject,任意类型.
- 可以通过as!转成需要的类型,再赋值(不可以直接赋值)
class Person: NSObject {
var name : String
var age : Int
// 自定义构造函数,会覆盖init()函数
init(dict : [String : NSObject]) {
name = dict["name"] as! String
age = dict["age"] as! Int
}
}
// 创建一个Person对象
let dict = ["name" : "John", "age" : 18]
let p = Person(dict: dict)
字典转模型(利用KVC转化)
利用KVC字典转模型会更加方便
注意:
- KVC并不能保证会给所有的属性赋值
- 因此属性需要有默认值
- 基本数据类型默认值设置为0
- 对象或者结构体类型定义为可选类型即可(可选类型没有赋值前为nil)
class Person: NSObject {
// 结构体或者类的类型,必须是可选类型.因为不能保证一定会赋值
var name : String?
// 基本数据类型不能是可选类型,否则KVC无法转化
var age : Int = 0
// 自定义构造函数,会覆盖init()函数
init(dict : [String : NSObject]) {
// 必须先初始化对象
super.init()
// 调用对象的KVC方法字典转模型
setValuesForKeysWithDictionary(dict)
}
}
// 创建一个Person对象
let dict = ["name" : "John", "age" : 18]
let p = Person(dict: dict)
闭包
闭包和OC中的block非常相似
OC中的block是匿名的函数
Swift中的闭包是一个特殊的函数
block和闭包都经常用于回调
在函数中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
• 全局函数是一个有名字但不会捕获任何值的闭包
• 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
• 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
block写法:
类型: 返回值(^block的名称)(block的参数)
值:
^(参数列表) {
// 执行的代码
};
闭包写法:
类型:(形参列表)->(返回值)
技巧:初学者定义闭包类型,直接写()->()再填充参数和返回值
值:
{
(形参) -> 返回值类型 in
// 执行代码
}
闭包的简写
- 如果闭包没有参数,没有返回值.in和in之前的内容可以省略
尾随闭包写法:
- 如果闭包是函数的最后一个参数,则可以将闭包写早()后面
如果函数只有一个参数,并且这个参数是闭包,那么()可以不写
func someFunctionThatTakesAClosure(closure: () -> Void) {// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
闭包的循环引用问题
//weakSelf?.view
//如果前面的可选类型没有值,后面所有的代码都不会执行
//如果前面的可选类型有值,系统会自动将weakSelf进行解包,并且使用weakSelf
weak vaar weakSelf = self;
request.loadData{ (jsonData ) -> ()
weakSelf?.view.backgroundColor = UIColor.redColor()
}
//推荐写法
weak vaar weakSelf = self;
request.loadData{ [weak self] (jsonData ) -> ()
self?.view.backgroundColor = UIColor.redColor()
}
//unowned不是太安全相当于 OC 中的__unsafe_unretained
weak vaar weakSelf = self;
request.loadData{ [unowned self] (jsonData ) -> ()
self.view.backgroundColor = UIColor.redColor()
}
懒加载
swift中也有懒加载的方式
(苹果的设计思想:希望所有的对象在使用时才真正加载到内存中)
和OC不同的是swift有专门的关键字来实现懒加载
lazy关键字可以用于定义某一个属性懒加载
// 懒加载的本质是,在第一次使用的时候执行闭包,将闭包的返回值赋值给属性
// lazy的作用是只会赋值一次;也是闭包的一种书写形式
lazy var array : [String] = {
() -> [String] in
return ["why", "lmj", "lnj"]
}()