Swift 언어 총정리
Swift 언어의 특징
safe type 이다.
- 타입 안전한, 강타입, 타입이 엄격하다.
- 변수를 처음 만들때의 선언된 형식을 변경할 수 없다.
ex.
var str = "hello"
str = 123 <-- error
Type Annotations
- 타입을 명시해주는 거라고 생각하자.
ex.
var myAge: Int = 0
var screenHeight: Float = 560
var vs let
- var: 변할수 잇는 값을 저장하는 변수를 선언
- let: const 와 동일
booleans
- true or false
ex.
var isOpen = false
if isOpen { <-- !isOpen 하면 기존 false 가 true 로 변함
/* code */
} else {
/* code */
}
Tuples
- ( , ) 의 형태
ex.
var topTitle = ("main", "mainIcon.png")
| |
0 1
topTitle.0 --> main
topTitle.1. --> mainIcon.png
/* dictionary 처럼 키를 지정할 수 있다. */
vat httpError = (statusCode: 404, description: "not found")
httpError.statusCode
httpError.decription
optional
- 값이 있을수도 있고 없을수도 있다.
- 값이 있다.
- 0 --> 다쓴 상태인 값 - 값이 없다.
- NULL --> 값이 아에 없는 상태
var myAge: Int? = 0 <-- int 타입이긴 한데 값이 없을 수도 있다. (?)
myAge = nill <-- 위 처럼 옵셔널을 해주어야지 에러가 안난다.
// 값이 없는 상태 체크 --> 하지만 이렇게 하면 틀릭 로직이다. (Swift 언어 스팩)
if myAge == 0 {
// alert - 나이를 입력해 주세요.
}
if myAge == nil {
// alert - 나이를 입력해 주세요.
}
- 옵셔널은 그냥 사용할 수 없는 경우가 많이 있다.
var a: Int? = 10
var b: Int? = 20
var sum = a + b
- unwrapped or unwrapping
- Int? -> Int
- String? -> String
- 이러한 에러를 해결하려면 다음과 같이 하면됨
- var sum = (a ?? 0) + (b ?? 0) : coalesce
- var sum = a! + b!: force unwrap
- 주의사항: 값이 무조건 있는 상태로 가정한다. --> 값이 없는 상태면 예외상황이 발생한다.
- 값이 무조건 있을때만 사용하자. 아니면 앱이 죽는다.
- if Statements 를 사용한다.
var a: Int? = 20
if a != nil {
print(a) --> Optional(20) 으로 출력됨
}
if let hasNumber = a {
print(hasNumber) --> 20으로 출력됨
}
if var hasNumber = a { --> unwrapping 도 해주고 var 로 선언했기 때문에 변수 수정이 가능하다.
hasNumber = hasNumber * 2
print(hasNumber) --> 20으로 출력됨
}
- guard 사용 : class 나 function 안에 쓴다.
- 함수의 시작부분에 써서 반드시 가져가야할 조건들을 검사하는 파트
- 조건이 True 이면 guard 문은 그냥 지나가고, false 이면 else 구문을 수행한 뒤 함수를 바로 종료한다.
func testFunc(){
guard let hasNumber = a else{
return
}
print(hasNumber)
print("end")
}
기초 연산자
- +, -, *, /
- %: trunctaiong reminder operation
- 주의사항: c언어 처럼 변수의 타입을 주의하자., 변수 타입을 통일해야 한다. (type safe)
비교 연산자
- ==, 부등호 등등...
유니코드
- 조건: 숫자만 입력받을 수 있어야 한다.
let inputValue = "7"
"\u{30}" == "0" --> true
if inputValue >= "\u{30}" && inputValue <= "\u{39}" {
print("숫자다")
}else{
print("숫자가 아니다")
}
- 알파벳 : "\u{41}" ~ "\u{7a}"
String
- 문자열의 문자 하나하나를 가지고 오려면 다음과 같이 하면 된다.
let myName = "yoon yeoung jin"
for character in myName {
print(character)
}
- 문자열의 '+' 은 두 문자열을 붙이는 역할을 한다.
- value.description --> value를 string 으로 변환해준다.
let isOn = true
isOn.description --> "true"
let myNumber = 123
myNumber.description --> "123"
- 문자열 안에 값을 넣고싶다. ==> (변수)
let myNumber = 123
"my number is \(myNumber)"
- 문자열 나누기 --> split(separator: "")
let myNumber = 12.33
myNumber.split(separator: ".") ==> ["12", "33"]
collection type
- array
- 선언 방법: var myName = Array() or var myName = Int
var myNames = Array<String>()
var myAges = [Int]()
- .append() 를 사용해서 데이터를 넣는다.
myNames.append("yoon")
myNames.append("yeoung")
myNames.append("jin)
- index 개념으로 접근
- index out of range에 대한 방어 코드를 만드는 것이 매우매우 좋다. (안전한 코딩 스타일)
let index = 3
if myNames.count > index { // out of range에 대한 방어 코드 추가.
print(myNames[index])
}
- .append(contentsOf: array): array 자체를 넣는다.
myNames.append(contentsOf: ["hi", "hello"]) ==> ["yoon", ..., "hello"]
myNames + ["hi", "hello"]
- .remove() : 값을 지움 (first, last, all)
- .isEmpty: 리스트 안에 값이 없는지 확인하는 함수
- .insert(value, at: int): int 자리에 값을 넣는다.
- for문으로 하나한 값을 추출할 수 있다.
- .enumerated(): index와 value를 둘다 가져올 수 있다.
for (index, name) in myNames.enumerated() {
print(index, name)
}
- set
- 기본 형태: var names = Set()
- set은 순서를 보장하지 않는다.
- 값이 동일한 건 중첩되서 넣어지지 않는다.
- .insert() 를 사용해서 데이터를 넣는다.
var numbers1: Set = [1,2,3,4,5]
var numbers2: Set = [4,5,6,7,8]
numbers1.intersection(numbers2) // 교집합
numbers1.union(numbers2) // 합집합
numbers1.symmetricDifference(numbers2) // 대칭차집합
numbers1.subtracting(numbers2) // 여집합
- dictionary
- 키-value 형식으로 되어있음
- 순서 정의 개념이 없다.
- 선언 방법 : var nameOfStreet = String : String (Any: 어떤 타입이든 상관 없다는 의미)
- 결과값은 무조건 optional 이다. nil 값이 있을 수 있음
- crash 부분은 신경 안써도 됨. (값이 있고 없고만 생각하면 됨.)
- nil 넣으면 해당 값이 없으니까 키도 없어진다.
- .keys ==> 키 값 리스트를 가져올 수 있음
Control Flow (흐름 제어)
- for, if, switch, while
for index in 0..<5 { <-- 0 1 2 3 4
print(index)
}
- while : 왠만하면 사용하지 않는게 좋다.
- 무한으로 반복문안에서 계속 실행되는 경우 -> 멈춰버림.
while condition {
// code..
}
function
- class 와 sturcture 안에도 만들 수 있다.
- 계산기 만들기 --> class
- 더하기 기능 --> function - 선언
let a = 10
func plus(num1: Int){
print("받은값", num1)
}
plus(num1: a)
let a = 10
let b = 20
func plus(num1: Int, num2: Int) -> Int{ <-- 리턴 값을 받고 싶을 떄 -> <type> 을 넣어주면 된다.
print("sum = ", num1 + num2)
return num1 + num2
}
plus(num1: a, num2: b)
func plus(num1: Int, num2: Int) -> (String, Int) { <-- 여러개의 결과값 리턴
return ("결과값은", num1 + num2)
}
func plus(_ num1: Int, _ num2: Int) -> (String, Int) { <-- argument label 을 밖에서 안써도 됨
return ("결과값은", num1 + num2)
}
plus(a,b)
- View, present, Display : 화면에 뿌려주는 로직.
- 화면에 뿌려주는 로직도 많이 필요하다.
let a = 10
let b = 20
func plus(_ num1: Int, _num2: Int) -> Int {
return num1 + num2
}
func minus(_ num1: Int, _num2: Int) -> Int {
return num1 - num2
}
func multiply(_ num1: Int, _num2: Int) -> Int {
return num1 * num2
}
var inputButtonType = "+"
if inputButtonType == "+"{
printf("연산 결과", plus(a,b))
}else if inputButtonType == "-"{
print("연산 결과", minus(a,b))
}else if inputButtonType == "*"{
print("연산 결과", multiply(a,b))
}
// 위와 같이 형식이 비슷한 함수를 계속 사용하기에는 유지보수에 좋지 않다. 이를 다음과 같이 할 수 있다.
func displayCalc(result: (Int, Int) -> Int){ <-- 함수를 입력받을 수 있게 설정
print("연산 결과", result(a,b))
}
if inputButtonType == "+"{
displayCalc(result: plus)
}else if inputButtonType == "-"{
displayCalc(result: minus)
}else if inputButtonType == "*"{
displayCalc(result: multiply)
}
- 위와 같이 하는 것에 장점
- 코드를 읽을때 쉽게 읽을 수 있다.
- 실수 유발을 최소화 할 수 있다.
- 유지보수 포인트가 적어진다. - 함수를 작성하는데 하나의 기능만 하는 함수를 만들도록 하자.
- 기능이 많은 함수를 만들게 되면 나중에 코드를 보았을 때 읽기 힘들어 진다.
- 코드가 지저분해진다.
Closure
- func 과 유사하다.
- 이름이 없다. - 그렇기 때문에 따로 변수로 이름을 지정해주어야 한다.
//function
func myScore(a: Int) -> String {
return "\(a)점"
}
// closure
let myScore2 = { (a:Int) -> String in
return "\(a)점"
}
// let score = myScore(a:40)
let score = myScore <-- (int) -> String : int 가 들어가고 String 이 나오는 타입이다. (type)
score(50) // function 호출
myScore2(20) // closure 호출
- 축약 (생략)
// closure
let myScore3 = { (a:Int) -> String in
"\(a)점" <-- 리턴문 한줄만 있을 경우 return 을 생략 가능
}
// closure
let myScore4 = { (a:Int) in <-- return 값으로 추론할 수 있어서 String이 생략 가능
"\(a)점"
}
let myScore5: (Int) -> String = { a in <-- 타입을 먼저 선언
"\(a)점"
}
let myScore6: (Int) -> String = {
return "\{$0}점" <-- 첫번쨰 파라미터의 값을 $0으로 표시 가능 (n번째 --> $(n-1))
<-- in을 쓰면 안된다.
}
- 예제
let names = ["apple", "air", "brown", "red", "orange", "blue", "candy"]
// 특정 문자열을 찾는 함수
// 조건 -> 찾는다 -> 특정한 글자가 포함된 것을 찾는다.
// 조건 -> 찾는다 -> 입력한 글자로 시작하는 첫글자를 찾는다.
let containsSomeText: (String, String) -> Bool = { name, find in
name.contains(find){
return true
}
return false
}
let isStartSomeText: (String, String) -> Bool = { name, find in
if name.first?.description == find {
return true
}
return false
}
func find(findString: String, condition: (String, String) -> Bool) -> [String] {
var newNames = [String]()
for name in names {
if condition(name, findString) {
newNames.append(name)
}
}
return newNames
}
find(findString: "a", condition: containSomeText)
find(findString: "a", condition: isStartSomeText)
func someFind(find: String) -> [String] { <-- 이렇게 하면 새로운 조건이 추가될 때 함수 내부를 수정해야된다. 이를 위처럼 closure을 사용하면 이쁘게 코딩 할 수 있다.
var newNames = [String]()
for name in names {
if name.contains(find) {
newNames.append(name)
}
}
return names
}
- 실제로 사용해보자.
import UIKit
var names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
// sort
// let sorted = names.sort() // <-- 결과물이 나오는 형태면 과거형으로 네이밍 되어 있다.
names.sort { $0 < $1 } // throws : error handling
names.sort(by: { $0 < $1 }) // 여기서 by: 하나밖에 없음으로 생략 가능 위에꺼랑 동일
names.sort(by: <) // < 자체가 closure 이다. 여기서는 by: 를 생략할 수 없다.
===================================================================================
import UIKit
var names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
// sort
names.sort { (lhs, rhs) -> bool in
return lhs > rhs
}
names.sort(by: {$0 > $1 })
names.sort() {$0 > $1}
names.sort{$0 > $1 }
names.sort(by: >)
Enumerations
- enum
- 타입을 분류한다.
- Ex. 도서관 -> 항목 -> 소설, 문제집, 패션, 만화책
import UIKit
// 분류만 하고 싶다.
enum BookType {
case fiction(title: String, prince: Int, year: Int)
case comics(title: String, prince: Int, year: Int)
case workbook(title: String, prince: Int, year: Int)
}
extension BookType { <- enum의 확장 기능을 만들 수 있다.
var typeName: String {
switch self {
case .comics:
return "comics"
case .fiction:
return "fiction"
case .workbook:
return "workbook"
default:
""
}
}
}
var bookStyle: BookType?
var books = [BookType]()
func saveBook(book: BookType) {
books.append(book)
}
saveBook(book: .comics(title: "aaa", prince: 5000, year: 2020))
for book in books {
if case let BookType.comics(title, _, _) = book { <- 안쓰는 변수를 _ 처리 하지 않으면 워닝 뜸
print("comics", title, book.typeName)
}
switch book{
case let .comics(_, prince, _):
print(prince)
case let .fiction(_, prince, _):
print(prince)
default:
break
}
}
class
- 정의
class MyInfo {
init (gender GenderType){
self.genderType = gender
}
enum GenderType { <- enum 선언 가능
case male
case female
}
private var genderType: GenderType <- init에서 gender 값을 받아오면 operator 설정을 안해도 된다. private 로 외부에서는 값에 접근 할 수 없다.
var name = "" <-- 변수 만들기 가능
var age = ""
func isAdult() -> Bool { <-- 함수도 가능
if age > 19 {
return true
}
return false
}
}
var myInfo = MyInfo(gender: .female)
myInfo.genderType = .male <- private 로 외부에서 접근 할 수 없다. 즉 에러 발생
myInfo.age = 20
var myInfo2 = myInfo <- 원본은 하나가 있는거고 그거를 참조하는게 myInfo2와 myInfo 두개가 있는 거다.
myInfo2.age = 100
var myInfo3 = myInfo2
myInto.age // 100
myInto2.age // 100
myInto3.age // 100
- class는 참조 타입이다.
- class에는 상속 개념이 존재한다.
import UIKit
// 클래스 내부의 동일한 것들을 하나로 만들어서 관리하자.
/* 부모 클래스 */
class GameInfo {
var homeScore = 0
var awayScore = 0
final func presentScore() -> String { // final을 사용하면 override를 사용할 수 없다.
return homeScore.description + " : " + awayScore.description
}
}
/* 자식 클래스 */
class Soccer: GameInfo {
var time = 0 // <-- 자식 클래스 만의 변수를 선언 가능
}
class Baseball: GameInfo {
/* 기존의 부모 클래스에 있는 로직을 쓰지 않고 override func으로 정의되어있는 로직대로 쓰겠다는 의미 */
override func presentScore() -> String {
return homeScore.description + " 대 " + awayScore.description
}
var round = 0
}
class Football:GameInfo {
}
let soccer = Soccer()
soccer.awayScore = 1
soccer.homeScore = 2
soccer.presentScore()
properties
- 어딘가에 들어있는 형태를 properties라고 한다.
import UIKit
class MyInfo {
// stored property : 값을 저장할 수 있는 property
var name = ""
var age = 0
// lazy stored property
/* lazy 를 사용하면 사용하려고 할때 메모리를 사용한다. (class를 생성할때 메모리에 올라가는게 아님) */
lazy var myProfiles = [UIImage(named: "n"), UIImage(named: "a"), UIImage(named: "b"), UIImage(named: "c"), UIImage(named: "d")] // <- 한번에 너무 많은 변수를 사용하면 오버헤드 발생
// computed property : 계산된 프로퍼티
// 로직이 실행되서 값을 만들어지는 변수
// 값을 입력할 순 없고 사용만 할 수 있다.
// 해당 로직에서는 get을 생략 가능하다.
var isAdult: Bool {
get{
if age > 19 {
return true
}
return false
}
}
// email -> 보안 -> 암호화 된 값으로 사용한다. (항상)
// 항상 무언가를 해야된다고 하면 아래와 같이 로직을 작성할 수 있다.
var _email = ""
var email: String {
get { // 값을 가져오기
return _email
}
set{ // 값을 셋팅하기
_email = newValue.hash.description
}
}
}
let myInfo = MyInfo() // 이때는 lazy 변수는 메모리에 올라가지 않는다.
myInfo.email = "[email protected]" // 값을 입력하면 set이 실행된다.
myInfo.email // 값을 호출하면 get 이 실행된다.
initializer
- 생성자
import UIKit
class MyInfo {
var name: String
var myId: String
var age = 0
var isAdult: Bool
// designated initializer
init(n: String, id: String) {
self.name = n
self.myId = id
self.isAdult = (age > 19) ? true : false
}
// init(){ // 위에 선언된 변수에서 값을 정해주지 않은것은 무조건 init에 셋팅 해주어야 한다.
// self.name = ""
// self.myId = ""
// self.isAdult = (age > 19) ? true : false
// }
// init(id: String){ // init 은 여러개 만들 수 있다.
// self.name = ""
// self.myId = id
// self.isAdult = (age > 19) ? true : false
// }
// convenience initializer (편의)
// 필수 조건 - 다른 init을 반드시 실행해야 한다.
// 하나의 init을 공통으로 사용할 수 있게 만든다.
convenience init(){
self.init(n: "", id: "")
}
convenience init(id: String){
self.init(n: "", id: id)
}
}
var myInfo1 = MyInfo(n: "yoon", id: "abcd") // MyInfo.init() 과 동일한 형식
myInfo1.myId
myInfo1.name
var myInfo2 = MyInfo()
myInfo2.myId
myInfo2.name
var myInfo3 = MyInfo(id: "abcd")
myInfo3.myId
myInfo3.name
struct MyConfig { // structure 일때는 init을 만들지 않아도 자동으로 입력하게끔 할 수 있다.
var conf: String
}
var myCon = MyConfig(conf: "test")
- convenience initializer : init이 여러개인 상황에서 공통된 부분을 활용하여 깔끔하게 사용할 수 있게 만들어 준다 .
Deinitialization
- 메모리 해제
import UIKit
var a: Int? = 10
a = nil // nil 을 주면 메모리가 없어진다.
class Game {
var score = 0
var name = ""
var round: Round?
init (){ // 메모리에 올라갈때 실행
print("game init")
}
deinit { // 메모리에서 해제될때 실행
print("game deinit")
}
}
class Round {
weak var gameInfo: Game? /* 상호 참조 */ // game 의 정보가 없어지면 나도 없어질거야! <-- 이렇게 하면 상호 참조로 사용 가능하다
var lastRound = 10
var roundTime = 20
deinit {
print("round deinit")
}
}
var game: Game? = Game()
//var game2: Game? = game
/* 참조하는 곳이 두곳이기 때문에 두곳을 모두 메모리 해제를 시켜주어야 game 메모리가 해제된다. */
//game = nil
//game2 = nil
var round: Round? = Round()
// game = nil // 메모리 해제
/* 이렇게 상호 참조를 하게 되면 nil을 해도 deinit이 되지 않는다. */
round?.gameInfo = game
game?.round = round
game = nil
round = nil
- 주의: 상호 참조를 하게 되면 메모리 해제가 안된다. 이때 weak를 사용하자.
structure
- 구조체
- class와 기본적인 구조는 동일하다.
- value type이다.
struct ImageType{
var type = ""
}
var imageType = ImageType() // 아래 세개는 하나를 참조하는 것이 아닌 분리되어있는 변수이다.
var imageType2 = imageType
var imageType3 = imageType2
- 상속이 불가능
extention
- 기능을 확장할때 사용
- struct, class, enum, protocol
import UIKit
// 숫자 (int) 짝수, 홀수
extension Int {
var oddOrEven: String {
if self % 2 == 0{
return "짝수"
}
return "홀수"
}
}
// UIColor
extension UIColor {
class var mainColor1: UIColor {
UIColor(red: 50/255, green: 70/255, blue: 120/255, alpha: 1)
}
}
var button = UIButton()
button.titleLabel?.textColor = .mainColor1
protocol
- 규격, 규약, 규칙, 뼈대
- 여러 곳에서 사용하는 반드시 가져야 하는 것들을 선언하는 것
- 값이 정하는, 구현되는 부분은 넣을 수 없다.
- 놓치는 것 없이 구현할 수 있다.
import UIKit
protocol UserInfo {
var name: String { get set }
var age: Int { get set } // set 을 안쓰면 써도 되고 안써도 되는 개념임. 하지만 변수가 let 이 있으면 set 을 사용할 수 없다.
func isAdult() -> Bool
}
protocol UserScore {
var score: Int { get set }
}
protocol UserDetailInfo: UserInfo, UserScore{ // <-- protocol 에서도 protocol 을 합성할 수 있음
/* code */
}
extension UserInfo { // UserInfo 에서 확장 기능으로 구현함으로 아래 클래스에서는 따로 구현할 필요가 없다.
func isAdult() -> Bool {
if age > 19 {
return true
}
return false
}
}
class Guest: UserInfo, UserScore { // <-- 여러개의 protocol을 받을 수 있다.
var name: String = ""
var age: Int = 0
var score: Int = 90
}
class Member: UserInfo {
var name: String
var age: Int
init (name: String, age: Int){
self.name = name
self.age = age
}
}
class VIPMember: UserInfo {
var name: String = ""
var age: Int = 0
}
class UserInfoPresnter {
func present() {
let guest = Guest()
let memver = Member(name: "jame", age: 25)
let vip = VIPMember()
let members: [UserInfo] = [guest, memver, vip] // any type 으로 하게 되면 내부에 어떤 값이 있는지 members 변수가 알 수 없다.
for element in members{
print(element.name)
}
}
}
let presenter = UserInfoPresnter()
presenter.present()
inheritance
- 상속, 이거는 class 에서만 사용된다.
- super. 를 통해서 부모 클래스에 접근 가능하다.
import UIKit
class UserInfo {
var name = ""
var age = 0
func isAdult() -> Bool {
if age > 19 {
return true
}
return false
}
}
class Guest: UserInfo {
override func isAdult() -> Bool {
return true
}
func present () {
name = "yoon"
print(name)
print(super.name) // super. 를 통해서 부모 클래스에 접근 가능하다.
print(isAdult())
print(super.isAdult())
}
}
let guest = Guest()
guest.present()
generic
- 타입이 여러가지를 사용하게 될때 사용한다.
import UIKit
// generic <내가 정한 임의의 타입>
// stack
// where 구문을 사용하여 제한을 할 수 있다.
// Equtable은 비교할 수 있는 것들. (dict 는 안됨)
struct IntStack<MyType> where MyType: Equatable{
var items = [MyType]()
mutating func push(item: MyType) { // struct 구조는 자기 스스로가 내부 값을 변경할 수 없다. 이를 실현시키려면 mutating을 사용하면 됨
items.append(item)
}
mutating func pop() -> MyType? {
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
var myStack = IntStack<Int>()
myStack.push(item: 4)
var myStack2 = IntStack<String>()
myStack2.push(item: "a")
myStack.pop()
myStack2.pop()
// queue
- 요즘 추세는 dictionary를 사용을 많이 안하는 추세
higher order function
- 고차 함수
import UIKit
let names = ["yoon", "kim", "lee", "min"]
// map -> 원하는 스타일로 변경 (길이는 같다)
let names2 = names.map { $0 + "님"}
names2
let names3 = names.map { (name) in
name.count
}
names3
let name4 = names.map { (name) in
name.count > 3
}
name4
// filter -> 거른다.
let filterNames = names.filter { (name) -> Bool in
name.count > 3
}
filterNames
// reduce 하나로 뭉친다. 통합. 합친다.
let sumName = names.reduce("") { (first, second) in
first + " " + second
}
sumName
// compactMap
let numberArr = [1,2,3,4,5, nil, 6, nil, 8]
let numbers = numberArr.compactMap { (num) in
return num
}
numbers
// flatmap
let numbers2 = [[1,2,3], [4,5,6]]
let numbers2_flatmap = numbers2.flatMap { $0}
numbers2_flatmap
- 고차함수를 잘 사용하면 깔끔한 코드를 작성 가능하다.
Author And Source
이 문제에 관하여(Swift 언어 총정리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@palinyee12/Swift-언어-총정리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)