[Swift 4.0] Properties

IOS/Swift 2017. 4. 1. 23:16

Introduction
: property는 class, structure, enumeration의 속성을 나타내는 값이다. Stored property는 변수와 상수를 저장하는 용도로 쓰이고, computed property는 저장하는 용도 보다는 값을 계산하는 용도로 쓰인다. 그리고 Stored property는 class, structure에만 쓰일수 있지만, Computed property는 class, structure, enumeration에서 모두 쓰일 수 있다.


Stored Properties
: instance를 구성하는 값을 저장하는 용도로 사용하는 property이다. 선언과 동시에 값을 할당할 수 있으며 let으로 선언된 상수라 하더라도 initialization 과정에서 값을 변경할 수 있다.
class, structure만 사용이 가능하다.

struct FixedLengthRange {

    var firstValue: Int

    let length: Int

}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)

// the range represents integer values 0, 1, and 2

rangeOfThreeItems.firstValue = 6

// the range now represents integer values 6, 7, and 8

Stored Properties of Constant Structure Instances
: structure를 let으로 선언하면 var property라 하더라도 property 값을 변경하지 못한다.

let
rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// compile 에러 발생!!

이는 structure가 value type 이기 때문이다. value type이 constant로 선언되면 property들 또한 constant 성질을 가진다.

Lazy Stored Properties
: lazy stored property는 처음 사용되기 전까지 해당 property가 초기값을 가지지 않는 성질을 가지고 있다.
lazy stored property는 반드시 var로 선언되어야 한다. let으로 선언 한다는 의미는 initialization이 끝나기 전에 초기값을 가지고 있어야 한다는 뜻이다. 그러나 lazy stored property는 initialization이 끝난 이후에 값을 가지기 때문이다.

lazy property는 외부 요인에 의해서 initialization 과정이 끝난 이후에 해당 값을 알 수 있을경우 사용하면 유용하다. 또한, lazy property가 initialization 과정에서 하기에는 부담스러운 작업을 가지고 있을경우, 즉, 필요할 때만 해당 작업을 하도록 하기 위해서 사용하는 데에도 유용하다.

class
DataImporter {
    /*
     DataImporter is a class to import data from an external file.
     The class is assumed to take a nontrivial amount of time to
initialize.
     */

    var filename = "data.txt"

    // the DataImporter class would provide data importing functionality here

}


class DataManager {

    lazy var importer = DataImporter()

    var data = [String]()

    // the DataManager class would provide data management functionality here

}


let manager = DataManager()

manager.data.append("Some data")

manager.data.append("Some more data")

// 아직 DataImporter instance는 만들어지지 않았다.

위에 예제에서 data.txt 파일에 있는 데이터를 DataManager의 data 배열에 넣지 않을 거라면 굳이 DataImporter를 생성할 필요가 없다. DataImporter의 initialization 작업이 굉장히 무거울 것이기 때문에 lazy property로 생성하여 instance 생성을 꼭 필요할때만 하도록 한것이다.

print
(manager.importer.filename)
// Prints "data.txt"
// 이때 DataImporter instance가 생성된다.

* lazy property가 멀티 스레드에서 접근이 가능하고, 해당 property가 아직 초기화 되지 않았다면, property가 한번만 초기화 되리라고는 보장하지 못한다.


Computed Properties
: 실제로 값을 저장하지 않고 getter, optional setter를 이용해서 stored property를 계산해서 원하는 값을 get, set하는 용도로 사용한다.

struct Point {

    var x = 0.0, y = 0.0

}

struct Size {

    var width = 0.0, height = 0.0

}

struct Rect {

    var origin = Point()

    var size = Size()

    var center: Point {

        get {

            let centerX = origin.x + (size.width / 2)

            let centerY = origin.y + (size.height / 2)

            return Point(x: centerX, y: centerY)

        }

        set(newCenter) {

            origin.x = newCenter.x - (size.width / 2)

            origin.y = newCenter.y - (size.height / 2)

        }

    }

}

var square = Rect(origin: Point(x: 0.0, y: 0.0),

                  size: Size(width: 10.0, height: 10.0))

let initialSquareCenter = square.center

square.center = Point(x: 15.0, y: 15.0)

print("square.origin is now at (\(square.origin.x), \(square.origin.y))")

// Prints "square.origin is now at (10.0, 10.0)"

Rect의 center는 computed property이다. 자신의 stored property인 origin과 size를 이용해서 원하는 값을 추출하거나 세팅하고 있다. 

Shorthand Setter Declaration
: computed property의 setter로 전달되는 파라미터의 이름을 명시하지 않으면, Swift가 해당 파라미터 이름을 "newValue"로 지정한다. 

struct AlternativeRect {

    var origin = Point()

    var size = Size()

    var center: Point {

        get {

            let centerX = origin.x + (size.width / 2)

            let centerY = origin.y + (size.height / 2)

            return Point(x: centerX, y: centerY)

        }

        set {

            origin.x = newValue.x - (size.width / 2)

            origin.y = newValue.y - (size.height / 2)

        }

    }

}

Read-Only Computed Properties
: computed property에 setter가 없으면 당연히 read-only property가 된다. 
그리고 computed property는 애초에 값이 하나로 고정된 것이 아니기 때문에 var로 선언해야 한다.(read-only property 포함)

struct Cuboid {

    var width = 0.0, height = 0.0, depth = 0.0

    var volume: Double {

        return width * height * depth

    }

}

let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)

print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")

// Prints "the volume of fourByFiveByTwo is 40.0"


Property Observers
: property observer는 property의 값이 변경되면 자동으로 invoke 되어 호출된다. 심지어 기존값과 값은값이 set 되더라도 invoke 된다.
property observer는 자신이 정의한 모든 stored property에 적용이 가능하다.(lazy stored property는 적용 불가능 하다.) 또한 상속된 stored, computed property를 overriding 해서 observer를 적용할 수도 있다. 다만, 자신이 만든 computed property 에는 observer를 만들 필요가 없다. setter에서 observing을 하면 되기 때문이다. 
willSet과 didSet 메서드를 통해 observing을 할 수 있다.

willSet은 값이 저장되기 직전에 invoke되고 didSet은 값이 저장된 직후에 invoke 된다. 둘다 구현해도 되고 하나만 구현해도 된다.
willSet의 default parameter 이름은 "newValue"이고 didSet의 default parameter 이름은 "oldValue"이다. 직접 parameter 이름을 지정할 수도 있다.

super class property의 willSet, didSet observer 메서드는 child class의 initialization 과정에서 값을 변경하면 invoke 되지만, 자신의 class initialization 과정에서 property가 초기화 될때는 invoke 되지 않는다.

class StepCounter {

    var totalSteps: Int = 0 {

        willSet(newTotalSteps) {

            print("About to set totalSteps to \(newTotalSteps)")

        }

        didSet {

            if totalSteps > oldValue  {

                print("Added \(totalSteps - oldValue) steps")

            }

        }

    }

}

let stepCounter = StepCounter()

stepCounter.totalSteps = 200

// About to set totalSteps to 200

// Added 200 steps

stepCounter.totalSteps = 360

// About to set totalSteps to 360

// Added 160 steps

stepCounter.totalSteps = 896

// About to set totalSteps to 896
// Added 536 steps


*observer를 가지고 있는 property가 in-out parameter로 넘겨지면, observer method는 무조건 불린다. in-out parameter가 동작하는 copy-in copy-out 모델에 의해 메서드가 종료될 때 property에 값이 다시 쓰여지기 때문이다. 


Global and Local Variables
: Global, local 변수도 computing, ,observing 변수로 정의하는 것이 가능하다. 

* Global 변수 및 상수는 lazily stored property처럼 생성되는 시점이 delay 된다. 다만, 앞에 "lazy" 키워드를 붙이지 않아도 된다.


Type Properties
: Type Property는 클래스 변수라고 생각하면 된다. instance의 갯수와는 무관하게 클래스내에 하나의 set만 존재하는 변수이다. let으로 선언한 Type Property는 C의 static constant와 비슷하고 var로 선언한 Type Property는 C의 static variable과 비슷하다.

stored instance property와 다르게 stored type property는 default 값을 가지고 있어야 한다. 
type property는 instance init 시점에 생성되는것이 아니기 때문에 default 값을 넣어줄만한 타이밍이 딱히 존재하지 않기 때문이다.
그리고 stored type property는 lazy initialized 되더라도 멀티 스레드에서 동시 접근해도 한번만 init 되는것을 보장한다.

Type Property Syntax
: C와 Objective-C 에서는 static 변수, 상수를 global static 변수, 상수로 선언하여 global scope를 가졌는데, Swift 에서는 Type Property로 선언하여 해당 scope를 클래스 내부로 한정시킨다.

선언 문법은 Type property 앞에 "static" 키워드를 붙이면 된다. 다만, computed type property의 경우 자식클래스에서 override가 가능하도록 "class" 키워드를 대신 붙여도 된다.

struct
SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}

enum SomeEnumeration {

    static var storedTypeProperty = "Some value."

    static var computedTypeProperty: Int {

        return 6

    }

}

class SomeClass {

    static var storedTypeProperty = "Some value."

    static var computedTypeProperty: Int {

        return 27

    }

    class var overrideableComputedTypeProperty: Int {

        return 107

    }

}

Querying and Setting Type Properties
: type property에 접근할때는 앞에 Type이름 뒤에 dot을 붙여서 접근한다.

print(SomeStructure.storedTypeProperty)

// Prints "Some value."

SomeStructure.storedTypeProperty = "Another value."

print(SomeStructure.storedTypeProperty)

// Prints "Another value."

print(SomeEnumeration.computedTypeProperty)

// Prints "6"

print(SomeClass.computedTypeProperty)

// Prints "27"




'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Objective-C 와는 달리 Swift는 헤더파일과 구현파일을 나누지 않는다. 구현파일에다가 코드를 작성하면 Swift는 각 메서드, 멤버변수의 접근자에 따라 자동으로 헤더파일을 만들어 준다.

그리고 Swift의 Class와 Structure는 다른 언어에 비해 object로서의 역할 보다 functionality의 역할에 더 치우쳐져 있다.


Comparing Classed and Structures
1) 공통
 - property를 정의할 수 있다.
 - method를 정의할 수 있다.
 - subscript를 정의할 수 있다.
 - initializer를 구현할 수 있다.
 - extension을 구현할 수 있다.
 - protocol을 confirm할 수 있다.

2) 차이점
 - Class만 상속이 가능하다.
 - Type Casting이 가능하다.
 - Deinitializer를 구현할 수 있다.
 - Reference Counting이 가능하다. (Structure는 어떤 방식으로든 다른곳에 전달될 때, reference count를 증가시키는 것이 아니라 복사된다.)

Definition Syntax 

struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

Class and Structure Instances
: class와 structure의 instance를 생성하는 문법이다.

let someResolution = Resolution()
let someVideoMode = VideoMode()

Accessing Properties
: property에 access할 때는 dot syntax를 사용한다.

print("The width of someResolution is \(someResolution.width)")
//Prints "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"


Structures and Enumerations Are Value Types
: value type이 의미하는 것은 variable이나 constant에 할당되거나 다른 function으로 전달될 때 값이 복사되어 진다는 것이다.

모든 Structure, Enumeration은 value typed이다.

integers, floating-point numbers, Booleans, strings, arrays, dictionaries 모두 structure, 즉, value type이다.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

cinema.width = 2048

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

위의 예처럼 값이 복사되는 것을 알수 있다. 이는 Enumeration 에도 똑같이 적용된다.

enum CompassPoint {
    case north, south, east, west
}

var currentDirection = CompassPoint.west
let rememberDirection = currentDirection

currentDirection = .east

if rememberedDirection == .west {
    print("The remembered direction is still .west")
}
// Prints "The remembered direction is still .west"


Classes Are Reference Types
: reference type은 value type과 달리 copy 되는것이 아니라 같은 instance를 참조한다.

let tenEighty = VideoMode()

tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty

alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

Identity Operators
- Identical to(===), Not Identical to(!==)
- 두 contants 또는 variables가 같은 Class의 instance를 참조하고 있는지 판별한다.

* "=="는 두 instance가 의미상으로 같은지 체크한다. 이것은 클래스 디자인에 따라 equal 되는 조건이 틀려진다.


Choose Between Classes and Structures
: Class와 Structure 중에 어떤것을 써야할지 고민될 때는 애플의 가이드 라인을 참고하면 된다.

Structure를 사용하기 적합한 경우
 - Structure의 주요 목적이 간단한 데이터들을 encapsulation 하는 것일때.
 - instance가 다른곳으로 전달될 때 encapsulated value들이 reference 되는것 보다 copy 되는것이 합리적일때
 - 모든 property가 value type이고 reference 되는것 보다 copy 되기를 원할 때
 - 상속을 사용하지 않을 경우.


Assignment and Copy Behavior for Strings, Arrays. and Dictionaries
: value type은 다른곳으로 전달될 때 무조건 복사되는 것이 아니라, 필요할 때만 복사된다. 예를들어 value type이 다른곳으로 전달된 이후에 상태가 하나도 변경되지 않았다면 굳이 복사해서 메모리에 2개를 가지고 있을 필요가 없다. 이러한 경우 복사를 하지 않고 reference 하고 있다가 상태가 변경될때 복사해서 상태를 변경한다.






'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Enumeration은 관련있는 value들의 group으로 하나의 타입을 정의하는 것을 말한다. Enumeration을 통해 type-safe way로 코드작성이 가능하다.

Swift의 Enumeration은 C의 Enumeration 보다는 조금 더 flexible하다. 모든 case에 value를 할당할 필요가 없다. value의 타입은 string, character, 모든 integer 또는 floating-point 종류의 타입이 가능하다.


Enumeration Syntax

enum SomeEnumeration {
    // enumeration definition goes here
}

예제 코드이다.

enum CompassPoint {
    case north
    case south
    case east
    case west
}

다른 언어의 Enumeration과 달리 각각의 case는 default integer 값을 가지지 않는다.

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

multiple value들은 single line으로 표현이 가능하다.

var directionToHead = CompassPoint.west

directionToHead = .east

directionToHead는 CompassPoint 타입으로 추론되었기 때문에 추후 .east 같이 앞에 타입을 생략해서 값 할당이 가능하다.


Matching Enumeration Values with a Switch Statement
: Enum을 Switch 문에서 쓰는 법을 알아보도록 하겠다.

directionToHead = .south

switch directionToHead {
    case .north:
        print("Lots of planets have a north")
    case .south:
        print("Watch out for penguins")
    case .east:
        print("Where the sunrises")
    case .west:
        print("Where the skies are blue")
}
// Prints "Watch out for penguins"


Associated Values
: Swift의 enumeration은 각각의 case에 관련된 value들을 가질 수 있게 해준다. 예제를 보자.

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
    case .upc(let numberSystem, let manufacturer, let product, let check):
        print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
    case .qrCode(let productCode):
        print("QR code: \(productCode).")
}

// Prints "QR code: ABCDEFGHIJKLMNOP."

associated value 들이 모두 var거나 let이면 앞에 한번만 명시하면 된다.

switch productBarcode {
    case let .upc(numberSystem, manufacturer, product, check):
        print("UPC:\(numberSystem), \(manufacturer), \(product), \(check).")
    case let .qrCode(productCode):
        print("QR code: \(productCode).")
}

// Prints "QR code: ABCDEFGHIJKLMNOP."


Raw Values
: enumeration의 case는 각각 다른 타입의 associate value 들을 가질 수 있었다. 뿐만 아니라, 같은 타입의 Raw value들 또한 정의할 수 있다. 예제를 보자.

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Raw Value는 string, characters, integer or floating-point 종류의 타입으로 정의할 수 있다. 그리고 Raw Value는 enumeration 안에서 unique한 값이어야 한다. 

implicitly Assigned Raw Values
: raw value 타입이 Integer 또는 String 이라면, Swift는 자동으로 raw value를 대입해준다. integer일 경우 바로 앞의 case의 값에 1을 더한값이 자신의 raw value가 되고, first case의 raw value가 명시되어 있지 않으면 0이 된다. 

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

위의 경우 Planet.mercury는 1이고 Planet,venus는 2이다.

enum compassPoint: String {
    case north, south, east, west
}

위의 경우 raw value 타입이 String이고 아무값도 지정하지 않았기 때문에 raw value는 case 이름을 따라간다. CompassPoint.south의 raw value는 "south" 이다. 

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Initializing from a Raw Value
: enumeration이 raw value 타입과 함께 정의되었다면 initializer의 param으로 rawValue값을 받아서 해당 enumeration case 또는 nil을 return 한다. 

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus

위 예제에서 rawValue 파라미터에 대한 case가 없을때를 nil을 return 한다. 

let positionToFind = 11

if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
        case .earth:
            print("Mostly harmless")
        default:
            print("Not a safe place for humans")
        }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"


Recursive Enumerations
: case의 associated value로 하나 또는 그 이상의 enumeration case를 포함하는 경우를 뜻한다. 이 경우 "indirect"를 명시해야 한다. 

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)    
}

모든 case에 대한 indirect을 허용하려면 enum 앞에 "indirect"을 명시하면 된다.

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)    
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_expression: ArithmeticExpression) -> Int {
    switch expression {
        case let .number(value)
            return value
        case let.addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// Prints 18


*Enumeration은 inherits를 할 수 없다.(structure도 마찬가지로 불가능.) 하지만 protocol을 confirm하는 형식으로 inherits 비슷하게 구현이 가능하다.
아래 예제를 보자.


protocol TableSection {

    static var rows: [Self] { get }

    

    var title: String { get }

    

    var mandatoryField: Bool { get }

}


extension TableSection {

    var mandatoryTitle: String {

        if mandatoryField {

            return "\(title)*"

        } else {

            return title

        }

    }

}


enum RegisterTableSection: Int, TableSection {

    case Username

    case Birthdate

    case Password

    case RepeatPassword

    

    static var rows: [RegisterTableSection] {

        return [.Username, .Password, .RepeatPassword]

    }

    

    var title: String {

        switch self {

        case .Username:

            return "Username"

        case .Birthdate:

            return "Date of birth"

        case .Password:

            return "Password"

        case .RepeatPassword:

            return "Repeat password"

        }

    }

    

    var mandatoryField: Bool {

        switch self {

        case .Username:

            return true

        case .Birthdate:

            return false

        case .Password:

            return true

        case .RepeatPassword:

            return true

        }

    }

}


class ViewController: UITableViewController {

    

    override func viewDidLoad() {

        super.viewDidLoad()

    }

    

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

        return 1

    }

    

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return RegisterTableSection.rows.count

    }

    

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        guard let row = RegisterTableSection(rawValue: indexPath.row) else {

            // This should never happen

            return UITableViewCell()

        }

        

        let cell = UITableViewCell()

        cell.textLabel?.text = row.mandatoryTitle

        return cell

        

    }

}


위 코드를 실행하면 tableView에 3개의 cell이 나타나게 되고, 각 cell에는 순서대로, "Username*", "Date of birth", "password*"이 나타나게 된다.


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Closures

IOS/Swift 2017. 4. 1. 23:15

Introduction
: Swift의 Closure는 C와 Obj-C의 Block, 다른 language의 lambda와 비슷하다.


Closure Expressions
: Closure는 의미의 명확성을 해치지 않는 범위 내에서 축약되어 표현될 수 있다.
지금부터 예제 메서드인 sorted(by:)가 변화하는 과정을 살펴 보면서 어떤식으로 축약될 수 있는지 살펴 보겠다.

The Sorted Method
: Swift의 standard library는 sorted(by:)라는 메서드를 제공한다. 이 메서드는 메서드로 넘어오는Closure를 기반으로 array의 값들을 정렬한다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

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

알파벳 순서대로 내림차순 정렬된다.

Closure Expression Syntax
: Closure는 다음과 같은 형식을 지닌다.

{ ("parameters") -> "return type" in 
    statements
}

parameters는 in-out parameter가 될수 있고, variadic parameter로 지정해서 사용할 수도 있다. 또한 Tuple을 parameter와 return type으로 지정할 수 있다.

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
 })

Inferring Type From Context
: 위의 parameter로 넘겨진 closure의 경우 type을 명시하지 않더라도 추론할 수 있다. 왜냐하면 names array가 String type의 value를 다루기 때문에 closure의 type은 (String, String) -> Bool이 되어야 한다.
그래서 다음과 같이 생략해서 표현할 수 있다.

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

이처럼 Closure는 다른 function이나 method로 넘겨질 때 항상 type 추론될 수 있기 때문에 위의 예처럼 축약형 표현이 가능하다.

Implicit Returns from Single-Expression Closures
: 위 closure는 'return' 키워드 또한 생략할 수 있다.

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

위 예제의 경우 Closure가 반드시 Bool 값을 return 해야 한다. 그리고 Closure의 body는 single expression( s1> s2)만 포함하고 있기 때문에 'return' 키워드를 생략할 수 있는것이다.

Shorthand Argument Names
: Swift의 Closure는 각 argument에 대해서 shorthand 버젼을 제공한다. argument 순서대로 $0, $1, $2 ... 형식으로 제공된다.
shorthand argument를 사용하면 argument의 정의를 생략할 수 있다.

reversedNames = names.sorted(by: { $0 > $1 } )

Operator Methods
: Swift의 String type은 '>' operator의 구현을 operator method로 제공하고 있다. 해당 operator method는 두개의 String type을 argument로 받고, Bool을 return 한다. 이는 sorted(by:)의 argument type과 동일하므로 다음과 같이 축약해서 사용가능하다.

reversedNames = name.sorted(by: >)

operator method는 'Advanced Operators' 챕터에서 배우도록 하겠다.


Trailing Closures
: 만약 어떤 function의 마지막 argument가 closure이고 그 closure의 내용이 상당히 길다면 Trailing Closure형태로 표현할 수 있다. 
예제를 보자.

func someFunctionThatTakesAClosure(closure: () -> void) 
{
    
}

someFunctionThatTakesAClosure(closure: {
    
})
// 일반적인 function 호출 방법이다.

someFunctionThatTakesAClosure() {

}
// trailing closure 표현법을 사용한 function 호출 방법이다.

위에서 예로 든 sorted(by:)를 trailing closure 표현법으로 호출해 보겠다.

reversedNames = names.sorted() { $0 > $1 }

() 도 생략할 수 있다.

reversedNames = names.sorted { $0 > $1 }


Capturing Values
: closure는 closure안에 있는 상수와 변수를 capture 한다. 그래야만 capture한 상수, 변수의 scope이 끝나서 없어지더라도 미리 capture 했기 때문에 closure 안에서는 계속 사용할 수 있는 것이다. 예제를 보자.

func makeIncrementer(forIncrement amount: Int) -> () -> Int 
{
    var runningTotal = 0

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

    return incrementer
}

위에 함수에서 incrementer closure(또는 Nested Function)는 makeIncrementer 에서 선언 및 argument로 넘어온 runningTotal, amount를 capture 한다. 그래서 makeIncrementer call이 끝나고 runningTotal, amount의 scope를 벗어나더라도 incrementer closure 안에서는 해당 변수 및 상수들을 계속 사용할 수 있는것이다.

let incrementByTen = makeIncrementer(forIncrement: 10)

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

let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()
// returns a value of 7

incrementByTen()
// returns a value of 40

* 만약 closure를 class instance의 property로 할당하고, closure가 자신이 속한 class instance 또는 instance의 멤버 변수를 capture함으로써 자신이 속한 class instance를 캡쳐하고 있다면, strong reference cycle이 만들어 지게 된다.
이런 문제를 회피하는 방법은 'Automatic Reference Counting' 챕터에서 자세히 배울 것이다.


Closure Are Reference Types
: 위 예에서 incrementBySeven, incrementByTen은 let 으로 선언되어 constant 이지만, 이 constant들이 참조하고 있는 closure는 reference type 이기 때문에 캡쳐 하고 있는 변수인 runningTotal의 값을 증가시킬 수 있다. 이것은 function과 closure는 referenceType 이기 때문이다.


Escaping Closures
: 우리가 정의하는 많은 함수들이 비동기 operation을 수행한다. 그리고 block을 매개변수로 받아 비동기 operation이 완료되면 해당 block을 호출함으로써 비동기 operation이 완료되었다는 것을 호출자에게 알려준다. 이때 함수는 operation이 끝나기전에 반환 되지만, block은 operation이 끝날때까지 반환되면 안되기 때문에 Swift에서는 @escaping 라는 syntax를 정의했다. 즉, 함수의 범위를 벗어나서 함수가 반환 되더라도 나중에 호출될 수 있는 block 이라는 것을 가리키는 것이다.

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping() -> Void) {
    completionHandlers.append(completionHandler)
}

위 코드에서 @escaping 을 빼게되면 compile error가 발생한다. 

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

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()

instance.doSomething()
print(instance.x)
//Prints "200"

completionHandlers.first?()
print(instance.x)
//Prints "100"

위 코드에서 @escaping 선언되어 있는 block 에서는 self를 명시적으로 capturing 해야 한다.(개인적인 생각은 @escaping 되면서 self를 자동으로 인식할 수 있는 범위를 벗어나서 명시해줘야 하는것 같다.) self를 명시하지 않으면 compile error가 발생한다.


AutoClosures
: AutoClosure는 함수에 인자로 전달되는 표현식을 wrapping 하기 위해 자동 생성되는 Closure이다. 아무런 인자도 가지지 않고, 호출될때 내부에서 wrapping된 표현식의 값을 반환한다. 이 구문은 명시적인 Closure 대신에 일반 표현식으로 작성해서 함수의 매개변수 주변의 중괄호를 생략할 때 편리하다. 
이것을 이용해서 closure가 만들어질때 바로 실행하는 것이 아니라 closure가 호출될 때 실행되기 때문에 delaying evaluation이 가능하다.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at : 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print("customersInLine.count")
// Prints "4"

위 코드와 같이 autoClosure가 생성될 때 로직이 실행되는 것이 아니라, autoClosure가 호출될 때 실행된다. 

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: { customersInLine.remove(at: 0) })
// Prints "Now serving Alex!"

함수의 매개변수로 전달될 때도 실제 autoClosure가 호출될 때 로직이 실행된다.

@autoclosure syntax를 사용해서 parameter로 String Argument 인것 처럼 전달 하지만 자동으로 AutoClosure를 만들어서 위 코드와 동일한 동작을 하도록 만들 수 있다.
아래 코드를 보자.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoClosure () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

* @autuclosure syntax를 자주 남용하면 읽기 어려운 코드를 유발할 수 있다. 사용하더라도 네이밍을 적절히 하여 해당 argument가 autoclosure 인지 명확히 표시해야 한다. 

만약, autoClosure가 escape되야 한다면, @autoclosure와 @escaping을 같이 명시해야 한다. 

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
[Swift 4.0] Collection Types  (0) 2017.02.03
Posted by 홍성곤
,

[Swift 4.0] Functions

IOS/Swift 2017. 4. 1. 23:15

Defining and Calling Functions
: Swift 에서는 Function도 타입을 가질 수 있다. 타입을 구별하는 요소는 Function의 parameter type과 return type이다. 

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
        
    return greeting
}

print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"


Function Parameters and Return Values
: Swift Function의 parameter와 return value의 형태는 매우 다양하다. 이제 하나하나씩 알아볼 것이다.

Functions Without Parameters

return 값만 있고 parameter는 존재하지 않는 함수다.

func sayHellosWorld() -> String {
    reutrn "hello, world"
}

print(sayHelloWorld())

Functions With Multiple Parameters

여러개의 parameter와 return 값이 있는 function 이다.

func greet(person: String, alreadyGreeted: Bool) -> String {
    if alresyGreeted {
        return greetAgain(person: person)
    } else {
        return greet(person: person)
    }
}

print(greet(person: "Tim", alreadyGreeted: true))

이 함수는 우리가 위에서 정의한 greet(person:) 함수와 이름이 똑같다. 하지만 parameter의 타입이 다르기 때문에 서로 다른 타입으로 구별된다.

Functions Without Return Values

return Value를 갖지 않는 Function 이다.

func greet(person: String) {
    print("Hello, \(person)!")
}

greet(person: "Dave")

엄밀히 말하면, 위 함수는 값을 전혀 return하지 않는것이 아니고, Void type을 return한다. 이것은 empty tuple '()'로 표현될 수 있다.

Functions with Multiple Return Values
: return type으로 tuple을 지정할 수 있다.

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]

    for value in array[1 ..< array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }  
    
    return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])

print("min is \(bounds.min) and max is \(bounds.max)")
// Prints "min is -6 and max is 109"

Optional Tuple Return Types
: return type이 tuple인데 nil이 될 가능성이 있는경우 뒤에 '?'을 붙여서 optional type으로 만들어야 한다. 
단, (Int, Int)? 와 (Int?, Int?)는 구분해야 한다. 전자는 튜플 자체가 optional type이고 후자는 튜플안에 있는 각각의 타입이 optional type인 것이다.

func minMax(array: [Int]) -> (min: Int, max:Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }

    return (currentMin, currentMax)
}

if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
    print("min is \(bounds.min) and max is \(bounds.max)")
}
// Prints "mins is -6 and max is 109"


Function Argument Labels and Parameter Names
: Function의 구성요소로 Argument Label과 Parameter Name이 있다.
Argument Label은 function을 call 할때 각각의 argument 앞에 붙여서 사용하고 parameter name은 함수 내부에서 넘어온 argument를 사용할 때 사용한다.
기본적으로 argument Label을 따로 지정해 주지 않으면 parameter name을 argument Label로 사용한다.

func someFunction(firstParameterName: Int, secondParameterName: Int)
{
    // 따로 argument Label을 지정해주지 않았으므로
    // firstParameterName, secondParameterName은 parameter name이 됨과 동시에 argument Label이 된다.
}

specifying Argument Labels
: Argument Label을 지정하는 경우를 살펴보자.

func someFunction(argumentLabel parameterName: Int) 
{
    // argument label이 지정된 경우이다. 함수 내부에서는 parameter name을 써야하고
    // 함수를 호출할 때에는 argument label을 사용해야 한다.
}

여러개의 parameter가 존재할 경우 argumentLabel을 일부 생략할 수도 있다.

func greet(person: String, from hometown: String) -> String
{
    return "Hello \(person)! Glad you could visit from \(hometown)."
}

print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino"

Omitting Argument Labels
: argument label을 사용하고 싶지 않다면 '_'을 추가해서 생략할 수 있다.

func someFunction(_ firstParameterName: Int, secondParameterName: Int)
{
    // body...
}

someFunction(1, secondParameterName: 2)

Default Parameter Values
: parameter에 Default value를 지정하는 방법을 알아보도록 하자.

func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12)
{
    // body...
}

someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6)
someFunction(parameterWithoutDefault: 4) // 이 경우 두번째 parameter에 12가 자동 지정되어 함수가 호출된다.

Variadic Parameters
: 가변 갯수 파라미터를 뜻한다. 
가변 갯수 파라미터로 넘어오면 constant array로 취급된다.
함수는 최대 하나의 variadic parameter를 가질 수 있다.

func arithmeticMean(_ numbers: Double...) -> Double
{
    var total: Double = 0
    
    for number in numbers
    {
        total += number
    }
    
    return total / Double(numbers.count)
}

arithmeticMean(1, 2, 3, 4, 5)
arithmeticMean(3, 8.25, 18.75)

In-Out Parameters
: 전산 전공자라면, 학부때 call-by-value, call-by-reference 개념을 배웠을 것이다.
swift는 함수의 parameter가 default로 상수(let)로 취급되고, 기본 타입(Int, Double . . . )이 구조체이기 때문에 call-by-value로 동작한다.
swift는 이러한 제약을 탈피하고자 In-Out Parameter라는 문법을 제공한다. 

func swapTwoInts(_ a: inout Int, _b: inout Int) {
    let temporaryA = a
    
    a = b
    b = temporaryA
}

in-out parameter로 상수, literal 값을 넘길 수 없다. 오직 변수(var)만 가능하다. 
또한, in-out parameter는 default 값을 가질 수 없고 variadic parameter는 in-out parameter가 될 수 없다. in-out parameter로 변수를 넘길 때 앞에 '&'을 붙여서 값이 변경될 것이라는 것을 표시한다. 

var someInt = 3
var anotherInt = 107

swapTwoInts(&someInt, &anotherInt)

print("someInt is now \(someInt), and anotherInt is now (anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"


Function Types
: 모든 function은 type을 가지고 있고, type은 parameter type과 return type에 의해 구별된다.

func addTwoInts(_ a: Int, _b: Int) -> Int 
{
    return a + b
}

func multiplyTwoInts(_ a: Int, _ b: Int) -> Int
{
    return a * b
}

두 함수의 타입은 (Int, Int) -> Int로 서로 같다.

Using Function Types
: function type은 다른 type들과 다를 것이 없다. 예를들어, function type의 변수, 상수를 만드는 것이 가능하다. 

var mathFunction: (Int, Int) -> Int = addTwoInts

print("Result: \(mathFunction(2, 3))")
// Prints "Result : 5"

mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"
// multiplyTwoInts는 addTwoInts와 같은 타입이니 같은 변수에 대입할 수 있다.

let anotherMathFunction = addTwoInts
// anotherMathFunction은 (Int, Int) -> Int 으로 타입추론 된다.

Function Types as Parameter Types
: function type을 parameter type으로 사용하여 function도 parameter로 넘길 수 있다.

func printMathResult(_ mathFunction: (Int, Int) -> Int, _a: Int, _ b:Int) 
{
    print("Result: \(mathFunction(a, b))")
}

printMathResult(addTowInts, 3, 5)
// Prints "Result: 8"

Function Types as Return Types
: function을 함수의 return type으로 사용할 수 있다.

func stepForward(_ input: Int) -> Int 
{
     return input + 1
}

func stepBackward(_ input: Int) -> Int 
{
    return - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int
{
    return backward ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 는 stepBackward()를 가리키게 된다.

print("Counting to zero:")

while currentValue != 0
{
    print("\(currentValue)...")
    currentValue = moveNearerToZero(currentValue)
}

print("zero!")
// 3...
// 2...
// 1...
// zero!


Nested Functions
: Nested Function은 function안에 존재하는 function이다.
위 chooseStepFunction(backward:) 예제를 Nested Function을 통해 구현할 수 있다.

func chooseStepFunction(backward: Bool) -> (Int) -> Int 
{
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    
    return backward ? stepBackward: stepForward
}

var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero는 stepForward()를 가리킨다.

while currentValue != 0 
{
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}

print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
[Swift 4.0] Collection Types  (0) 2017.02.03
[Swift 4.0] Strings and Characters  (0) 2017.01.22
Posted by 홍성곤
,

Introduction 
: Swift는 우리들에게 이미 익숙한 여러가지 control flow statement를 제공한다.
while, for, for-in, if, guard, switch, continue, break 등등..
특히, Swift의 switch는 다른 언어에 비해 상당히 powerful한 기능들을 가지고 있다. 이 부분은 뒤에서 차차 알아가도록 하자.


for-In Loops
: 'for-in'을 사용해서 array, dictionary, ranges of numbers 등을 iterating할 수 있다.

let names = ["Anna", "Alex", "Brian", "Jack"]

for name in names {
    print("Hello, \(name)!")

// array iterate

또한 dictionary를 key-value tuple로 iterate할 수 있다.

let numberOfLegs = ["spider : 8", "ant" : 6, "cat" : 4]

for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}
// dictionary iterate

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// ranges of numbers iterate
// index값이 필요 없다면 '_'로 대체할 수 있다.

let minutes = 60

for tickMark in 0..<minutes {
    // render the tick mark each minute (60 times)
}
// half open range operator iterate

stride(from:to:by:)를 사용해서 iterate 간격을 설정할 수 있다.

let minuteInterval = 5

for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // 0, 5, 10, 15 ... 45, 50, 55
}

또한, stride(from:through:by)를 사용해서 'through' 값을 포함하면서 iterate할 수 있다.

let hours = 12
let hourInterval = 3

for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // render the tick mark every 3 hours (3, 6, 9, 12)
}


While Loops
: while, repeat-while이 있는데 while은 다른 언어의 while과 다를바 없고 repeat-while은 다른 언어의 do-while과 같다. 

repeat {
    "statements"
} while "condition"


If

var temperatureInFahrenheit = 90

if temperatureInFahrenheit <= 32 {
    print("It is very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It is really warm. Don't forget to wear sunscreen.")
} else {
    print("It is not that cold. Wear a t-shirt")
}


Switch

: 앞서 말했듯이, Swift의 Switch문은 다른 언어에 비해 powerful한 기능을 제공한다.
Swift의 Switch문은 모든 case를 포함해야 한다. 즉, 'case'가 비교할 값이 될수 있는 모든 경우를 나타내지 않으면 반드시 'default:'를 구현해야 한다. 반대로 'case'가 모든 경우를 나타내고 있으면 'default:'를 생략할 수 있다. 

let someCharacter: Character = "z"

switch someCharacter {
    case "a":
        print("The first letter of the alphabet")
    case "z"
        print("The last letter of the alphabet")
    default:
        print("Some other character")
}

No Implicit Fall through
: swift의 Switch문은  C, Objective-C와 달리 자동으로 fall through 하지 않는다. 
이는, 실수로 break문을 생략함으로서 일어나는 버그들을 예방해준다.
'break'를 쓰지 않아도 fall through 하지 않지만 각 case마다 'break'를 사용해도 된다. 

각 case마다 하나이상의 실행문을 구현해야 한다. 그렇지 않으면 compile error가 발생한다.

let anotherCharacter: Character = "a"

switch anotherCharacter {
    case "a": // Invalid, the case has an empty body
    case "A":
        print("The letter A")
    default:
        print("Not the letter A")
}
// C, Objective-C 스타일 코딩이다. compile error 발생.

let anotherCharacter: Character = "a"

switch anotherCharacter {
    case "a", "A":
        print("The letter A")
    default:
        print("Not the letter A")
}
// Swift 스타일 코딩.

Interval Matching
: 해당 값이 특정 간격안에 포함되어 있는지 체크할 수 있다.

let approximateCount =62
let countedThings = 'moons orbiting Saturn"
let naturalCount: String

switch approximateCount {
    case 0:
        naturalCount = "no"
    case 1..<5:
        naturalCount = "a few"
    case 5..<12:
        naturalCount = "several"
    case 12..<100:
        naturalCount = "dozens of"
    case 100..<1000:
        naturalCount = "hundreds of"
    default:
        naturalCount = "many"
}

print("There are \(naturalCount) \(countedThings).")

Tuples
: Switch문에서 Tuple을 비교하는 방법을 알아보겠다. 

let somePoint = (1, 1)

switch somePoint {
    case (0, 0):
        print("\(somePoint) is at the origin")
    case (_, 0):
        print("\(somePoint) is on the x-axis")
    case (0, _):
        print("\(somePoint) is on the y-axis")
    case (-2...2, -2...2):
        print("\(somePoint) is inside the box")
    default:
        print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box"
// '_'는 제약이 필요없는 경우일 때 사용한다.

Value Bindings
: 비교값이 특정 case에 matching될 때 case문 안에서 사용할 수 있도록 let 또는 var로 값을 binding할 수 있다.

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))")
}
// Prints "on the x-axis with an x value of 2"
// binding 하기 위해서 '_'을 let 또는 var로 대체했다고 생각하면 된다.

Where
: Switch문 안에서 추가적이 condition을 체크하기 위해서 'where'문을 사용할 수 있다. 

let yetAnotherPoint = (1, 1)

switch yetAnotherPoint {
    case let(x, y) where x == y:
        print("(\(x), \(y)) is on the line x == y")
    case let(x, y) where x == -y:
        print("(\(x), \(y)) is on the line x == -y")
    case let (x, y):
        print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y"

Compound Cases 
: ','를 사용해서 하나의 case 여러 조건을 명시할 수 있다. 여러 조건중 하나에만 matching되면 해당 case를 만족하는 것으로 간주한다. 이를 'Compound Cases' 라고 한다.

let someCharacter: Character = "e"

switch someCharacter {
    case "a", "e", "i", "o", "u":
        print("\(someCharacter) is a vowel")
    case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
            "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
        print("\(someCharacter) is not a vowel or a consonant")
}
// Prints "e is a vowel"
// case문이 길 경우 여러줄로 명시해도 된다.

'Compound Cases'도 value binding을 사용할 수 있다. 

let stillAnotherPoint = (9, 0)

switch stillAnotherPoint {
    case (let distance, 0), (0, let distance):
        print("On an axis, \(distance) from the origin")
    default:
        print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"


Control Transfer Statements
: code 실행의 흐름을 전환시킬 수 있는 여러가지 statement들을 제공한다.
: 'continue', 'break', 'fallthrough', 'return', 'throw'가 있다.
이 장에서는 'continue', 'break', 'fallthrough'만 설명하겠다. 'return'은 "Function"에서, 'throw'는 "Propagating Errors Using Throwing Functions"에서 설명하겠다.

continue
: 현재 iteration 수행을 멈추고 다음 iteration을 실행하라는 뜻이다.

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "e", "u", " "]

for character in puzzleInput.characters {
    if charactersToRemove.contains(character) {
        continue
    } else {
        puzzleOutput.append(character)
    }
}

print(puzzleOutput)
// Prints "grtmndsthnklk"

Break
: control flow statement의 실행을 멈춘다. if문에서 사용시 labeled statement를 사용해야 한다.
: 반복문 에서도 사용 가능하다.

Break in Loop Statement
: 현재 속해있는 loop의 실행을 멈추고 loop를 빠져나온다. closing brace '}'를 기준으로 현재 loop를 판단한다. 

Break in a Switch Statement
: Switch문 안에서 'break'를 만나면 현재 실행을 중단 시키고 Switch문을 빠져나온다.
: 앞서 말했다시피, Swift의 Switch문은 모든 case를 명시해줘야 한다. 그리고 각 case는 하나 이상의 실행문을 가지고 있어야 한다. 그러면 어떠한 동작도 원하지 않는 case는 어떻게 처리할까? 그것은 해당 case문 안에 'break'만 명시해주면 된다.

let numberSymbol: Character = "1"
var possibleIntegerValue: Int?

switch numberSymbol {
    case "1"
        possibleInteger = 1
    case "2"
        possibleInteger = 2
    case "3"
        possibleInteger = 3
    default:
        break
}

if let integerValue = possibleIntegerValue {
    print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value could not be found for \(numberSymbol).")
}

Fallthrough
: C, Objective C의 switch문과 달리 Swift의 switch문은 자동으로 case를 fall through 하지 않는다. 만약 case를 fall through 하고 싶다면 case문 안에서 'fallthrough' 키워드를 사용해야 한다.
: 'fallthrough'를 사용하면 다음 case에 대해 조건을 체크하지 않고 무조건 실행 시킨다.(C와 마찬가지로)

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"

switch integerToDescribe {
    case 2, 3, 5, 7, 11, 13, 17, 19:
        description += " a prime number, and also"
        
        fallthrough
    default:
        description += " an integer."
}

print(description)
// Prints "The number 5 is a prime number, and also an integer."

Labeled Statements
: 간단히 말해서, break 또는 continue 하려고 하는 조건문, 반복문에 이름을 지어줘서 원하는 조건문, 반복문의 실행을 break, continue 하겠다는 것이다. 

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)

board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

var square = 0
var diceRoll = 0

gameLoop: while square != finalSquare {
    diceRoll += 1

    if diceRoll == 7 { diceRoll = 1 }
   
    switch square + diceRoll {
        case finalSquare:
            // diceRoll will move us to the final square, so the game is over
            break gameLoop
        case let newSquare where newSquare > finalSquare:
            // diceRoll will move us beyond the final square, so roll again
            continue gameLoop
        default:
            // this is a valid move, so find out its effect
            square += diceRoll
            square += board[square]
    }
}

print("Game over!")


Early Exit
: 'guard' statement는 'else' statement와 같이 쓰인다.
'guard' 뒤에 나오는 조건이 true가 아니면 else문이 실행 되는데 else문 안에서는 guard문이 존재하는 code block을 빠져 나오는 동작을 실행해야만 한다. 그렇지 않으면 compile error 발생. 
이러한 동작은 'return', 'break', 'continue', 'throw' statement를 사용하거나 return 하지 않는 method, function을 호출해서 실행할 수 있다. 여기서 return 하지 않는 method, function란 'fatalError(_:file:line:)과 같은 것들이다.


Checking API Availability
: Swift는 API의 Availability를 체크할수 있는 'availability condition'을 제공한다. 

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}
// iOS10, macOS 10.12 API를 지원하는지 체크한다. 맨 뒤 파라미터 '*'는 필수로 들어가야 되고, 이는 앞에 명시된 플랫폼 버전 이외에 다른 플랫폼 버전의 제약은 없다는 뜻이다. 


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Collection Types  (0) 2017.02.03
[Swift 4.0] Strings and Characters  (0) 2017.01.22
[Swift 4.0] Basic Operators  (0) 2017.01.16
Posted by 홍성곤
,

스위프트 중간 언어(SIL)
: 스위프트 컴파일러, swiftc는 스위프트 코드를 SIL(Swift Intermediate Language)로 만들고 LLVM IR로 변환한다. 또 IR 최적화기를 거쳐서 최종적으로 타깃머신에 맞는 기계코드를 생성한다.

swift 코드 -> SIL -> LLVM IR -> 기계 코드

Objective-C 호환성
: Objective-C에서 스위프트 객체를 사용하려면 NSObject를 최상위 클래스로 지정하고 상속받아 만들어야 한다. 그렇지 않으면 스위프트 전용 객체로 동작하기 때문에 Objective-C 에서는 사용할 수 없다. 
NSObject 객체를 상속받은 경우 스위프트 객체는 내부에서 Objective-C 객체로 자동 변환되고, Objective-C 런타임에서 동작한다.


Objective-C 객체와 메모리 구조
: 모든 운영체제는 동일한 구조로 메모리를 관리한다.
1- Stack: 함수, 함수 내에 변수에 대한 메모리 저장
2- Heap: 클래스, 클래스 인스턴스에 대한 메모리 동적 할당(클래스에 대한 메모리를 프로그램 실행시 로드된다.), Stack과 Heap 사이에 빈공간이 있음, Stack 윗 공간은 커널 영역이기 때문에 커널 영역을 보호하기 위해서 스택은 위에서 부터 메모리를 사용한고 Heap은 밑에서 부터 사용하기 때문에 두 메모리 공간을 많이 사용하면 할수록 빈 메모리 공간이 줄어든다.
3- BSS(심벌): 아직 초기화 되지않은 전역변수에 대한 메모리 저장
4- Data: 초기화가 완료된 전역변수에 대한 메모리 저장
5- Text: 프로그램 실행코드(실제 함수 구현, heap에 클래스에 대한 메모리가 저장되는데 여기에 클래스 메서드, 인스턴스 메서드의 Selector 변수들이 저장되는 것이고, 이 Selector에 대한 실제 구현 함수 코드는 Text영역에 할당된다.)


객체 예외성
: 모든 코코아 객체 인스턴스가 힙 영역에 생성되는 것은 아니다. 특이하게도 힙이 아니라 텍스트 영역과 데이터 영역에 생기는 경우가 있다. 
NSString 클래스는 NSObject에서 상속받는 코코아 클래스 중에서 유일하게 전역변수로 선언할 수 있다. NSString을 메서드 내에서 사용하더라도 NSString 지역변수는 Stack에 저장되고 NSString 클래스와 클래스 인스턴스에 대한 메모리는 Data영역에 저장되고 해당 문자 리터럴은 Text 영역에 저장된다. 즉 스택에 여러개의 NSString 변수가 생성되더라도 문자 리터럴이 같으면 중복으로 저장해서 사용할 필요가 없기 때문에 하나의 클래스, 클래스 인스턴스 메모리만 Data영역에 저장되고 문자 리터럴도 Text영역에 하나만 저장된다.

* Swift에서 String 객체는 네이티브 문자열 객체를 만들 수도있고, NSString을 연결해서 쓸수도 있다. 다만, 네이티브 문장열로 만들 경우에는 Objective-C의 NSString처럼 클래스, 클래스 인스턴스에 대한 메모리 할당 없이 Text영역에 있는 문자 리터럴을 포인터로 바로 연결해서 사용하기 때문에 NSString 보다는 가벼운 형태라고 할 수 있다. 


hash 메서드 
: isEqual: 메서드를 오버라이드한 경우라면 반드시 -hash 메서드도 다시 구현해야 한다. 왜냐하면 NSDictionary 같은 컬렉션 객체는 -isEqual: 메서드 대신 -hash 메서드를 사용해서 해시 값을 비교하기 때문이다. NSObject에 기본적으로 구현된 -hash 메서드는 self 메모리 포인터 값을 NSUInteger 타입 숫자로 바꿔줄 뿐이다. 따라서 -hash 메서드를 구현할 때도 -isEqual 메서드를 구현할 때와 마찬가지로 모든 속성에 대해 -hash 메서드를 호출해서 해시 값을 구하면 된다.


인스턴스, 클래스, 메타 클래스
: 인스턴스, 클래스, 메타 클래스 모두 isa 포인터를 가지고 있다. 인스턴스의 isa 포인터는 클래스를 가리키고 클래스의 isa 포인터는 메타 클래스를 가리키고 있다. 마지막으로 메타 클래스의 isa 포인터는 슈퍼메타 클래스가 있으면 그것을 가리키고 자신이 root 메타 클래스이면 자기 자신을 가리킨다. 인스턴스는 멤버 변수에 대한 정보만 가지고 있고, 클래스는 인스턴스 메서드 목록과 코드를 가지고 있으며, 메타 클래스는 클래스 메서드 목록과 코드를 가지고 있다. 


ARC 규칙
1) C 구조체 내부에 객체 포인터를 넣지마라
: C언어에서 사용하는  struct나 union 내부에 Objective-C 객체 포인터를 넣으면 ARC에서 메모리 관리를 할 수 없기 때문에 컴파일 에러가 발생한다.
ARC가 관리하지 않는다는 것을 강제로 명시하려면 '__unsafe_unretained' 수식어를 사용해야 한다. 다만, 해당 수식어를 사용하면 dangling 포인터에 접근할 위험이 있다.

2) id와 void* 타입을 명시적으로 타입 변환하라 


타입 연결(Objective-C 객체 <-> Core Foundation 객체)
: Core Foundation 객체란, C로 만들어진 구조체를 말한다.
대부분의 Core Foundation 구조체는 대응되는 Objective-C 객체와 toll-free bridged 된다. 즉, 무비용으로 타입 변환이 되는것이다. (CFRunLoop <-> NSRunLoop, CFBundle <-> NSBundle 등은 제외)
Core Foundation 구조체는 ARC가 메모리 관리를 하지 못하기 때문에 프로그래머가 직접 메모리 관리를 해줘야 한다. 때문에, Objective-C 객체로 타입 변환할 때에도 적절한 메모리 관리 코드가 삽입되야 한다. 
이제 부터 여러가지 타입변환 방법에 대해 알아보겠다.

1) __bridge 방식
: 객체의 소유권을 넘기지 않고 타입 연결만 하는 경우에 사용한다. 다만, 허상 포인터가 생길 수 있기 때문에 매우 위험하다. 

id sObject = [[NSObject alloc] init];
void *pObject = (__bridge void*) aObject;
id sOtherObject = (__bridge id) pObject;

2) __bridge_retained 또는 CFBridgingRetain 방식
: Objective-C 객체를 Core Foundation 포인터로 연결하면서 소유권도 주는 경우에 사용한다. 참조가 끝나면 CFRelease()와 같은 함수를 이용해서 소유권을 반환해야 한다.

id sObject = [[NSObject alloc] init];
void *pObject = (__bridge_retained void*) sObject;
// (__bridge_retained void*) 대신 CFBridgingRetain() 매크로를 사용해도 된다.

위 코드는 컴파일러에 의해서 다음과 같이 변환된다.

id sObject = [[NSObject alloc] init];
void *pObject = aObject;
[(id)pObject retain];

3) __bridging_transfer 또는 CGBridgingRelease 방식
: __bridge_retained와는 반대로 Core Foundation 참조 포인터를 Objective-C 객체로 연결하면서 소유권을 넘기는 경우에 사용한다.

id sObject = (__bridge_transfer id)pObject;
// (__bridge_transfer id) 대신 CFBridgingRelease() 매크로를 사용해도 된다.

위 코드는 컴파일러에 의해서 다음과 같이 변환된다.

is sObject = (id)pObject;
[sObject retain]:
[(id)aObject release];

Collection 클래스 성능
- Collection 클래스들은 직접 for문을 돌면서 objectAtIndex:, objectForKey: 등을 통해 iterating 하는것 보다 'for in' (fast enumeration), enumerateKeysAndObjectsUsingBlock: 등을 사용해서 iterating을 할때 더욱 성능이 좋다. 그 이유는 objectAtIndex, objectForKey 같은 메서드들은 해당 index, key에 대해 매칭되는 값을 찾아야 되고 해당 index, key에 대한 int, string 값을 생성하는 시간도 필요하기 때문이다.
'for in' 이나 enumerateKeysAndObjectsUsingBlock: 같은 경우는 이러한 것들을 고려하지 않고 단순히 iterating에만 신경 쓰면 된다.


NSArray vs NSSet 
- Construct
: NSArray가 빠르다. 중복체크를 할필요가 없기 때문
- iterating
: NSArray가 빠르다. 구성 방법이 NSSet보다 더 간단할 것이기 때문이다. 예를들어, NSArray는 단순한 Linked List로 구성되어 있다면 NSSet은 객체의 hash값을 테이블 형태로 저장할 것으로 보인다. (NSDictionary와 비슷한 형태로.. 왜냐하면 containsObject:를 써서 NSSet안에 있는 객체를 찾을때 객체의 hash값으로 O(1) 복잡도로 바로 찾도록 구현되어 있을것이기 때문)
- search
: NSSet이 빠르다. NSSet은 NSDictionary와 비슷하게 객체의 hash 값으로 찾기 때문에 0(1) 복잡도면 되지만, NSArray는 전체를 다 검색해야 되기 때문이다. 


NSInvocation
: Objective-C 메세지를 객체로 추상화 하는 클래스이다. 메시지 이름(selector), 시그니처(methodSignature), 메시지 수진자(target), 모든 인자 값(arguments)들을 포함하고, NSInvocation을 실행한 이후 리턴 값(return value) 까지도 담을 수 있다.
: 일반적으로 타깃과 액션을 지정해서 메시지를 보내는것보다 매우 번거로운 작업이다. 그러나  NSUndoManager 클래스와 비슷한 동작을 구현하는 것을 매우 쉽게 만들어 준다.

- 주의사항
1) 반드시 invocationWithMethodSignature 지정 생성자를 이용해서 생성하고 초기화를 해야 한다. (alloc, init 으로 하면 안됨)
2) -setArgument:atIndex: 메서드로 인자 값을 설정할 때 두 번째부터 인자 값을 지정해야 한다. 0번째와 1번째는 self, _cmd 이다. 
3) 넘기는 인자 값에 객체 참조가 있을 경우 효율성을 위해서 retain 하지 않는다. 따라서 인자 값으로 넘어가는 객체에 대한 소유권을 invoke시점까지 유지하고 싶으면 -retainArguments 메시지를 보내야 한다. 


메시지 포워딩
: 특정 객체에 구현되지 않은 메시지를 보내면 셀렉터와 일치하는 메서드가 없어서 오류가 난다. 이렇게 타깃 객체에 셀렉터와 일치하는 메서드가 없어도 에러를 발생시키지 않고 해결할 수 있는 방법은 다음과 같다

1) 동적 메서드 추가하기
: 우선 객체는 일치하는 메서드를 찾지 못하면 +resolveInstanceMethod:  메서드를 호출한다. 이 방법은 해당 메서드에 대한 동작을 하는 동적 메서드를 미리 구현해놓고, class_addmethod() 같은 런타임 API로 메서드를 추가하고 YES를 리턴하면 해당 메서드를 다시 호출한다.
 
2) 곧바로 포워딩 하기
: 다른 객체에게로 메시지를 포워딩 하는 것이다. 
-forwardingTargetForSelector: 메시지를 보내서 대신 수신할 객체가 있으면 리턴해주고 없으면 nil을 반환한다. 
이 메서드를 상위 클래스에 구현해놓고, 해당 메시지를 처리할 수 있는 내부 객체를 반환하는 형태로 구현하기도 한다. 

3) NSInvocation 사용해서 invoke
: 이 방법은 앞에도 설명했기 때문에 생략한다.


*  코어 파운데이션 객체들은 오픈소스로 애플 개발자 사이드 http://www.opensource.apple.com/source/CF에 공개되어 있다.







'IOS > 공통' 카테고리의 다른 글

Event Handling Guide - Responders  (0) 2017.05.03
Event Handling Guide - Gesture  (0) 2017.04.07
Cocoa Pods  (0) 2017.02.19
About Bundle  (0) 2017.02.14
iOS Architecture  (0) 2016.12.18
Posted by 홍성곤
,

Cocoa Pods

IOS/공통 2017. 2. 19. 17:46

1. ruby가 설치되어 있어야 한다. cocoapods는 루비에서 돌아가기 때문.
(OS X 10.7 이후에는 ruby가 포함되어 나오기 때문에 따로 설치 불필요.)
- sudo gem install cocoapods

2. setup
- pod setup --verbose (--verbose는 setup 과정을 로그로 찍어줌)

3. 해당 프로젝트 폴더로 이동 후 init.
- pod init (프로젝트 내에 Podfile이 생성됨.)
- open -a Xcode Podfile (textEdit로 열면 자동으로 인코딩 작업이 들어가 오류가 날 수 있으므로 xcode로 Podfile을 연다.)

4. 주석을 풀고 프로젝트에 맞게 세팅한다.
- platform :ios, "8.0"
- use_frameworks! (swift는 static Library가 지원이 안되기 때문에, 이 옵션을 꼭 넣어서 Pod이 해당 라이브러리를 동적 라이브러리로 생성하도록 해야 한다.)

platform :ios, "8.0" use_frameworks!

5.  Podfile에 사용할 library 이름, 버전 추가
- pod 'Alamofire', '2.0.2' 
- pod version은 Semantic versioning을 따른다
 : major.minor.bugfix(patch) 형식이다.
 : major 버전이 바뀌면 이전 버전이랑 호환이 안된다는 뜻이다. 즉, major 버전업은 함부로 하면 안된다. 
 : minor 버전은 기능이 추가되었지만 하위버전이랑 호환이 된다는 뜻이다. 별다른 작업없이 업데이트 해도 특별한 문제가 없다.
 : bugfix(patch) 버전은 말그대로 버그 픽스다. 
 : 단, 예외가 있다. 예를 들어 major버전이 0인경우. 이는 beta 프로젝트라는 것이다. 이 경우 minor 버전이 major 버전이 된다. 즉, 0.8.0 버전을 사용하고 있는경우, 0.9.0버전을 마음대로 업데이트 하면 안된다.

6. Xcode프로젝트 닫고 install
- pod install
- 해당 프로젝트와 pod 프로젝트가 포함된 workspace파일과 pod library들이 저장될 Pod폴더가 생겨난다.

* 추후 추가할 library나 버전 업데이트 할경우 podfile을 변경해서 다시 pod install 하면 된다. 


'IOS > 공통' 카테고리의 다른 글

Event Handling Guide - Gesture  (0) 2017.04.07
Objective-C, Swift 기초  (0) 2017.03.26
About Bundle  (0) 2017.02.14
iOS Architecture  (0) 2016.12.18
UIApplicationMain?  (0) 2016.02.18
Posted by 홍성곤
,

Naming Conventions

IOS/Objective-C 2017. 2. 18. 17:26

Category의 메서드를 만들때는 prefix를 붙여야 한다. 특히, Foundation 클래스의 카테고리를 만들때는....
ex) 내가 NSString의 a라는 메서드를 카테고리로 선언했는데, 다른 프레임워크에서 똑같이 NSString 카테고리로 a라는 메서드를 만들었으면 두 메서드는 충돌이 나고, runtime시 하나의 메서드만 NSString 클래스에 더해질 것이다.

ex2) 내가 iOS8 버전에서 NSString의 a라는 메서드를 만들었는데 iOS9버전에 애플이 NSString에 a라는 메서드를 추가했을때 역시 충돌이 일어난다.



'IOS > Objective-C' 카테고리의 다른 글

Objective-C Runtime Programing Guide  (0) 2017.02.17
Block의 모든것(4)  (0) 2017.01.07
Block의 모든것(3)  (0) 2017.01.02
Block의 모든것(2)  (0) 2017.01.02
Block의 모든것(1)  (0) 2017.01.02
Posted by 홍성곤
,

objective-c 프로그램은 런타임 시스템과 상호작용하기 위한 3가지 레벨이 있다.

1. Objectitve-C Source Code
- message call

2. NSObject Methods
- class, isKindOfClass, isMemberOfClass, respondsToSelector, conformsToProtocol, methodForSelector 등등

3. Runtime Functions
- 런타임 시스템은 dynamic shared library이다. 헤더파일들/usr/include/objc 디렉토리 안에 위치해 있다.


Messaging

objc_msgSend(receiver, selector, arg1, arg2, ...)

NSObject, NSProxy클래스로부터 상속받은 모든 클래스는 isa 변수(슈퍼클래스를 가리키는 포인터)와 dispatch table을 가진다.
dispatch table은 자신의 message name과 매칭되는 실제 함수 구현체의 포인터를 가지고 있는 map table이라고 보면된다.

objc_msgSend가 하는일은 receiver의 dispatch table에서 selector의 이름으로 함수 구현체를 찾고, 없으면 isa포인터를 통해 슈퍼클래스에 접근한 후 다시 dispatch table을 뒤진다.(이런식으로 recursive하게 root class까지 넘어간다.)

위 내용이 동적 바인딩이다. 


Using hidden Arguments

objc_msgSend가 결국 메세지에 맞는 함수를 찾아서 호출해주는데 프로그래머가 메세지에 넘겨주는 파라미터 이외에 두개의 숨겨진 파라미터 2개를 같이 넘겨준다.
하나는 receiver, 하나는 selector이다. 


Getting a Method Address

동적바인딩을 피하고 싶은경우, methodForSelector: 메서드를 사용해서 함수의 주소를 얻어와서 직접 함수를 call하면 된다. 하나의 메서드가 연속적으로 여러번 불려야 할경우 퍼포먼스를 조금이나마 개선하기 위해 해당 방식을 사용할 수 있다.

methodForSelector:은 런타임시스템에 의해 제공된다. Objective-C 언어가 제공하는것이 아니다. 


Dynamic Method Resolutions

프로퍼티를 @dynamic 지시어를 지정하면 dynamic 메소드 콜을 할 수 있다.
예를들어 appdelegate의 window 프로퍼티를 dynamic 메소드 콜로 동작하게 하려면

UIWindow *dynamicMethodIMP(id self, SEL _cmd) {

    return [[UIWindow alloc] initWithFrame:CGRectZero];

}

@dynamic window;

+ (BOOL)resolveInstanceMethod:(SEL)aSel

{

    if (aSel == @selector(window))

    {

        class_addMethod([self class], aSel, (IMP)dynamicMethodIMP, "aaa");

        

        return YES;

    }

    

    return [super resolveInstanceMethod:aSel];

}

이런식으로 구현 하면 됨. resolveClassMethod:를 사용하여 클래스 메소드도 dynamic 메소드 콜 적용 가능.


Dynamic Loading

NSBundle를 통해서 Objective-C 모듈을 동적으로 로딩할 수 있다. 


'IOS > Objective-C' 카테고리의 다른 글

Naming Conventions  (0) 2017.02.18
Block의 모든것(4)  (0) 2017.01.07
Block의 모든것(3)  (0) 2017.01.02
Block의 모든것(2)  (0) 2017.01.02
Block의 모든것(1)  (0) 2017.01.02
Posted by 홍성곤
,