2022년 12월 1일 목요일

swift mini ebook

swift mini ebook

Swift 로 Apple App 만들기 (Kotlin유저를 위한 Swift기초)

기존의 Apple App은 Objective-C라는 괴상한 언어로 만들었습니다. 친인간적이라는 문법이 오히려 위화감을 일켜서 일부러 아이폰앱개발을 안하는 사람도 있을 정도였습니다.
이제나마 정신을 차린 애플이 Swift라는 언어를 발표하고 앱개발에 사용할수 있도록 하여 지금은 거의다 Swift 언어로 개발하고 있습니다.

빠르고, 간단하다고는 하지만 Kotlin을 사용해본 사람에게는 애플다운 개소리수준이라고 생각되고 그냥 Kotlin으로도 아이폰앱개발이 되어 Xcode나 Swift따위는 없어졌으면 하는 바램뿐입니다.

그래도 아직은 어쩔수 없으니 Swift로 아이폰 앱을 만들어 봅시다.

Hello World생성

Xcode를 기동후에 새프로젝트에서 iOS 를 선택하고 다음화면에서 Language와 Interface를 SwiftUI를 선택하고 프로젝트를 생성합니다.

enter image description here
아래와 같이 생성됩니다.
enter image description here
Android의 Compose랑 같은 구성이죠 역시 애플이 좀더 성의껏 만들어 주네요.

Swift 코딩 기본

var, let 변수

var는 mutable 변경가능, let은 Immutable 변경불가 입니다.
타입은 “변수명 : String” 처럼 변수명 뒤에 붙여줘도 되지만 타입추론을 하기에 생략가능합니다.

var name: String = "kim"

배열(Array)과 딕셔너비(DIctionary)

애플의 작명센스 Dictionary 는 맵이라고 생각하면 됩니다. 다들 map 이라 할때 같은걸 DIctionary하고 똘끼는 인정해 줘야 합니다.
대괄호로 초기데이터 선언합니다.

var arr = ["A", "B", "C"]
var dick = [
"key-A": "value-A", 
"key-B": "value-B",  
"key-C": "value-C",  
]
arr[0] // A
dick["key-A"] // value-A


var dick2: [String: Int] = [
"key-A": 1, 
"key-B": 2,  
"key-C": 3,  
]

조건문 if, switch

switch 에서는 범위지정, 문자, 숫자, 불린 등 다양하게 지정가능.
if는 다른 프로그램과 같습니다.

반복 for , while

for가 좀 다르다. for 와 in 으로 합니다.

for moneys in pocketList {}
for (usename, password) in datas {}
for i in 0..<100 {}

while은 같습니다.

Null 값 제어 Optional 과 Optional Binding

kotlin 처럼 ? 와 ! 마크기본 제어합니다.
또한 let 이라는 키워드르 제공하여 옵셔널 바인딩을 할수 있습니다. kotlin에서 변수?.let{} 이 더 직관적이고 사용성이 좋네요.
Swift에서는 if let 변수명 이라는 형식으로 지정하여 변수값이 null이라면 코드를 실행하지 않습니다.

if let email = wtfNull {}
if let email = wtfNull, email == "MAIL" {}
if let email = wtfNull, name = wtfNullMeToo {}
let result = wtfNull?.lowercased()
let isEmptyArray = array?.isEmpty == true

if let 을 guard let으로 바꿔 쓸수 있습니다.
if let email 이런식으로 쓰면 email 스코프가 if 이하가 되는데 guard를 쓰면 email 스코프가 현재 레벨로 되기 때문에 다음줄의 코드에서 email값을 쓸구있습니다. 즉 검사도 하고 사용도 하고~

Lazy

Kotlin 의 Lazy 와 동일하게 호출되는 시첨에서 초기화를 합니다.

lazy var a:Int  = 10
lazy var b = B()

Class의 정의

Java 랑 동일합니다. 초기값을 init() 함수에서 실행합니다.
상속도 마찬가지로 : 기호를 사용합니다. (왜? -> 이런거 안쓰고?)

class Aa {}

Struct 정의

Swift에서는 전통적인 Struct라는 구조체를 이용할수있습니다. 쉽게말해 구조체는 데이터를 들고 있고 다른값에 대입시에 값을 통채로 복사 합니다.
Class는 다른 변수에 대입해도 같은 메모리 주소를 보고있는것과는 다릅니다.
데이터 객체라면 구조체를 처리객체라면 클래스를 사용합니다.
데이터만 들고 있기떄문에 상속 은 없습니다.

struct Aa {}

프로토콜

인터페이스를 프로토콜이라 말합니다. (다들 인터페이스 라고 하는데 애플만 프로토콜라고 부르는 변태값은 상황)

protocol Aa: Bb, Cc {}
Class UseAa : Aa {}

Generic

구조체나 클래스, 인터페이스(아…프로토콜이지요…익숙해 지지 않네요)에서 타입을 유동적으로 받아들이기 위한 제너릭을 지원합니다.

class My<HeheGeneric> {}
class My<HeheGeneric>: Mom<HeheGeneric> {}

프로토콜에서는 associatedtype이라는 키워드를 지정하여 프로토콜 구현부 에서 타입을 지정할수 잇도록 해줍니다. 인터페이스에 지저분하게 T, U같은 걸 안붙이고 깔금하게 제러릭 타입을 생성할수 있도록 해줍니다.

protocol TestInterface {
    associatedtype DataType
    func test(data: DataType) -> String
}
struct TestStruct {
    var a = "testStruct"
}

class TestImplement: TestInterface {
    typealias DataType = TestStruct

    func test(data: DataType) -> String {
        return data.a
    }
}

where 구문을 통해 제너릭에 제한을 걸수도있습니다.

함수 func

함수는 func 키워드로 정의하고 인수는 변수명:타입으로 지저합니다. 반환값은 “-> 타입명” 으로 지정합니다. 리턴값형식지정이 화살표랍니다. 어이없네요.

func hello1(usename: String) -> String { return "A" }
func hello2(fakename usename: String) -> String { return "A" }
func hello3(_: String) -> String { return "A" }

hello1(usename: "USER")
hello2(fakename: "USER")
hello3( "USER")

함수를 리턴값으로 반환

고차함수처럼 함수가 함수를 반환하는, 것도 가능합니다.
함수명() -> (인수타입) -> 리턴값 { return () -> 리턴값}
생각을 하고 만든건지 모르겠네요. -> 가 두개나 나옵니다.

func hello() -> (String, Int) -> Bool {
   func innnerHello(name:String, age:Int) -> Bool {
       return age > 0
   }
   return innnerHello
}
또는 Closer로 간단하게 이렇게 도 가능합니다.(다만 in 이라는 괴상한 키워드를 써야한다는)
func hello() -> (String, Int) -> Bool {
   return { name, age in
       return age >0
   }
}

또는 $0, $1 인자의 번호를 대신해서 입력하여 간결하게 가능한답니다.( 에러나도 알수없게 딱 좋게 만들었구나)
func hello() -> (String, Int) -> Bool {
   return { 
       return $1 >0
   }
}
// 클로저 내부가 한줄일때.
func hello() -> (String, Int) -> Bool {
   return { $1 >0 }
}

// 이렇게 해괴하게 짜는게 정상인지?

let myHello = hello1()
myHello("name", 18)

## 인자를 고차함수로 지정
고차함수에서 그렇듯이 인수에도 클로저를 지정할수 있습니다.
```swift
func hello(name: String, age:Int ,  hey : Int -> Bool ) -> Bool  { 
	return hey(age)
 }
hello(name : "wtf", age: 20, hey: {(checkage:Int)-> Bool in return checkage>20 }) 

또는 한줄이니까
hello(name : "wtf", age: 20, hey: {$0>20 }) 

kotlin과 마찬가지로 맞막 변수가 클로저일때는 {}블럭으로 본문처럼 사용할수있습니다.

hello(name : "wtf", age: 20){ 
   $0>20 
} 

변수타입을 클로저로 지정

고차함수에서 그렇듯이 변수에 클로저를 지정할수 있습니다.

let hello:(String, Int ) -> Bool = { $1 > 18 }
hello("wtf", 20) // false겟죠.

변수의 속성 Properties

변수의 속성? 이라 하니까 좀 이상합니다만 swift에서는 변수의 속성(get,set) 을 아래와 같이 두가지로 구분합니다.

  • Stored Property : 데이터 주는 대로 저장하거나 내뱉는 변수
  • Computed Property : 데이터를 주거나 받을때 무언가 추가 처리할수 있는 변수
 var forSavingString:String? = "first hello"
 var d : String? {
     get { self.forSaving?.uppercased() }
    
     set { self.forSaving = newValue!.lowercased() }
     
    }
 }

willset (값이 바뀌기전)과 didSet( 값이 바뀐후) 할때도 추가처리를 할수있습니다. 이런 지저분한 기능 넣지 말라고.

var d : String {
    willSet {
        print("\(self.d) --> \(newValue)")
    }
    
    didSet {
      print("\(oldValue) --> \(self.d)")
    }
}

함수 인자에 inout 선언

swift의 함수선언에는 inout 이라는 키워드가 인자 형식앞에 있는것을 볼수 있습니다. in-out은 자바의 레너릭에서 쓰던 키워드이지만 Swift에서는 전혀 관련이 없습니다.
여기서 inout은 함수인자의 데이터를 값복사, 즉 함수로 전달된 일반 변수는 값이 복사되기 때무에 함수안에서 아무리 변경해도 바뀌지 않는데 이를 참조형태로 전달하여 함수내에서도 인자의 값을 바꿀수 있도록 되어 있습니다.
마치 C에서 포인터를 던지로 함수안에서 값을 수정하는 거랑 같습니다. 실제로 & 기호를 붙여 전달합니다.

func A(number: inout Int){
  number ++
}
var numNumber = 1
add(number: &numNumber)

잘못쓰면 디버깅에만 며칠 날리게 저렇게 또 함정을 만들어 주세요.

Enum

case 라는 단어로 정의합니다. if문에서도 case 를통해 데이터체크가 가능합니다. kotlin 의 Sealed 처럼 여러가지 형태로 가능합니다. 단 Sealed처럼 강력하지는 않습니다.

enum Test {
    case dog
    case cat(Int)
    case myPocket(String, Bool)
}

if case Test.dog = enum할당변수 {} // enum할당변수 = Test.dog
if case Test.cat(10) = enum할당변수 {} // enum할당변수 = Test.cat(1)
if case Test.myPocket("Hi", false) = enum할당변수 {} // enum할당변수 = Test.myPocket("Hi", true) 

타입 검사, 캐스팅

as 와 is 가 있습니다.

let a = A()
print( a  is A)
a as? B

확장기능 Extension

기존의 타입에 속성이나 함수를 추가할수있습니다.

extension Int {
  init(stringNumber : String){ self = self +stringNumber.count }
  var isOdd: Bool { return self % 2 == 1 }
  func execMe(by: Int) -> Int {
     return self * by
  }
   func printMe(exe: (Int) -> String) -> String {
      return exe(self) + String(self)
   }
}
Int(stringNumber: "3") // init에 의해 Int에 초기설정가능
10.isOdd
10.execMe(by: 2)
10.printMe{ num in String(num) + "me"}

subscript

책에 보면 작은 글씨로 [1] 처럼 주석 비슷하게 보이는 표를 볼수 있습니다. subscript 는 그것처럼 여러개의 멤버요소를 같는 객체, 구조, Enum 등에서 []기호를 통해 빠르게 특정요소에 참고 하고자하는 쓸데없고 의미없는 기능입니다.

subscript(index: Int) -> Int {
    get {
        // 적절한 반환 값
    }
    set(newValue) {
        // 적절한 set 액션
    }
}

당연히 요소의 번호 index와 get, set 이 있습니다.

extension String {
    subscript(wantIdx: Int) -> String? {
        guard(0..<count).contains(wantIdx) else { 
            return nil
        }
        let result = index(startIndex, offsetBy: wantIdx)
        return String(self[result])
    }
}
let str =  "abcde"
str[2] // c

클래스나 구조체에도 가능합니다.

class Numbers {
   let numberList = (0...100).map { "Index\($0)" }
    subscript(wantNumIdx: Int = 0) -> String? {    
       self.numberList[wantNumIdx]
    }
}

let numbers = Numbers()
numbers[10]

SwiftUI

안드로이드 개발자로서 가장 부러웠던 XCode의 스토리 보드를 이제는 SwiftUI로 선언적 UI 로 바뀌는 추세입니다.
어차피 XCode 는 개발자가 만지고 있고, 스토리 보드도 개발자가 하는데 디자인 보면서 배치하는게 무슨 의미가 있나요. 그냥 코드에 UI선언해서 미리보기만 실시간으로 해주면 변경도 쉽고 이벤트나 데이터 연동도 쉬운데 말입니다.
Android 의 Compose 과 비슷하게 버튼, 텍스트등을 코드로 지정할수 있습니다.
SwiftUI 기본 객체 강좌는 구글에 차고 넘치니 찾아보시고 한번씩만 해보면 됩니다.

0 comments:

댓글 쓰기