DADAHAE's Log
비싼 장난감 가지고 노는 중 (❁´▽`❁)*✲゚*
[TIL] 22-10-05: Swift (Closure), Objective-C
22-10-05 수요일
4주차

 

 

잠시 머리 식히기.

옵셔널에 대해서 다시 보자. 새로 업데이트된 Swift 5.7 의 기능!

  • 숏컷
// 옵셔널이 되면 nil 값이 될 수도 있고, 아닐 수도 있다.
var name: String?

if let myName = name {
	print("\\(myName)")
} else {
	print("nil입니다")
}

name = "dadahae"

if let name = name {
	print("\\(name)")
} else {
	print("nil입니다")
}

// Swift 5.7의 신기능!
if let name {
	print("\\(name)")
} else {
	print("nil입니다")
}

// guard let 도 가능!!
func sayHello(name: String?) {
    guard let _ = name else {
        return print("nil입니다")
    }
    
    print("Hello World!")
}

sayHello(name: "ned")
sayHello(name: nil)

 

 

 


 

 

✔️ Closure

swift document: closure

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.

 

📌 self-contained?

자급자족이 가능한! 그 자체에서 할 수 있는 기능을 가지고 있는.

 

  • 기능 블록
    • 코드안에서 전달(pass)되고 사용(use)된다.
  • 생긴건 함수와 다른데 하는 일은 비슷하다.
  • 모든 상수, 변수에 대한 참조(reference)를 capture하고 store 할 수 있다.

 

더보기

> 컴퓨터 공학에서 closure 용어

  • 함수, 클로저 표현식과 같은 독립적인 코드 블록
  • 블록 주변에 있는 하나 이상의 변수가 결합된 것

 

func functionA() -> () -> Int {  // return type: () -> Int
	var counter = 0

	func functionB() -> Int {  // 중첩된 함수(Nested Type)
		return counter + 10
	}

	return functionB
}

let myClosure = functionA()
let result = myClosure()
  • functionB는 counter 변수를 “잡고있다(captured)” 또는 “가두고 있다(closed over)”라고 말할 수 있다. 전통적인 컴퓨터 공학 용어 closure로 간주된다.
  • Swift 에서는 clousre, closure expression 용어가 혼용되기 시작했지만, 어쨋든 둘 다 지원함!

 

 

0️⃣ Three forms of closure

Global and nested functions are actually special cases of closures.
  • Global functions are closures that have a name and don’t capture any values.
  • Nested functions are closures that have a name and can capture values from their enclosing function.
  • Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.

 

1️⃣ Closure Expressions

Closure expressions are a way to write inline closures in a brief, focused syntax. Closure expressions provide several syntax optimizations for writing closures in a shortened form without loss of clarity or intent.

The closure expression examples below illustrate these optimizations by refining a single example of the sorted(by:) method over several iterations, each of which expresses the same functionality in a more succinct way.

 

📌 closure expressions… 언제 쓰이는데

Nested 함수는 큰 함수의 한 부분으로, 자체 코드 블럭을 이름짓거나 정의할 때 유용하다.

근데 그렇지 않은 경우도 있다! 선언과 이름없이 함수처럼 생긴 짧은 버전을 쓰는게 더 유용할 때가 있다. (하나 이상의 argument로 사용되는 함수나 메서드를 사용할 때…!)

→ 그때 Closure Expressions를 도입한다.

자자 정리하면, 함수안에 포함되는 함수 즉 중첩된 함수(Nested function)를 쓰는 경우가 있다. 근데 이 중첩된 함수를 이름과 선언없이 쓰는게 더 유용할 때가 있다! 그때 사용하는게 Closure Expression.

  • 구체적으로 어떤 상황에서 유용하게 쓰이나?
    • 함수를 하나 이상의 인수로 사용하는 함수 또는 메서드로 작업할 때 특히 그렇습니다. (직역..)

 

📌 그래서 closure expressions이 뭔데

closure expressions는 간결(brief)하고 집중(focused)된 문법으로 inline closure을 작성하는 방법!

그래서 원문 코드의 명확성과 의도를 잃지 않으면서 클로저를 단축해서 쓸 수 있도록 몇가지 구문 최적화를 제공한다. 그래그ㅐ르ㅐ 클로저를 짧게 쓰고 싶은데 너무 짧게 하면 의미를 잃을 수도 있으니까 그런 점까지 최적화해주는 방법이 closure expressions 문법에 있다는 거네~~ ㅇㅋㅇㅋ

그거를 설명하는 예시로 sorted(by:)를 들어보겠다. 아래쪽에 설명 드루갑니다.

 

The Sorted Method

Swift’s standard library provides a method called sorted(by:), which sorts an array of values of a known type, based on the output of a sorting closure that you provide.
let name = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var defaultNames = names.sorted(by: >) // by: <
print(defaultNames)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
let name = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var defaultNames = names.sorted() 
print(defaultNames)

// 정렬 기준을 만들어주는 함수 만들기
func backward(_ s1: String, _ s2: String) -> Bool {
	return s1 > s2
}

var reverseNames = names.sorted(by: backward)
print(reverseNames)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

 

 

// 배열 정렬 예제
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var defaultNames = names.sorted()
print(defaultNames)

// 정렬 기준을 만들어주는 함수 만들기
func backward(_ s1: String, _ s2: String) -> Bool {
    print("\\(s1)과 \\(s2)를 비교합니다")
    return s1 > s2
}

var reverseNames = names.sorted(by: backward)
print(reverseNames)
/*
 Alex과 Chris를 비교합니다
 Ewa과 Alex를 비교합니다
 Ewa과 Chris를 비교합니다
 Barry과 Alex를 비교합니다
 Barry과 Chris를 비교합니다
 Daniella과 Alex를 비교합니다
 Daniella과 Barry를 비교합니다
 Daniella과 Chris를 비교합니다
 Daniella과 Ewa를 비교합니다
 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
 */

컴퓨터는 2개씩 밖에 비교를 못하므로, 두개씩(s1, s2)를 꺼내와서 비교한다.

// 배열 정렬 예제
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var defaultNames = names.sorted()
print(defaultNames)

// 정렬 기준을 만들어주는 함수 만들기
func backward(_ s1: String, _ s2: String) -> Bool {
//    print("\\(s1)과 \\(s2)를 비교합니다")
    return s1 > s2
}

var reverseNames = names.sorted(by: backward)
print(reverseNames)

// 전역함수 backward와 똑같이 작동하는 클로저 표현식을 만들어
// sorted의 by: 매개변수로 인라인 클로저라는 구현법으로 바로 써서 보낸다
reverseNames = names.sorted(by: { (_ s1: String, _ s2: String) -> Bool in
    return s1 > s2
})
print(reverseNames)

// 한줄로도 가능
reverseNames = names.sorted(by: { (_ s1: String, _ s2: String) -> Bool in return s1 > s2 })
print(reverseNames)

// 더 짧게
// 배열이 String 문자열들로 채워진 걸 아니까
reverseNames = names.sorted(by: { (_ s1, _ s2) -> Bool in return s1 > s2 })
print(reverseNames)

// 더 짧게
reverseNames = names.sorted(by: { s1, s2 in return s1 > s2 })
print(reverseNames)

// 더 짧게
reverseNames = names.sorted(by: { s1, s2 in s1 > s2 })
print(reverseNames)

// 짧은 인자이름으로 대체하는 방법도 있음
reverseNames = names.sorted(by: { $0 > $1 })
print(reverseNames)

// 연산자 메서드
reverseNames = names.sorted(by: >)
print(reverseNames)

 

Closure Expression Syntax

{ (parameters) -> return type in 
	// statement
}
  • parameters
    • in-out 매개변수일 수 있다.
    • 기본값 사용x

 

2️⃣ Trailing Closures

If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. You write a trailing closure after the function call’s parentheses, even though the trailing closure is still an argument to the function. When you use the trailing closure syntax, you don’t write the argument label for the first closure as part of the function call. A function call can include multiple trailing closures; however, the first few examples below use a single trailing closure.

 

함수의 괄호가 끝나고 따라오는 클로저!
보통의 클로저처럼 인자 이름을 안붙이고 그냥 {} 안에서 적어도 된다는 뜻.

 

  •  언제쓰나요?
    • 클로저를 함수의 마지막 인수로 전달해야 하고
    • 클로저가 긴 경우
  • map()
    • Array의 각 요소들을 하나씩 꺼내서 처리하려고 한다.
    • 클로저로 → 인라인 클로저 / 함수
// Array의 map 메소드 예제
let digitNames = [
  0: "Zero", 
	1: "One", 
	2: "Two",   
	3: "Three", 
	4: "Four",
  5: "Five", 
	6: "Six", 
	7: "Seven", 
	8: "Eight", 
	9: "Nine"
]

let numbers: [Int] = [16, 58, 510]

// ["OneSix", "FiveEight", "FiveOneZero"] 배열 만들기
let strings: [String] = numbers.map { (number: Int) -> String in 
	// 매개변수로 가져온 number는 상수이기 때문에, 변수로 다시 만들어줘야 변경 가능하다.
	var number: Int = number
	var output: String = ""

	repeat {
		// number % 10 문구는 number를 10으로 나눈 나머지
		// 소수점 맨 아랫자리 수 한자리
		output = digitNames[number % 10]! + output

		// number는 Int이기 때문에, 10으로 나누면 소숫점 이하의 값은 내림처리 한다.
		number /= 10
	} while number > 0

	return "Hello \\(number)"
}

// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
  • shadow variable
    • 함수의 매개변수는 상수값이라 변경 불가능. 그래서 shadow variable을 사용하여 함수 내부에서 매개변수 값을 사용함!

 

느낌표 피하고 싶어요!

// output = digitNames[number % 10]! + output

// 1안
if let name = digitNames[number % 10] {
	output = name + output
} else {
	output = "?" + output
}

// 2안
let name = digitNames[number % 10] ?? "?"

 

 

3️⃣ Capturing Values

A closure can capture constants and variables from the surrounding context in which it’s defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
  • 클로저는 정의된 둘러싸인 context에서 상수와 변수를 캡처(capture)할 수 있습니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

 

좀 더 자세히 보기!

// 캡처값 예제
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal: Int = 0
    
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    
    return incrementer
}

let increFunc = makeIncrementer(forIncrement: 10)

print("\\(increFunc())") // 10
print("\\(increFunc())") // 10 + 10 = 20
print("\\(increFunc())") // 20 + 10 = 30

 

중첩된 함수 incrementer()만 보면 이상하게 보일 수 있다.

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

파라미터가 없고, runningTotal과 amount를 ‘참조’하고 있다. (복사아님)

Swift가 알아서 메모리를 관리한다.

 

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

incrementByTen 함수는 runningTotal = 0, amount = 10으로 캡쳐되어 작동한다.

그럼 여기서 10이 아닌 다른 숫자를 매개변수로 하는 새로운 함수가 있으면 서로 영향을 줄까? nono.

 

// 캡처값 예제
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal: Int = 0
    
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    
    return incrementer
}

// 생성된 반환되는 함수는 runningTotal = 0,amount = 10으로 캡쳐되어 작동
let increFuncTen = makeIncrementer(forIncrement: 10)

// 생성된 반환되는 함수는 runningTotal = 0,amount = 7로 캡쳐되어 작동
let increFuncSeven = makeIncrementer(forIncrement: 7)

print("\\(increFuncTen())") // 0 + 10 = 10
print("\\(increFuncSeven())") // 0 + 7 = 7

print("\\(increFuncTen())") // 10 + 10 = 20
print("\\(increFuncSeven())") // 7 + 7 = 14

print("\\(increFuncTen())") // 20 + 10 = 30
print("\\(increFuncSeven())") // 14 + 7 = 21

increFuncTen 과 increFuncSeven는 서로 영향을 주지 않는당. → 왜 → 참조타입이니까.

 

함수를 변수처럼 쓸 수 있어서 함수 내부에서 수행한 변수 값이 메모리에 남아있다. 이걸 capturing value라고 부르는 것 같다.

 

 

4️⃣ Closures Are Reference Types

In the example above, incrementBySeven and incrementByTen are constants, but the closures these constants refer to are still able to increment the runningTotal variables that they have captured. This is because functions and closures are reference types.

함수와 클로저가 ‘참조 타입’ 이기 때문에 let으로 선언한 incrementBySeven과 incrementByTen에서 캡처한 변수를 계속 증가시킬 수 있다.

 

오잉? 참조타입이라고?

그럼 한번 확인해보겠다.

// 캡처값 예제
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal: Int = 0
    
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    
    return incrementer
}

// 생성된 반환되는 함수는 runningTotal = 0,amount = 10으로 캡쳐되어 작동
let increFuncTen = makeIncrementer(forIncrement: 10)

// 생성된 반환되는 함수는 runningTotal = 0,amount = 7로 캡쳐되어 작동
let increFuncSeven = makeIncrementer(forIncrement: 7)

print("\\(increFuncTen())") // 0 + 10 = 10
print("\\(increFuncSeven())") // 0 + 7 = 7

print("\\(increFuncTen())") // 10 + 10 = 20
print("\\(increFuncSeven())") // 7 + 7 = 14

print("\\(increFuncTen())") // 20 + 10 = 30
print("\\(increFuncSeven())") // 14 + 7 = 21

// 두 함수의 관계는 참조 타입이라는 걸 알아보려 한다.
// 동일한 runningTotal 값을 공유하고 있다.
let alsoIncreFuncTen = increFuncTen

print("\\(increFuncTen())") // 30 + 10 = 40
print("\\(alsoIncreFuncTen())") // 0 + 10 = 10 ? -> 실제로는 50
print("\\(increFuncTen())") // 40 + 10 = 50 ? -> 실제로는 60

가장 아래의 alsoIncrementFuncTen은 inreFuncTen을 copy 한것 이아니라 shared 했다.

그러니까 서로 값을 바꾸면 영향을 준다! 확인완료

 

 

4️⃣ Escaping Closures

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.
// 이스케이프 클로저 (Escaping Closures) 예제
var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosures(completionHandler: @escaping () -> Void ) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosures(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 0
    func doSomething() {
        someFunctionWithEscapingClosures { () -> Void in
            print("Hello")
            self.x = 100
        }
        someFunctionWithNonescapingClosures { () -> Void in
            print("World")
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print("someFunctionWithNonescapingClosures - x : \\(instance.x)") // 당연히 100

if let completionHandler = completionHandlers.first {   // (1)
    completionHandler()
    print("someFunctionWithEscapingClosures - x : \\(instance.x)") // 200이 아니라 100
}

클로저는 한번 써버리면 다시 못쓰는, 이름 없는 메서드(머 혹은 함수)! 라고 볼 수 있다.

그럼 클로저를 다시 쓰고 싶으면 어떻게 해야할까?

  • 클로저를 저장(store)한다.

오잉? 어떻게?

위 코드에서 보면 completionHandler라는 이름으로 클로저가 들어왔고, 이를 completionHandlers 배열에다 넣어주고 있다.

헉 그럼 클로저가 저장(store) 된건가? → 맞다.

 

그래서 someFunctionWithEscapingClosures 함수가 수행되면,

{ 
print(”Hello”) 
self.x = 100
}

위의 구문이 수행될 것 같지만…. 수행되지 않고 저 구문 자체가 () -> Void 타입의 item으로 가지는 completionHandlers 배열에 저장된다.

수행되지 않았음을 알 수 있는 이유는 (1)번 코드를 통해 알 수 있다. instance.x 값은 200이 아니라 100이다.

 

그럼 저장한 클로저를 수행하려면 어케 해용 ㅠ

현재 completionHandlers 에 있으니까 꺼내서 쓰면 된다. 일반 배열에서 요소 찾는 것처럼 접근하면 된다.

와~ 쉽다 쉬워

 

HTTP 통신할 때 비동기로 작업 처리한다고 @escaping 썼는데, 이제야 이론적으로 접근한다. 다시 봐야지..

 

  • escaping
    • 클로저는 참조타입으로 차곡차곡 쌓일 때 원본이 변경되면 내용이 다 날라갈 수 있다. 그래서 escaping, self로 강하게 참조하는 것이다… 암트 ㄴ그렇다.
  • self
    • escaping을 하려면 self를 꼭 써줘야 한다? ㅇㅇ

 

mutating

struct는 값타입이므로 메소드에서 프로퍼티 값 변경을 막고 있다. 메소드에 의해서 프로퍼티가 바꾸려면 해당 메소드 앞에 mutating 키워드를 붙여줘야 한다.

  • struct에서 프로퍼티를 건드리는 함수를 작성하기 위해선 mutating을 써야 한다.
// mutating을 알아보자

class SomeClass {
    var name: String = ""
    
    func changeName(newName: String) {
        self.name = newName
    }
}

var someThingByClass = SomeClass()
someThingByClass.changeName(newName: "ned")
print("someThingByClass.name = \\(someThingByClass.name)")

struct SomeStruct {
    var name: String = ""
    
    // 이 메소드에 의해서 프로퍼티가 바뀔 수 있음을 알리기 위해 mutating을 앞에 붙인다
    mutating func changeName(newName: String) {
        self.name = newName
    }
}

var someThingByStruct = SomeStruct()
someThingByStruct.changeName(newName: "뽀뽀로")
print("someThingByStruct.name = \\(someThingByStruct.name)")

 

 

5️⃣ Autoclosures

강해지고 싶은 자. 오토 클로저를 읽어라!

(쭈굴)

 

 

 


 

✔️ Objective-C

Sneak Preview

Intro.

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9791162243282&orderClick=LEa&Kc=

 

유닉스의 탄생 - 교보문고

세상을 바꾼 운영체제를 만든 천재들의 숨은 이야기 | 2019년은 유닉스가 태어난 지 50년이 되는 해였습니다. 이를 맞아 저자인 브라이언 커니핸은 벨 연구소에서 유닉스의 탄생과 발전, 번성과

www.kyobobook.co.kr

 

C언어

  • 1970년대 초, C언어 창시
    • AT&T Bell 연구소의 데니스 리치(Dennis Ritchie)가 만듦
    • AT&T Bell 연구소에서는 전화뿐만 아니라 컴퓨터 산업에 대한 연구도 많이 했다.
  • BUT, 이 언어는 연구소 밖에서 사용 가능하게 된 1970년대 후반에서야 인기를 얻고 널리 사용되었다.
  • UNIX의 인기에 힘입어 빠르게 대중에 전파되었다.
    • AT&T Bell 연구소에서 UNIX를 만들었고, C언어 기반으로 만들었기에 자연스럽게 C언어도 인기가 많아짐

 

Objective-C

  • 1980년대 초, Objective-C 설계
    • 브래드 콕스(Brad J. Cox)
  • SmallTalk-80 언어 기반
  • C언어 위에 있는 계층적(layered) 구조
  • C언어를 확장하여 ‘객체’를 생성하고 다룰 수 있는 새로운 언어

LINUX는 UNIX를 기반으로 만듦. 근데 무료라는게 다른 점임

  • NeXT Software는 1988년에 Objectiv-C의 라이선스를 받아 NeXTSTEP 운영체제의 개발환경과 라이브러리를 개발하였다.
  • 1992년, 자유 소프트웨어재단(Free Software Foundation)의 GNU 개발 환경에 Objective-C 개발 환경이 추가되었다.
    • FSF의 모든 제품의 저작권은 재단이 소유하며 GNU General License(GPL)로 배포된다.
  • 이후 NeXT는 애플에 인수되고 Objective-D는 Mac OS X와 iPhone OS 앱 개발의 기본 언어가 되었다.
  • 2007년 Objective-C 2.0이 발표되었다. → 이게 처음이자 마지막 업데이트가 되었다.

 

한번 실습 해보자구요

  • NSLog : 프린트 문과 동일하게 사용할 수 있다.
#import <Foundation/Foundation.h>
// import는 C언어의 include와 달리 중복으로 가져오는 걸 방지해준다.

// C 언어 계열은 main 함수를 통해 처음 실행될 코드 위치를 찾아간다.
int main (int argc, const char * argv[]) {

    // NSAutoreleasePool 부터 [pool drain] 까지는 메모리 관리를 해주는 자동 영역이며
    // 그 안에 우리의 코드가 들어가게 된다.
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    // NSLog는 print문과 같은 식의 출력문
    // Objective-C의 문자열은 @"...." 로 표현한다.
    NSLog(@"Hello World");
    
    [pool drain];
    return 0;
}

 

 

ㅋㅋ 웃긴게 티스토리에 obj 코드블럭을 지원해주지 않는다. 

 

 


 

수업 들으면서 이스케이핑 클로저에서 뇌의 작동이 멈췄다. 엄..음? 이러고 들었다.

 

Recap 스터디 하면서 다시 회생했지만 아직 어려운 개념이다...

 

 

 

 

  Comments,     Trackbacks