'IOS'에 해당되는 글 51건

  1. 2017.02.14 About Bundle
  2. 2017.02.03 [Swift 4.0] Collection Types
  3. 2017.01.22 [Swift 4.0] Strings and Characters
  4. 2017.01.16 [Swift 4.0] Basic Operators
  5. 2017.01.13 [Swift 4.0] The Basics
  6. 2017.01.07 Block의 모든것(4)
  7. 2017.01.02 Block의 모든것(3)
  8. 2017.01.02 Block의 모든것(2)
  9. 2017.01.02 Block의 모든것(1)
  10. 2017.01.01 ARC 규칙

About Bundle

IOS/공통 2017. 2. 14. 18:48

패키지
: any directory라고 표현되고, finder는 패키지를 하나의 파일인것 처럼 보여준다.


번들
: 표준화된 구조를 가지고 있는 디렉토리라고 한다. 실행가능한 코드와 코드에 의해 실행되는 resources들을 담고 있다.


번들의 종류
- Application : 어플리케이션 번들은 코드와 앱을 launch할때 필요한 resource들을 다룬다.
- Frameworks : 프레임워크 번들은 dynamic shared library와 이것과 연관된 resource들을 다룬다. 예를들어 header file같은것들..
 - plug-in들(OS X)


번들의 생성
: 보통 번들은 우리가 직접 생성하지 않는다. 보통 Xcode에서 새로운 프로젝트를 만들면 그때 Xcode가 target에 맞는 번들을 생성해 준다. 물론 모든 target이 번들을 기본적으로 가지는것은 아니다. application, framework, loadable bundler target등이 자신과 매칭되는 번들을 가지고 있고, shell tool, static library target등은 번들을 기본적으로 가지지 않는다.


번들 관리
: Objective-C에서는 NSBundle 클래스를 통해 번들을 관리한다. C-based application들은 CFBundleRef를 통해 번들을 관리한다.

- 대부분의 Core Foundation, Cocoa type들과 단리 CFBundleRef와 NSBundle과 toll-free bridged 되지 않는다. 그러나 각자의 객체에서 bundle path를 추출할 수 있으며, 그것을 가지고 각자 객체를 만들 수 있다.







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

Objective-C, Swift 기초  (0) 2017.03.26
Cocoa Pods  (0) 2017.02.19
iOS Architecture  (0) 2016.12.18
UIApplicationMain?  (0) 2016.02.18
디버깅 (Crash 핸들링)  (1) 2015.11.05
Posted by 홍성곤
,

Introduction
: 스위프트의 주요 Collection type은 Array, Set, Dictionary가 있다. 
Array는 ordered collection, Set은 Unordered, unique value collection이고 Dictionary는 Unordered, key-value 기반 collection이다.


Mutability Of Collections
: Mutability를 결정하는 기준은 collection을 var, let 중 무엇으로 선언했느냐에 따른다.
var로 선언하면 mutable이 되고, collection이 포함하고 있는 item들을 adding, removing, changing할 수 있다. 
let으로 선언하면 immutable이 되고, item들의 조작이 불가능하다.

*변경이 불가능한 collection에 대해 모두 immutable로 선언하는 것이 예상치 못한 side effect나 performance 측면에서도 유리하다.


Arrays
: 같은 type의 value들을 저장한다. 똑같은 값의 중복 저장이 가능하다. 
: Objective-C의 NSArray와 bridged(호환)된다. 

Creating an Empty Array

var someInts = [Int]()

print ("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
// someInts는 [Int] 타입이 된다. Int 타입의 값들을 저장할 수 있는 배열이다.

Array가 이미 타입이 지정되었으면 타입을 명시할 필요없이 바로 'empty array literal'로 빈 배열을 생성할 수 있다. 

someInts.append(3)
// someInts는 1개의 Int 타입의 value를 저장하고 있다.
someInts = []
// someInts는 empty array가 된다. 하지만 여전히 [Int] 타입이다. 

Creating an Array with a Default Value
: Array 초기화시 size와 default값을 지정할 수 있다.

var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 타입은 [Double]이고, [0.0, 0.0, 0.0] 값을 갖는다.

Creating an Array by Adding Two Arrays Together
: 기존 두개의 Array를 합쳐서 하나의 Array를 생성할 수 있다. 단, 기존 두개의 Array의 타입이 compatible해야 한다.(compatible이란 의미가, 꼭 같은 타입만을 의미하는것은 아닌것 같다. 상속 관계에 있는 클래스로 시도를 해봤으나 실패했다. Double, Int 타입도 실패했다. 하지만 same이 아닌 compatible이란 단어를 쓴것으로 보아 같은 타입이 아님에도 '+' 연산이 되는 type 메커니즘이 있는게 아닐까?)

var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles는 [Double]타입이다.
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles는 [Double]타입으로 추론되고, [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] 값을 갖는다.

Creating an Array with an Array Literal
: Array literal로 Array선언이 가능하다.

var shoppingList: [String] = ["Eggs", "Milk"]

shoppintList는 [String] 타입으로 선언되었기 때문에 array literal 안에는 String 타입의 value들이 들어가야 한다.
단, 타입을 명시하지 않을 수도 있다.

var shoppingList = ["Eggs", "Milk"]

타입을 명시하지 않았지만 array literal에 들어있는 값들이 모두 String 타입이기 때문에, [String] 타입으로 추론된다.

Accessing and Modifying an Array
: array의 값을 접근하거나 변경하기 위해서 array의 메서드나 프로퍼티 또는 subscript syntax를 이용한다.

print("The shopping list contains \(shoppingList.count) items")

if (shoppingList.isEmpty) {
    print("The shopping list is empty")
} else {
    print("The shopping list is not empty")
}

append(_:) 메서드를 이용해 item을 array 끝에 추가할 수 있다.

shoppingList.append("Flour")

+= 연산자를 이용해 한개 이상의 item들을 추가할 수 있다.

shoppingList += ["Banking powder"]
shoppingList +=["Chocolate spread", "Cheese", "Butter"]

subscript syntax를 이용해서 값을 검색할 수 있다.

var firstItem = shoppingList[0]

또한 subscript syntax를 이용해 기존 index에 값을 변경할 수 있다.

shoppingList[0] = "Six eggs"

subscript syntax에 범위를 지정해서 값을 변경할 수 도 있다. 심지어 변경하려는 값들의 범위와 subscript syntax로 지정한 범위가 맞지 않아도 된다.

//현재 shoppingList에는 ["Six Eggs", "Milk", "Flour", "Banking powder", "Chocolate Spread", "Cheese", "Butter"] 7개 item들이 들어있다.

shoppingList[4..6] = ["Bananas", "Apples"]
// 4, 5, 6번째 index item인 "Chocolate Spread", "Cheese", "Butter"이 "Bananas", "Apples"로 바뀌면서 shoppingList의 갯수도 7개에서 6개로 줄어들었다. 

shoppingList.insert("Maple Syrup", at: 0)
// 첫번째 index item이 "Maple Syrup"이 되고 나머지 6개 item들은 하나씩 뒤로 밀린다. 총 갯수 7개.

let mapleSyrup = shoppingList.remove(at: 0)
// "Maple Syrup"이 제거 되고 다시 갯수는 6개가 된다. 

let apples = shoppingList.removeLast()
// "Apples"가 제거 된다. array의 마지막 item을 제거할 때는 remove(at:) 보다 removeLast()를 사용해라. index가 array의 범위를 벗어나면 runtime error가 발생한다.

Iterating Over an Array

for item ins shoppingList  {
    print(item)
}
// Six eggs, Mils, Flour, Baking Powder, Bananas가 출력된다.

for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)
")
}
// index가 필요한 경우 Tuple을 사용한 enumerate를 한다.


Sets
: Set은 
Unordered, unique value collection이다. Objective-C의 NSSet과 bridged 된다.

Hash Values For Set Types
: Set에 저장되기 위해선 hashable type이어야 한다. 즉, hash value를 계산할 수 있는 type이어야 하고, a == b 이면, a.hashValue == b.hashValue 여야 한다.
Swift의 모든 basic type( String, Int, Double, Bool )들은 hashable 하다. Enumeration case value들도(associated value들 없이) 기본적으로 hashable 하다.  

* 자신이 만든 custom type이 dictionary key나 set value type으로 저장되길 원한다면, Hashable 프로토콜을 
conform해야 한다. hashable 프로토콜을 구현한다는 것은 hashValue를 property로 제공해야 한다는 것이다. 다만, hashValue는 꼭 프로그램내 또는 다른 프로그램에서 항상 같은 값을 가지고 있을 필요 없다.

Hashable 프로토콜은 Equatable 프로토콜을 conform하고 있으므로 equals operator(==)를 구현해야만 한다. '==' 구현은 다음 세가지 조건을 만족해야 한다.
1) a == a
2) a == b 는 b == a를 의미한다.
3) a == b 이고 b == c 이면 a == c 이다.

Set Types Syntax
: Set<Element> 일때, 'Element'는 해당 Set이 저장할 수 있는 Type 이라는 뜻이다.

Creating and Initializing an Empty Set

var letters = Set<Character>()

print("letters is of type Set<Character> with (letters.count) items.")

letters.insert("a")
letters = []
// letters는 empty set이 되지만, Set<Character> type을 유지한다.

Creating a Set with an Array Literal

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
// 이처럼 type을 생략할 수도 있다.

Accessing and Modifying a Set

print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."

if favoriteGenres.isEmpty {
    print("As far as music goes, I'm not picky.")
} else {
    print("I have particular music preferences.")
}

favoriteGenres.insert("Jazz")
// 4 items을 포함하게 된다.

if let removedGenre = favoriteGenres.remove("Rock") {
    print("\(removedGenre)? I'm over it.")
} else {
     print("I never much cared for that.")
}
// Rock이 있기 때문에 Rock이 제거되고 "Rock? I'm over it."이 출력된다. 3 items을 포함하게 된다.

if favoriteGenres.contains("Funk") {
    print("I get up on the good foot.")
} else {
    print("It's too funky in here.")
}
// 해당 item이 포함되었는지 check하기 위해 contains() 메서드를 사용한다.

Iterating Over a Set

for genre in favoriteGenres {
    print("\(genre)")
}
// Jazz, Hip hop, Classical을 출력.

Set은 unordering collection이기 때문에 ordered iterating을 하기 위해서는 sorted() 메서드를 사용해야 한다. sorted() 메서드는 '<' operator 기준으로 정렬시킨 array를 반환한다.

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical, Hip hop, Jazz 순으로 출력된다.


Performing Set Operations
: set operation들을 통해서 교집합, 합집합, 여집합 등을 체크할 수 있다.

Fundamental Set Operations


let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]

Set Membership and Equality

- '==' operator는 두 set이 모두 같은 value들을 포함하고 있는지 체크한다.
- isSubset(of:), isSuperset(of:)를 통해 포함관계(equal도 포함)를 파악할 수 있다.
- isStrictSubset(of:), isStrictSuperset(of:)은 equal은 해당되지 않는 포함관계를 파악할 수 있다.
- isDisjoint(with:)를 통해 두 set이 서로소 인지 체크할 수 있다.


Dictionaries
: <key, value> 이면서 unordering collection이다. Objective-C의 NSDictionary 타입과 bridged 된다.

Dictionary Type Shorthand Syntax
: dictionary의 keyType은 hashable 프로토콜을 conform해야 한다. dictionary 타입은 '[Key:Value]'로 표현할 수 있다.

Creating an Empty Dictionary

var namesOfIntegers = [Int: String]()

namesOfIntegers[16] = "sixteen"
namesOfIntegers = [:]
// namesOfIntegers는 empty dictionary로 초기화 되었더라도 기존 type인 '[Int: String]'을 유지한다.

Creating a Dictionary with a Dictionary Literal

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// 타입을 명시하지 않고 Dictionary 초기화가 가능하다.

Accessing and Modifying a Dictionary

print("The airports dictionary contains \(airports.count) items.")
// "The airports dictionary contains 2 items" 출력 

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}

airports["LHR"] = "London"
// airports는 이제 3 item을 포함한다.

airports["LHR"] = "London Heathrow"
// "London" 값을 "London Heathrow"로 대체된다. 

위 subscript를 사용해서 value를 set하는 것을 updateValue(_:forKey:) 메서드로 대체할 수 있다. 다만, 
updateValue(_:forKey:) 메서드는 oldValue가 존재하면 return 한다.

if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
    print("The old value for DUB was \(oldValue).")
}
// "
DUB" key에 "Dublin" 값이 저장되어 있기 때문에 oldValue에 "Dublin"이 return 된다.
// "The old value for DUB was Dublin" 출력.

if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} else {
    print("That airport is not in the airports dictionary.")
}
// "The name of the airport is Dublin Airport" 출력

airports["APL"] = "Apple International"
airports["APL"] = nil
// nil 값을 대입하면 해당 Key, Value는 Dictionary에서 제거 된다. 

위 nil을 대입해서 해당 값을 제거하는것을 removeValue(forKey:) 메서드로 대체할 수 있다. 이 메서드는 해당 값이 있으면 제거하고 제거된 값을 return한다. 해당값이 없으면 nil을 return 한다. 

if let removedValue = airports.removeValue(forKey: "DUB") {
    print("The removed airport's name is \(removedValue).
")
} else {
    print("The airports dictionary does not contain a value for DUB.")
}
// 
"DUB" key에 대한 "Dublin Airport"이 removedValue에 대입된다.
// "The removed airport's name is Dublin Airport." 출력

Iterating Over a Dictionary

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson, LHR: London Heathrow 출력.

for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}
// key만 iterating이 가능하다.

for airportName in airports.values {
    print("Airport name: \(airportName)")
}
// value만 iterating이 가능하다.

Dictionary의 keys, values를 통해 array를 초기화 할 수 있다.

let airportCodes = [String](airports.keys)
// airportCodes는 ["YYZ", "LHR"]로 초기화 된다.

let airportNames = [String](airports.values)
// airportNames는 ["Toronto Pearson", "London Heathrow"]로 초기화 된다.

Dictionary는 unordering collection이다. ordering iterating을 하려면 keys 또는 values의 sorted() 메서드로 iterating을 해야한다. 

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

[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
[Swift 4.0] Strings and Characters  (0) 2017.01.22
[Swift 4.0] Basic Operators  (0) 2017.01.16
[Swift 4.0] The Basics  (0) 2017.01.13
Posted by 홍성곤
,

Introduction
: 스위프트의 String 타입은 Foundation 프레임워크의 NSString 클래스의 bridged 타입이다.
Foundation 프레임워크는 NSString 클래스의 public 메서드를 String 클래스에서도 그대로 사용할 수 있게 만들었다.(스위프트 3.0.1 부터 가능)


String Literals
: "" 로 둘러쌓여 있는 문자는 String Literal로 간주한다.

let someString = "Some string literal value"


Initializing an Empty String
: variable에 empty string을 만드는 방법은 두가지가 있다.

var emptyString = ""
var anotherEmptyString = String()

if (emptyString.isEmpty) {
    print("Nothing to see here")
}
// 위 두 변수 모두 empty string 으로 간주되며, 'isEmpty'를 통해 empty 체크된다.


String Mutability
: String의 mutability는 var, let 중 어느것으로 선언되었느냐에 따라 달라진다.

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another highlander"
// 컴파일 에러 발생, let으로 선언된 String은 수정이 불가능 하다.


Strings Are Value Types
: String 타입은 구조체이다. 즉, reference 타입이 아니라 value 타입이다. 그래서 String이 다른 함수나 메서드에 넘겨지거나 다른 변수, 상수에 할당될 때 값이 복사된다. 
: 스위프트 컴파일러는 이러한 복사 동작을 최적화 해서 꼭 복사가 필요한 경우에만 복사한다.

let a = "abc"
var b = a 
// 아직 복사가 이루어 지지 않는다. a, b 모두 같은 String 구조체를 가리킨다.

b += "def"
// 이때 복사가 이루어 진다. 즉, 기존 문자 리터럴이 변경되지 않는것을 보장하지 못할 경우에만 복사가 이루어 진다.


Working with Characters
: String의 characters property를 통해 각 character를 iterating할 수 있다.

for character in "Dog!".characters {
    print(character)
}

character 타입의 상수, 변수를 선언할 수 있다.

let exclamationMark: Character = "!"
// let exclamationMark = "!" 처럼 자료형을 명시해주지 않으면 String으로 타입추론됨.

String은 Character의 Array로 만들어질 수 있다.

let catCharacters: [Character] = ["C", "a", "t", "!"]
let catString = String(catCharacters)
print(catString)


Concatenating Strings and Characters

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

Character를 덧붙이는 것도 가능하다.

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
//welcome now equals "hello there!"


String Interpolation

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"


Unicode
: Unicode는 text의 인코딩, 표현, 처리에 관한 국제 표준이다. 스위프트의 String, Character는 fully Unicode-compliant 타입이다.

Unicode Scalars
: 스위프트의 String 타입은 Unicode scalar value들로 이루어 진다. Unicode scalar는 21 bit의 unique number 값을 갖는다. 

ex) U+0061은 "a"(LATIN SMALL LETTER A)를 나타낸다.

Special Characters in String Literals
: String literals은 special characters를 포함할 수 있다.

- The escaped special characters
\0 (null character), \\ (backslash), \t (horizontal tab), \n(line feed), \r (carriage return), 
\" (double quote), \' (single quote)
- An arbitrary Unicode scalar
\u{n} n은 16진수다. 

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein

let dollarSign = "\u{24}"
// $, Unicode scalar U+0024
let blackHeart = "\u{2665}" 
// ♥, Unicode scalar U+2665
let sparklingHeart  = "\u{1F496}"
// 💖, Unicode scalar U+1F496


Extended Grapheme Clusters
: Grapheme은 문자소를 뜻한다. 문자소란 의미를 나타내는 최소문자 단위이다.(ex. 'ㄴ', 'ㄹ', 'ㅏ') 
: Extended Grapheme Clusters란 하나이상의 문자소가 합쳐져서 만들어진 human-readable character정도로 이해하면 되겠다. Character 타입은 Extended Grapheme Clusters를 표현하는 타입이라고 생각하면 된다.

let precomposed: Character = "\u{D55C}" 
// '한'
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"
// 'ㅎ' 'ㅏ' 'ㄴ' 이 합쳐져서 '한'이 된다.
// (precomposed == decomposed) 비교하면 true가 return 된다.

Counting Characters

: String의 프로퍼티인 characters의 프로퍼티인 count로 Characters의 갯수를 얻을 수 있다.
: string concatenation, modification이 string의 count의 항상 영향을 미치치 않는다. extended grapheme clusters를 통해서 여러개의 Unicode Scalars가 더해져서 하나의 Character를 표현할 수 있기 때문이다. 아래 예제를 보자.

var word = "cafe"
print("the number of characters in \(word) is \(word.characters.count)")
// Prints "the number of characters in cafe is 4"

word += "\u{301}"
// '`', COMBINING ACUTE ACCENT, U+0301

print("the number of characters in \(word) is \(word.characters.count)")
// Prints "the number of characters in café" is 4"

*Extended grapheme Clusters는 하나 이사의 Unicode scalars로 이루어져 있다. 그러므로 스위프트의 Character 타입은 똑같은 문자를 표현하고 있다하더라도 저장 메모리 공간은 다를수 있다. 이것은 String의 갯수를 측정할 때, 단순의 메모리의 크기로 측정할 수 없다는것을 뜻한다. 즉, String의  count를 구하기 위해서는 String의 프로퍼티인 characters를 Extended grapheme Clusters 단위로 iterating 해서 구해야 한다. 반대로 Objective-C의 NSString은 UTF-16(16비트 기준)로 문자를 표현하기 때문에 메모리 크기로 NSString의 length를 계산할 수 있다. 
즉, 이러한 이유로 똑같은 문자열을 표현하더라도 스위프트의 String.characters.count와 NSString의 length 프로퍼티의 값이 다를 수 있다. 


Accessing and Modifying a String
: 스위프트에서는 method, 프로퍼티들 그리고 subscript를 통해서 String을 접근 및 변경할 수 있다.

String Indices
: String은 index type을 갖는데, 이는 String 안에 있는 각 Character의 Position과 연관된다.
위에도 언급했듯이 Character는 같은 문자라 하더라도 다른 메모리 공간을 사용할 수 있어서 String은 integer 값으로 indexing 하지 않는다.

let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

범위 밖에 있는 index를 접근하려고 하면 runtime error가 발생한다.

greeting[greeting.index] //Error
greeting.index(after: greeting.endIndex) //Error

characters의 indices 프로퍼티를 접근해서 String 안에 있는 모든 index에 접근할 수 있다.

for index in greeting.characters.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n T a g ! "

* 위에 소개된 startIndex, endIndex 프로퍼티와 index(before:), index(after:), index(_:offSetBy:) 메서드는 Collection 프로토콜을 따르는 타입은 모두 사용할 수 있다. 즉, String 이외에 Array, Dictionary 그리고 Set 에서도 사용 가능하다.

Inserting and Removing
: insert(_: at:), insert(contentsOf:at:) 메서드를 통해 문자나 문자열을 삽입할 수 있다.
: remove(at:), removeSubrange(_:) 메서드를 통해 문자나 문자열을 제거할 수 있다.

var welcome = "hello"

welcome.insert("!", at: welcome.endIndex)
// welcome = "hello!"

welcome.insert(contentsOf:" there".characters, at:welcome.index(before: welcome.endIndex))
// welcome = "hello there!"

welcome.remove(at: welcome.index(before: welcome.endIndex))
//welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6) ..< welcome.endIndex

welcome.removeSubrange(range)
// welcome now equals "hello"

* 위에 소개된 insert(_: at:), insert(contentsOf:at:), remove(at:), removeSubrange(_:) 메서드는 RangeReplaceableCollection 프로토콜을 따르는 타입은 모두 사용할 수 있다. 즉, String 이외에 Array, Dictionary, Set 에서도 사용 가능하다.


Comparing Strings
: 스위프트는 string, character의 비교연산을 하기위한 3가지 방법을 지원한다. 

String and Character Equality

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."

if quotation == sameQuotation {
    print("These two strings are considered equal")
}

String은 linguistic meaning과 appearance이 같으면 둘은 같다고 인식한다. 즉, 다른 Unicode scalars로 이루어져있다 하더라도 의미와 나타내는 모양이 같으면, 둘을 같은 문자열로 취급하는 것이다. 

let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
// 두 상수 모두 "Voulez-vous un café"를 나타낸다.

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// 두 문자열을 구성하는 Unicode scalars가 다르더라도 의미와 모양이 같기 때문에 같은 문자열로 취급한다.

let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"
// 하나는 영어 'A'이고, 다른 하나는 러시아어의 'A'이다.

if laticCapitalLetterA != cyrillicCatitalLetterA {
    print("These two characters are not equivalent.")
}
// 둘은 모양은 같지만 linguistic meaning 즉, 의미가 다르기 때문에 다른 문자로 취급된다.

Prefix and Suffix Equality
: 두 문자열이 같은 prefix 또는 suffix를 가지고 있는지 확인하려면 hasPrefix(_:), hasSuffix(_:) 메서드를 사용해야 한다. 

let romeoAndJuliet = [
    "Act 1 Scene 1 : A street outside Capulet's mansion",
    "Act 1 Scene 2 : The Great Hall in Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's mansion",
    "Act 2 Scene 2: Outside Friar Lawrence's cell" 


var act1SceneCount = 0

for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
// act1SceneCount = 2

var mansionCount = 0
var cellCount = 0

for scene in romeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence' cell") {
        cellCount += 1
    }
}
// mansionCount = 3, cellCount = 1


Unicode Representations of Strings
: Unicode 문자열이 text file이나 다른 저장소에 쓰여질때 encoding 될수 있는 여러개의 encoding form이 있다. 각 encoding form은 문자열을 small chunk들, 즉 code unit들로 쪼개서 encoding한다. 
- UTF-8 : 8bit의 code units로 encoding (ASCII encoding과 representation이 동일하다.)
- UTF-16 : 16bit의 code units로 encoding
- UTF-32 : 32bit의 code units로 encoding (Unicode의 scalar가 21bit로 이루어져 있기 때문에 Unicode scalar 단위로 쪼개서 encoding 하려면 UTF-32를 이용해야 한다.)

let dogString = "Dog!!🐶"
// !!(Unicode scalar U+203C) , 🐶(Unicode scalar U+1F436)

위 dogString을 각각의 encoding form으로 다뤄보겠다.

UTF-8 Representation

String의 utf8 프로퍼티를 통해 각 code unit에 접근할 수 있다. utf8 프로퍼티는 String.UTF8View 타입이며, 이는 UInt8 값들의 collection이다.

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
// 68 111 103 226 128 188 240 159 144 182

UTF-16 Representation

String의 utf16 프로퍼티를 통해 각 code unit에 접근할 수 있다. utf16 프로퍼티는 String.UTF16View 타입이며, 이는 UInt16 값들의 collection이다.

for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
// 68 111 103 8252 55357 56347

Unicode Scalar Representation

String의 unicodeScalars 프로퍼티를 통해 각 code unit에 접근할 수 있다. unicodeScalars 프로퍼티는 UnicodeScalarView 타입이며, 이는 UnicodeScalar의 collection이다.
각 UnicodeScalar는 21bit의 scalar 값을 가지고 있으며 value 프로퍼티로 접근할 수 있다. 이는 UInt32 타입이다.

for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
// 68 111 103 8252 128054

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D o g !! 
🐶

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

[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
[Swift 4.0] Basic Operators  (0) 2017.01.16
[Swift 4.0] The Basics  (0) 2017.01.13
Posted by 홍성곤
,

Introduction
: 스위프트는 대부분의 standard C operators를 지원한다. 게다가 빈번히 일어나는 실수를 줄이기 위한 동작들도 추가되었다. 예를들어, '=' operator는 값을 return 하지 않는다.(우리는 종종 if문 안에서 '==' 대신 '='를 입력한다. C에서는 '='는 값을 return하기 때문에 compile time에 해당오류를 잡기 힘들고, 심지어는 runtime 에서조차 잡기 힘들다.)
또한 '+', '-', '*', '/', '%'는 overflow를 허용하지 않는다. 그리고 'a..<b', 'a...b' 같은 다양한 operator를 제공하면서 프로그래머에게 편의를 제공한다. 


Assignment Operator

let b = 10
let a = 5
a = b

let (x, y) = (1, 2)


Arithmetic Operators
: Addition (+), Substraction(-), Multiplication(*), Division(/)
: 여느 언어와 다르게 overflow를 허용하지 않는다. 
: Addition operator는 String concatenation을 지원한다. 

"hello, " + "world"
// "hello, world"


Remainder Operator
: C와 동일

9 % 4
// equals 1

-9 % 4
// equals -1


Unary Minus Opeartor
: C와 동일

let three = 3
let minusThree = -three
let plusThree = -minusThree


Unary Plus Operator
: C와 동일

let minusSix = -6
let alsoMinusSix = +minusSix
// equals -6


Compound Assignment Operators
: C와 동일

var a = 1
a += 2
// a = 3


Comparison Operators
: C와동일 ( '==', '!=', '>', '<', '>='. '<=' )
: '===', '!==' reference type 비교, 두 object가 같은 instance를 참조하고 있는지 체크한다.
: tuple 또한 비교가 가능하다. 단 tuple의 value들의 갯수가 같아야 하며, 각 value들이 비교가 가능한 type이어야 한다. 예를 들어 tuple의 value가 (Int, String)이면, 두 type 모두 비교가 가능한 type이기 때문에 비교 연산자를 사용할 수 있지만 value중 Bool이 있으면 비교가 불가능하다.
: 왼쪽에서부터 오른쪽으로 비교한다. 밑에 예제를 보고 어떻게 동작 하는지 알아보자.

(1, "zebra") < (2, "apple") 
// true because 1 is less than 2; "zebra" and "apple" are not compared

(3, "apple") < (3, "bird") 
// true because 3 is equal to 3, and "apple" is less than "bird"

(4, "dog")  == (4, "dog")
// true because 4 is equal to 4, and "dog" is equal to "dog"

(3, "zebra") > (4, "dog")
// false because 3 is less than 4, "zebra" and "dog" are not compared

* 스위프트에서 기본으로 제공하는 tuple에 대한 comparison operator는 tuple의 value들의 갯수를 6개까지로 한정한다. 7개 이상의 value들을 가지는 tuple을 비교하려면 스스로 operator를 만들어야 한다. 


Ternary Conditional Operator
: 삼항 연산자, C와 동일
: question ? answer1 : answer2

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight is equal to 90


Nil-Coalescing Operator
:(a ?? b), a는 optional type이다. a가 value를 가지고 있으면 a를 unwrap하고 a가 nil이면 b를 return한다.
: a와 b는 같은 type 이어야 한다.

let defaultColorName = "red"
var userDefinedColorName: String?
var colorNameToUse = userDefinedColorName ?? defaultColorName
// colorNameToUse = "red"


Range Operators
: 스위프트는 2개의 range Operators를 제공한다.

Closed Range Operator
: 'a...b' 는 a와 b를 포함하는 범위를 뜻한다. a는 b보다 크지 않아야 한다.

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

Half-Open Range Operator
: 'a..<b' a는 포함하되, b는 포함하지 않는 범위를 뜻한다. a는 b보다 크지 않아야 한다.
이 operator는 array같은 zero-based 리스트를 다룰때 유용하다. 

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called" \(names[i])")
}


Logical Operators
: 세개의 Logical Operators를 제공한다. C와 동일.
: Logical NOT (!a)
: Logical AND (a && b)
: Logical OR (a || b)


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

[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
[Swift 4.0] Strings and Characters  (0) 2017.01.22
[Swift 4.0] The Basics  (0) 2017.01.13
Posted by 홍성곤
,

[Swift 4.0] The Basics

IOS/Swift 2017. 1. 13. 19:08

Introduction
: 스위프트는 우리에게 이미 익숙한 Int, Double, Float, Bool, String, Array, Set, Dictionary등의 타입을 제공하고, 조금은 생소한 Tuple, optional type이라는 개념을 제공한다.
Tuple은 값들의 grouping 이라는 개념으로 이해하면 된다. 여러개의 값 뭉치를 하나의 타입으로 만들 수 있다는 의미이다.
optional Type은 원어로 "there is a value, and it equals x" or "there isn't a value at all"로 표현한다. 자세한 의미는 나중에 알아보기로 하고, 중요한 점은 스위프트의 powerful한 feature들의 구현 중심에는 optional type이 존재한다는 것이다.
스위프트는 type-safe 언어이다. 이것은 우리가 앱개발 도중에 가능한한 빨리 에러를 발견하고 고칠수 있게 만들어준다.


Constants and Variables
: 말 그대로 Constants는 상수, 한번 값을 set하면 변경할 수 없다. Variable은 값을 set 했더라도 변경할 수 있다.

Declaring Constants and Variables
: 변수는 var, 상수는 let 키워드를 사용해서 선언한다.

let maximumNumberOfLoginAttempts = 10;
var currentLoginAttempt = 0;

Type Annotations
: 변수, 상수의 타입을 명시한다.

var welcomeMessage: String
var red, green, blue: Double

Naming Constants and Variables
: 변수, 상수는 거의 모든 문자를 담을 수 있다.(Unicode 문자들 포함)

Printing Constants and Variables
: 변수, 상수를 Print 할 수 있다.
: print(_: separator: terminator:)
: separator, terminator 파라미터는 default값을 가지고 있다. print() 호출시 생략한다면 terminator는 빈 문자열이 들어가고 terminator에는 line break가 들어간다.

let friendlyWelcome = "Bonjour!"
print("The Current value of friendlyWelcome is \(friendlyWelcome)")


Integers
: 스위프트는 singed, unsigned interger를 8, 16, 32, 64 bit 형태로 제공한다.
: UInt8, UInt16,  UInt32, UInt64, Int8, Int16, Int32, Int64

Int 
: 대부분의 경우에는 Integer의 특정크기를 지정해줄 필요가 없다. 그래서 보통 Int 자료형을 사용한다.
: Int는 32bit 플랫폼에서는 32bit, 64bit 플랫폼에서는 64bit크기를 갖는다.

UInt
: UInt도 Int와 마찬가지로 32bit 플랫폼에서는 32bit, 64bit 플랫폼에서는 64bit크기를 갖는다.

Floating-Point Numbers
- Double : 64비트 실수형 타입
- Float : 32비트 실수형 타입


Type Safety and Type Inference
: 스위프트는 type safe 언어이기 때문에 type check를 컴파일 타입에 실행한다. 그러나 모든 상수, 변수에 타입을 명시할 필요는 없다. 타입을 명시하지 않아도 컴파일러가 타입을 추론하기 때문이다.

let meaningOfLife = 42;
// meaningOfLife상수에 타입을 명시하지 않아도 컴파일러는 Int형 타입으로 추론하여 컴파일 한다.

let pi = 3.14159
// Double형으로 추론
let anotherPi = 3 + 0.14159
// Double형으로 추론, 3에 대한 명시적인 타입선언이 없기때문에 두 변수의 합에 대한 타입은 Double이 더 적절하다.


Numeric Literals

let decimalInteger = 17
let binaryInteger = 0b10001
let octalInteger = 0o21
let hexadecimalInteger = 0x11
// 위 내게 상수 모두 17을 뜻한다.

실수는 십진수, 또는 16진수로 표현할 수 있다. 십진수 실수는 exponent 값을 포함할 수 있으며, 16진수는 exponent값을 포함해야 한다.

십진수 exponent
- 1.25e2 : 1.25 * 10^2 -> 125.0
- 1.25e-2 : 1.25 * 10^-2 -> 0.0125

16진수 exponent
- 0xFp2 : 15 * 2^2 -> 60
- 0xFp-2 : 15 * 2^-2 -> 3.75
- 0xFp0 : 15 * 2^0 -> 15

Numeric literal은 가독성을 높이기 위한 포맷팅이 존재한다.
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1


Numeric Type Conversion
:코드에서 정수를 다룰때 특별한 경우를 제외하고는 Int 타입을 쓰기 바란다. 그 이유는 CPU가 정수 계산을 할 때 피연산자 들을 모두 Int형으로 변환한 후 계산하기 때문이다. 그리고 우리가 명시적인 약속으로 Int형을 Default 정수형 타입이라고 정해놓으면 불필요한 type casting이 줄어들게 될것이다. 

Integer Conversion

let twoThousand: UInt16 = 2000
let one: UInt8  = 1
let twoThousandAndOne = twoThousand + UInt16(one)

SomeType(ofInitialValue)은 SwiftType으로 type casting하는 가장 일반적인 방법이다.

Integer and Floating-Point Conversion
: 정수와 실수간의 type casting은 명시적으로 이루어져야 된다.

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi = 3.14159

let integerPi = Int(pi)
let minusValue = -3.9
let minusInt = Int(minusValue)
// integerPi = 3
// minusInt = -3


Type Aliases

typealias AudioSample = Uint16
var maxAmplitudeFound = AudioSample.min


Booleans

C의 Bool과 달리 0이 아닌값으로 Booleans 값을 대체할 수 없다.

let i = 1

if (i)
{


// 컴파일 에러 발생.


Tuples
: Tuple은 여러개의 값을 grouping한 자료형이다.
: 두개의 자료형이 같을 필요없다.

let http404Error = (404, "Not Found")
let (statusCode, statusMessage) = http404Error

print("The status code is \(statusCode)")
print(The status message is \(statusMessage)")
// Prints "The status code is 404"
// Prints "The status message is Not Found"

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// Prints "The status code is 404"

print("The status code is \(http404Error.0)")
//Prints "The status code is 404"
print("The status message is \(http404Error.1)")
//Prints "The status message is Not Found"

let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")
print("The status message is \(http200Status.description)")
// Prints "The status code is 200"
// Prints "The status message is OK"


Optionals
: Optional은 상수, 변수의 상태라고 보면 된다. 두가지 상태 중 하나에 속해있는것인데, 하나는 값을 가지고 있고 그것은 unwrap할 수 있는 상태이고, 나머지 하나는 값이 없는상태이다.

: Optional은 C, C++에는 존재하지 않는 개념이다. 그나마 가까운 개념은 Objective-C의 nil이다. nil의 의미는 object의 invalid한 상태를 뜻한다. 그러나 nil은 object에만 적용되는 개념이다. Objective-C는 structure, basic C type, enum 타입 등이 invalid한 상태이면 NSNotFound 같은 special value를 return 한다. 이를 제대로 처리하기 위해서는 method 호출자가 NSNotFound와 같은 special value에 대해 대응이 가능한 상태여야 한다. 하지만 스위프트에서는 걱정할 필요 없다. Optional은 object뿐 아니라 다른 타입들 모두에게 적용되는 개념이기 때문이다.

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

Int(String) 생성자에 넘겨지는 문자열이 "abc"였다면 Int형 타입으로 올바르게 변환이 되지 않을 것이다. 그래서 Int(String)은 항상 optional Int(Int?)를 반환한다. 이것이 뜻하는 바는 올바른 Int값이 들어있을 수도 있고, valid한 값을 안가지고 있을수도 있다는 것이다.

nil
: optional type 변수에 값이 없는상태를 표현하기 위해서 nil을 대입할 수 있다.
: optional type 변수 선언시 initial value를 대입하지 않으면 자동으로 nil이 대입된다.

* Objective-C의 nil과 스위프트의 nil은 다르다. Objective-C의 nil은 객체의 포인터 타입으로 포인터가 아무것도 가리키는 것이 없는 상태를 나타내는 것이고, 스위프트의 nil은 포인터 타입이 아니고 모든 객체에 적용되는 개념으로, 해당 변수가 값을 가지고 있지 않은 상태를 나타내는 것이다.

If Statements and Forced Unwrapping
: 스위프트에서는 If문을 optional type 변수가 nil인지 valid값을 가지고 있는지 판별하는 용도로 사용할 수 있다.

if convertedNumber != nil {
    print("convertedNumber contains some integer value")
}

위 if문 안으로 들어갔다는 것은 optional type 변수 convertedNumber가 valid한 값을 가지고 있다는 것이므로 해당 변수뒤에 "!"을 붙여서 optional이라는 껍질을 벗겨냄으로서 정상적으로 해당 변수를 사용할 수 있게 만든다. 이를 unwrap이라고 한다.

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}

* 사실 "!"을 사용하여 unwrap하는 것은 사용하지 않는것이 좋다. optional type이 nil인 상태에서 "!"으로 unwrap을 시도하려고 하면 runtime error가 발생한다. "!"을 사용해서 unwrap 한다는 것은 optional type 변수가 valid한 값을 가지고 있을거라고 확신한다는 것인데, 프로그래밍에 있어서 100프로 확신하는 것은 굉장히 위험한 발상이다.

Optional Binding
: Optional Biding이란, "!"으로 optional type 변수를 강제 unwrap 하지 않고 안전하게 optional type변수를 unwrap 하기위한 기법이다. 
: if, while 구문을 사용하여 Optional Binding할 수 있다.

if let actualNumber = Int(possibleNumber) {
    print("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
    print("\(possibleNumber) could not be converted to an integer")
}

possibleNumber가 올바르게 type casting이 된다면 Int(String)이 return 하는 Int?을 unwrapping하여 actualNumber 상수의 값을 대입한 후 if문에 진입하고, 그렇지 않으면 else문을 실행하게 된다.

단일 if문 안에 복수개의 optional binding과 Boolean condition 체크를 할 수 있다. 복수개의 optional binding들 중 어느 하나라도 nil을 가지고 있거나 Boolean condition이 어느 하나라도 false일 경우에는 if statement는 false가 된다. 

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// if문의 모든 조건을 만족하므로 if문에 진입.


Error Handling
: 다른 언어의 try, catch문을 통한 Error handling이랑 크게 다를바가 없다.
: error가 발생되면 현재 error가 발생한 scope 한단계 밖으로 error가 전파되고 handling 메서드를 찾지 못하면 계속 한단계 밖 scope로 전파된다. Top scope까지 전파되었는데 handling 메서드를 찾지 못하면 runtime error가 발생한다. 

func makeASandwich() throws {
    //....
}

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

makeASandwich() 실행도중 SandwichError.outOfCleanDishes 에러가 발생하면 washDishes()를 실행시키고 SandwichError.missingIngredients 에러가 발생하면 buyGroceries(let ingredients)를 실행시킨다.


Assertions and Preconditions
: 코드 실행 중 특정 조건이 만족 안되면 더 이상 코드 실행을 할 수 없을 때가 있다. 이는 8~90프로 버그 상황인데 이 버그상황을 디버그 환경에서 조금 더 효율적으로 체크할 수 있는 방법이 있다. 
첫번째는 Assertions이고 두번째는 Preconditions이다. 사용방법은 비슷하고 해당 조건을 만족하지 못했을 경우 앱이 종료된다. 다만, 차이점이 하나 있다. Assertions은 debug 빌드일 경우에만 동작하고, precondition은 debug, production 빌드 둘다 동작한다.

* Assertions, precondition 모두 optimization 레벨 None 으로compile되면 작동되지 않는다.(이 Option은 app target이 Release configuration이면 자동으로 YES 세팅된다.) 다만, fatalError(_:file:line:)의 경우는 optimization 레벨의 상관없이 항상 동작한다.


Debugging with Assertions

let age = -3
assert(age, "A person's age cannot be less than zero")

위 코드가 debug 환경에서 실행중이라면 assert문에서 assert 에러 메시지가 출력되고 앱을 종료될 것이다. 에러 메시지는 생략할 수 있다. 

if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

이 처럼 실패의 경우만 체크하기 위해  'assertionFailure'를 사용할 수 있다.

Enforcing Preconditions

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

위 코드는 subscript 안에서의 사용예를 보인것이다. index가 0보다 작거나 같으면 앱이 종료될 것이다.


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

[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
[Swift 4.0] Strings and Characters  (0) 2017.01.22
[Swift 4.0] Basic Operators  (0) 2017.01.16
Posted by 홍성곤
,

객체 캡쳐하기
: 객체는 블록에서 캡쳐되면 블록 구조체의 멤버 변수로 id __strong object;형태로 선언하고 객체를 소유한다.
원래 C구조체는 __strong 속성이 부여된 멤버 변수를 가질 수 없다. 그 이유는 컴파일러가 구조체를 언제 초기화하고 언제 폐기하는지의 적절한 메모리 관리 방식을 가지고 있지 않기 때문이다. 그러나, Block 구조체는 Objective-C객체로 취급되기 때문에 컴파일러가 적절한 메모리 관리 방식을 가지고 있다. 그래서 Block 구조체는 멤버 변수로 __strong 속성이 부여된 멤버변수를 가질 수 있다.

다만, __weak 속성의 객체가 캡쳐될 경우 블록 구조체는 id _weak object; 형태 멤버변수를 선언하고 객체를 가리킨다. 밖에서 객체가 release되면 블록 구조체의 id _weak object는 nil이 된다. 

__block id __weak object 객체를 캡쳐하면 __block 구조체 변수는 가리키고 블록이 힙으로 복사될 때 __block변수는 소유를 하지만 __weak 객체가 block 구조체의 할당될 때 __weak속성으로 할당되기 때문에 객체가 release되면 nil이 할당된다.

블록 순환 참조
: __strong 속성의 객체형 지역 변수가 블록에서 사용되면 블록은 해당 객체의 소유권을 갖는다. 하지만 이 때문에 순환 참조가 발생할 수 있다. 

@interface MyObject : NSObject
{
    blk_t blk;
}
@end

@implementation MyObject

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        blk =^{ NSLog(@"self = %@", self); };
    }

    return self;    

}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

int main()
{
    {
        id o = [[MyObject alloc] init];
    }   
}
이 경우 순환참조가 발생하여 "dealloc" 메세지는 절대 발생하지 않는다.
blk가 self를 strongly하게 캡쳐하면서 MyObject 인스턴스의 retainCount는 2가 되고, MyObject 인스턴스 지역 변수의 범위를 벗어난다고 해도 retainCount는 1이 되어 dealloc되지 않는다.

self를 캡쳐할 때,

id __weak sWeakSelf = self;

if (self)
{
    blk =^{ NSLog(@"sWeakSelf = %@", sWeakSelf); };
}

이렇게 캡쳐해야 순환참조 발생을 예방할 수 있다. 

그러나, self를 블록에서 사용하지 않더라도 순환 참조가 발생할 수 있다. 다음 코드를 보자.

@interface MyObject : NSObject
{
    blk_t blk;
    id obj;
}
@end

@implementation MyObject

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        blk =^{ NSLog(@"
obj = %@", obj); };
    }

    return self;    

}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

이 경우도 순환 참조가 발생한다. init메서드에서 obj란 self->obj이기 때문에 self가 캡쳐된다.
순환 참조를 피하기 위해서 블록안에서 self 캡쳐를 못하게 하는 방법이 있다.

id sObj = obj;

if (self)
{
    blk =^{ NSLog(@"sO
bj = %@", sObj); };
}

이러면 블록 안에 코드에서는 self가 들어가지 않는다. 그러므로 self를 캡쳐하지 않는다.

* ARC를 사용하지 않을 때의 블록 순환 참조에 대한 방법은 생략하겠다. 


Copy/Release

블록은 C 언어에서 확장된 것이기 때문에 C언어 에서도 블록을 사용할 수 있다. 이 경우 Objective-C의 copy, release메서드 대신에 Block_copy와 Block_release메서드를 사용한다. Objective-C의 런타임은 스택 메모리 영역에 있는 블록을 retain할 때, Block_copy메서드를 사용한다.(스택 메모리 영역의 있는 객체에 retain 메세지를 보내봤자 아무일도 일어나지 않기 때문이다.) Block_copy메서드는 스택 메모리 영역에 있는 객체를 힙 영역으로 복사하고 retainCount를 하나 증가 시킨다.
C언어에서 Block을 retain, release 할 때, Block_copy, Block_release 메서드를 사용하기 바란다.

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

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

쓰기 가능한 변수들

: 지역 변수는 블록안에서 값을 쓰지 못한다. 그러나 블록 안에서 쓰기가 가능한 변수들이 있다.

정적 변수나 전역 변수
- 정적 변수(정적 지역 변수)
- 정적 전역 변수
- 전역 변수

위 3가지 종류의 변수들은 블록안에서 값을 쓸 수 있다. 다만 블록 내부에서 처리하는 방법이 서로 다르다. 일단 블록이 C 함수로 변환될 때 블록을 선언한 메서드 내부에서 블록함수가 선언되지 않는다. 즉 블록을 선언한 메서드 내부에 선언된 정적 변수는 원칙적으로 블록함수에서 접근이 불가능 하다.
하지만 이러한 한계를 극복하기 위해서 블록이 선언될 때 블록 구조체 멤버 변수로 정적 지역 변수의 포인터를 할당한다. 블록 함수에서 블록 구조체 내부에 저장된 정적 지역변수의 포인터를 통해 값을 조작한다.

정적 전역 변수와 전역 변수는 블록 구조체에 따로 저장하지 않아도 접근이 가능하기 때문에 그대로 사용한다.

__block 지시어

예제 코드)

__block int val = 10;

void (^blk)(void) = ^{ val = 1; };

------> C, C++ 로 변환

struct __Block_byref_val_0
{
    void *__isa;
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    int val;
}

struct __main_block_impl_0
{
    struct __block_impl impl;
    struct __main_block_desc_0 *Desk;
    __Block_byref_val_0 *val;
}
// 생성자는 생략했다.

static void __main_block_func_0(struct __main_block_impl_0 *__self)
{
    __Block_byref_val_0 *val = __cself->val;
    
    (val -> _forwarding->val) = 1;
}

static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src)
{
    _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_impl_0 *src)
{
    _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

static struct __main_block_desc_0
{
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0 *);
    void (*dispose)(struct __main_block_impl_0 *);
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};

int main()
{
    __Block_byref_val_0 val = {
        0,
        &val,
        0,
        sizeof(__Block_byref_val_0),
        10
    };

    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val,     0x22000000);

    return 0;
}

__block 변수 val이 구조체로 바뀌었고 멤버변수로 10을 가진다. 그리고 블록을 위한 구조체의 멤버로 __block 변수 구조체의 포인터가 전달된다. 이는 여러개의 블록에서 하나의 __block 변수를 공유하기 위함이다.

void (^blk0)(void) = ^{ val = 0; };
void (^blk1)(void) = ^{ val = 1; };

-------> C, C++ 로 변환

__Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 };

blk 0 = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000 );
blk 1 = &__main_block_impl_1( __main_block_func_1, &__main_block_desc_1_DATA, &val, 0x22000000 );

위 두개의 블록은 __Block_byref_val_0 구조체의 동일한 인스턴스 val에 대한 포인터를 멤버변수로 가져서 __block 변수를 공유할 수 있는것이다.

블록에서 메모리 세그먼트
: 이전 챕터에서 블록은 Objective-C 객체라는 것을 배웠다. 그리고 블록의 클래스는 _NSConcreteStackBlock 이라고 배웠다. 다만, 블록이 저장되는 메모리 위치에 따라서 블록의 클래스가 달라질 수 있다.
- NSConcreteStackBlock : 스택 메모리에 저장되는 블록을 뜻한다.
- NSConcreteGlobalBlock : 데이터 섹션 메모리에 저장되는 블록을 뜻한다.
- NSConcreteMallocBlock : 힙 메모리에 저장되는 블록을 뜻한다.


NSConcreteGlobalBlock 클래스 객체 형태의 블록
: 글로벌 변수로 사용하는 블록 리터널이 있다면, 그 블록은 _NSConcreteGlobalBlock 클래스 객체로 생성된다.

void (^blk)(void) = ^{ printf("Global Block"); };

int main()
{
    ,,,,

이 코드에서 블록 리터럴은 _NSConcreteGlobalBlock 클래스 객체로 생성된다. 즉, 블록은 데이터 섹션에 저장되고 하나의 애플리케이션에서 하나의 인스턴스만 생성된다.  

다음은 지역변수로 블록 리터럴이 선언되었는데도, 데이터 섹션에 저장되는 경우를 살펴보자.
블록 인스턴스는 지역변수를 캡처하는 경우에만 변경된다. 예를들어 보자.
typedef int (^blk_t)(int);

for (int rate = 0; rate < 10; ++rate)
{
    blk_t blk = ^(int count){return rate * count; };
}
매 for문 반복시점마다 지역변수를 캡쳐하기 때문에 다른 인스턴스가 생성된다.

for (int rate = 0; rate < 10; ++rate)
{
    blk_t blk = ^(int count){return count; };
}
위 블록은 어떠한 지역변수도 캡쳐하지 않았기 때문에 데이터 섹션 메모리에 저장된다.

즉, 전역 변수로 블록이 선언되는 경우와 블록에서 지역변수를 캡쳐하지 않은경우에는 _NSConcreteGlobalBlock 클래스 객체가 되고, 나머지 방법으로 생성되면  _NSConcreteStackBlock 클래스 객체가 된다.

그러면 언제 _NSConcreteMallocBlock 클래스가 사용되는가? 
전역 변수처럼 데이터 섹션 메모리에 저장되는 블록은 변수 영역의 밖에서도 포인터를 이용해 안전하게 접근할 수 있다. 반면, 스택에 저장되어 있는 블록들은 블록의 영역을 벗어나면 폐기된다. 이 문제를 해결하기 위해 블록은 블록이나 __block 변수를 스택에서 힙으로 복사하는 기능을 제공한다.
블록이 힙으로 복사될 때, 블록 구조체의 isa 멤버 변수는 _NSConcreteMallocBlock으로 덥혀 써지고 힙 메모리 영역에 복사된다. 


블록을 힙 메모리로 복사하기
: ARC를 활성화하면 대부분의 경우 컴파일러가 자동으로 필요한 부분은 발견하고 블록을 스택에서 힙으로 복사한다. 다만, 컴파일러가 찾아내지 못하는 경우에는 블록을 수동으로 스택에서 힙으로 복사해야 한다. 그 작업을 수행하려면 copy 인스턴스 메서드를 사용하면 된다.


보통 블록을 메서드나 함수의 인자값으로 전달할 때 컴파일러가 찾아내지 못하여 수동으로 복사해야 한다. 
- (id)getBlockArray
{
    int val = 10;
    
    return [[NSArray alloc] initWithObjects:^{ NSLog(@"blk0:%d", val); },
                                                                   ^{ NSLog(@"blk1:%d", val); } ];
}

id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();

위 코드는 blk();가 실행되면서 강제 종료된다. getBlockArray 함수에서 블록이 생성될때 블록은 스택 메모리 영역에 생성이 되기 때문에, NSArray의 initWithObjects메서드 안에서 매개변수로 넘어온 블록들을 retain하려고 해도 retain할 수 없기 때문이다. 그래서 매개변수로 블록을 넘길때는 명시적으로 블록을 힙 메모리 영역으로 복사해주는 작업이 필요하다. 이 작업은 두가지 방법을 통해 할 수 있다.

1) copy 메서드 사용.
- (id)getBlockArray
{
    int val = 10;
    return [[NSArray alloc] initWithObjects:[^{ NSLog(@"blk0:%d", val)]; } copy],
                                                                   [^{ NSLog(@"blk1:%d", val); } copy] ];
}
// 블록에 copy 메서드를 사용하면 블록을 힙으로 복사하고 retainCount를 하나 늘린다. 그리고 매개변수로 넘어갈때 autorelease를 시켜줘서 initWithObjects 메서드 안에서 retain해도 retainCount는 1이 된다.

2) 지역변수로 retain한뒤 매개변수로 넘기기. 
- (id)getBlockArray
{
    int val = 10;
    typedef void (^blk_t)(void);
    blk_t blk1 = ^{ NSLog(@"blk1:%d", val)]; };
    blk_t blk2 = ^{ NSLog(@"blk2:%d", val); };

    return [[NSArray alloc] initWithObjects:blk1, blk2];
}
// 블록이 생성될 때는 스택 메모리 영역에 생성되지만 blk1, blk2에 대입될 때 ARC환경이기 때문에 block이 자동으로 retain된다. 블록을 retain하는 과정에서 스택 메모리 영역의 블록을 힙 영역으로 옮기고 그 뒤 retainCount를 하나 증가 시킨다. 그리고 initWithObjects 메서드의 매개변수로 전달될 때 autorelease가 된다. 결과적으로는 1)과 똑같은 동작을 하게 된다.


단 세가지의 경우는 컴파일러가 자동으로 복사한다.
1) usingBlock이란 이름을 포함하는 Cocoa 프레임워크 메서드
2) Grand Central Dispatch API
3) 함수, 메서드가 블록을 리턴하는 경우

위 세가지의 경우는 신경쓰지 않아도 컴파일러가 알아서 블록을 힙 메모리 영역으로 복사한다.

표) 블록복사

 블록 클래스

복사되는 곳 

복사 동작 방식 

_NSConcreteStackBlock 

스택 

스택에서 힙으로 복사 

_NSConcreteGlobalBlock 

데이터 섹션 

아무 변화 없음 

_NSConcreteMallocBlock 

힙 

객체의 참조 카운트 증가 

*데이터 섹션에 있는 블록을 복사해도 아무일도 생기지 않는다. 그리고 설상 힙 영역에 있는 블록을 복사한다고 해도 나쁜영향을 끼치치 않는다.

blk = [[[blk copy] copy] copy]

---->> 변환된다.

{
    blk_t tmp = [blk copy];
    blk = tmp;
}

{
    blk_t tmp = [blk copy];
    blk = tmp;
}

{
    blk_t tmp = [blk copy];
    blk = tmp;
}

이렇게 여러번 반복되도 결국 retainCount는 1이 된다. 


__block 변수의 메모리 세그먼트
: 블록 안에서 __block 변수를 사용하여 스택에서 힙으로 복사하면 __block 변수도 영향을 받는다.
__block 변수를 사용하는 블록이 스택에서 힙으로 복사 될 때 __block 변수들도 같이 스택에서 힙으로 복사된다. __block 변수가 __strong 속성의 객체라면 __block구조체의 멤버변수로 strongly하게 할당이 되고, __block변수가 블록에 캡쳐될 때도 strongly하게 할당된다. 


__ forwarding
: __block 변수 구조체의 __forwarding은 __block 변수가 스택 메모리 영역에 생성 될 때는 스택의 생성된 __block변수 구조체를 가리키고 있지만 __bock 변수가 힙영역으로 복사되면 스택 메모리 영역에 있는 __block 변수 구조체의 __forwarding은 힙 영역의 자신의 복사본인 __block 변수를 가리키고 있다. 
블록에서 또는 블록 밖에서 __block변수의 값을 사용할때는 무조건 __forwarding 포인터를 참조해서 값을 가져오기 때문에 __block변수가 스택에서 힙으로 복사되어 스택, 힙 두개의 영역에 중복 존재하더라도 사용하는 곳에서는 늘 똑같은 변수를 참조해서 사용하게 된다.


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

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

[블록 구현]
여기서 설명되는 코드는 ARC 환경이라고 가정한다.

블록의 내부
: 블록구문은 뭔가 특별하다고 생각할지 모르지만, 컴파일러는 블록을 평범한 C 언어 코드로 변환한 다음 똑같이 컴파일한다.

소스코드 변환하기
: 블록 구문을 포함하는 소스코드는 -rewrite-objc 컴파일러 옵션을 사용해서 표준 c++ 소스코드로 변환할 수 있다. 변환된 소스코드가 C++로 쓰여졌기는 하지만, 구조체를 위해 생성자를 사용할 때를 제외하고는 거의 C로 쓰여졌다.
(clang -rewrite-objc file_name_of_the_source_code)

int main()
{
    void (^blk)(void) = ^{ printf("Block\n"); };    
    blk();
    return 0;
}

---> C++소스로 변환

struct __block_impl 
{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 //실제 블록 구조체 라고 보면 됨.
{
    struct __block_impl impl;
    struct __main_block_desc_0 *Desk;
    __main_block_impl_0 (void *fp, struct __main_block_desc_0 *desk, int flags = 0)
    {
        impl.isa = &NSConcreteStatckBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

static struct __main_block_desc_0
{
    unsigned long reserved; //여유 공간
    unsigned long Block_size; //블록 크기
} __main_block_desc_0_DATA =  {
        0,
        sizeof(struct __main_block_impl_0)
    };

int main()
{
    void (*blk)(void) = 
        (void) (*)(void)&__main_block_impl_0(
            (void *)__main_block_func_), &__main_block_desc_0_DATA);

    ((void *)(struct __block_impl *)
        (struct __block_impl *)blk) -> FuncPtr)((struct __block_impl *) blk);

    return 0;
}

엄청나게 코드가 늘어났다! -_- 추후에 하나하나 자세히 들여다 볼것이다. 일단 선행되는 개념부터 알아보겠다.


Objective-C 에서 self

MyObject의 인스턴스 메서드라고 하자.
- (void)method:(int)arg
{
    NSLog(@"%p %d\n", self, arg);
}

------> 컴파일러가 C함수로 변환한다.

void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
{
    NSLog(@"%p %d\n", self, arg);
}

이제 메서드를 호출하는 경우를 보자.

MyObjecdt *obj = [[MyObject alloc] init];
[obj method:10];

------> 
컴파일러가 C함수로 변환한다.

MyObject *obj = objc_msgSend( objc_getClass("MyObject"), self_registerName("alloc"));
obj = objc_msgSend(obj, sel_registerName("init"));
objc_msgSend(obj, sel_registerName("method:", 10));

세번째 줄에서 MyObject의 인스턴스 메서드로 "mehod"라는 이름으로 등록되 있는 함수 포인터를 찾아 호출한다. 그러면 
void _I_MyObject_method_(struct MyObject *self, SEL _smd, int arg) 함수가 호출될 것이다. 이때 첫번째 인자로 넘어온 obj를 함수의 첫번째 인자 self에 대입한다.

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}
를 보면 __cself에 블록 구조체인 
struct __main_block_impl_0의 포인터를 인자로 받고 있다. 이 함수에서는 __cself가 사용되지 않았지만 블록에 변수가 캡쳐되어 블록안에서 사용된다면 캡쳐된 변수를 __cself를 통해 가져올 것이다. 이해가 안된다면 밑에 설명을 계속 보자.

생성자를 제외한 블록 구조체이다.
struct __main_block_impl_0
{
    struct __block_impl impl;
    struct __main_block_desc_0 *Desk;
};
블록 구조체는 블록에 대한 정보를 가지고 있는 2개의 구조체를 멤버변수로 가진다.

main 함수에서 호출되는 부분을 살펴보자.

void (*blk)(void) = 
        (void) (*)(void)&__main_block_impl_0(
            (void *)__main_block_func_), &__main_block_desc_0_DATA);

----> 형변환 코드를 제외해 보자.

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

코드를 보면 블록 구조체가 적절한 값(함수 포인터, desc 구조체)으로 초기화 되어 변수에 할당 되었다.

이제 __block_impl 부분을 블록 구조체에 넣어서 확장해보면 다음과 같이 쓸 수 있다.

struct __main_block_impl_0
{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0 *Desk;
}

이 구조체는 다음과 같이 초기화 되었다.

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

그리고 실제 블록이 호출되는 부분을 살펴보자.

((void *)(struct __block_impl *)
        (struct __block_impl *)blk) -> impl.FuncPtr)((struct __block_impl *) blk);

-----> 형 변환 코드를 제거해보자.

(*blk->impl.FuncPtr
)(blk);

블록 구조체의 저장되어 있는 함수포인터를 호출 하면서 자신(블록 구조체)을 인자로 넘겨주고 있다. 이는 __cself로 전달된다.

결국 Block의 구조를 보면 Objective-C 객체의 구조랑 똑같다. 
struct __main_block_impl_0은 우리가 objective-C객체의 구조체라고 알고있는 struct objc_object의 확장형이라고 보면된다. 즉, 블록 역시 Objective-C 객체라고 볼 수 있다. 블록 구조체의 isa값 _NSConcreteStackBlock 역시 Objective-C 클래스 구조체라고 알려져있는 class_t 구조체의 인스턴스를 가리킨다. 즉, _NSConcreteStackBlock은 블록의 클래스라고 보면되고, 블록을 Objective-C 객체로 인식하고 처리하기 위한 정보를 담고 있다.


지역 변수 캡쳐
: 블록이 생성될 때, 블록의 매개변수로 지역변수가 전달되면 블록 구조체의 멤버변수가 생성되고 매개변수로 넘어온 값이 저장된다. 즉, 생성된 시점에 값이 블록 구조체의 멤버변수로 저장되기 때문에 블록 생성 이후에 값이 변경된다 하더라도 블록 구조체의 멤버변수의 값이 바뀌지 않는다.


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

Block의 모든것(4)  (0) 2017.01.07
Block의 모든것(3)  (0) 2017.01.02
Block의 모든것(1)  (0) 2017.01.02
ARC 규칙  (0) 2017.01.01
GCD(Grand Central Dispatch)(2)  (0) 2016.12.19
Posted by 홍성곤
,

개요

: C 언어에 새로 추가된 확장 기능이다.
: "자동(지역) 변수와 함께 동작하는 익명 함수"라고 할 수 있다.


블록 변수 선언과 사용
int (^blk)(int); 
: 지역 변수, 함수 인자, 정적 지역 변수, 정적 전역 변수, 전역 변수로 선언이 가능하다.
typedef int (^blk_t)(int);로 복잡성을 피할 수 있다. 이렇게 하면 blk_t 타입으로 변수를 선언할 수 있다.
: 블록 포인터로도 사용이 가능하다.
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1};
blk_t *blkptr = &blk;
(*blkptr)(10);


자동 변수 캡처

int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{ printf(fmt, val); };

val = 2;
fmt = "These values were changed. val = %d\n";
blk();

// 결과: "val = 10"

fmt, val은 블록 리터럴이 선언될 때 이미 캡쳐되어 있어서, 블록 리터럴 선언 이후에 값이 변경되더라도 블록 리터럴 내부의 값은 절대 영향을 받지 않는다.


__block 지시어 
: __block 지시어를 사용하면 지역변수를 캡처하지 않고 블록 리터럴 내부에서 수정도 가능하다.

int val = 0;
void (^blk)(void) = ^{val = 1;};
// 컴파일 에러 발생! 블록 리터럴 밖에서 선언한 지역 변수에 값을 할당했기 때문!

__block int val = 0; 으로 코드를 변경해야 한다.


캡처된 자동 변수

그렇다면 이 코드도 컴파일 에러가 발생할까? 

id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
    [array addObject:@2];
};
// 컴파일 에러가 발생하지 않는다. 이 경우 캡쳐된 것은 NSMutableArray 클래스 객체에 대한 구조체 인스턴스의 포인터이다.

id array = [[NSMutableArray alloc] init];
void (^blk)(void)=^{
    array = [[NSMutableArray alloc] init];
};
// 컴파일 에러 발생! 

C 배열은 블록에 의해 자동으로 캡쳐될 수 없다.
const char text[] = "hello";
void (^blk)(void) = ^{
    printf("%c\n", text[2]);
};
// 컴파일 에러 발생. 

그러므로 포인터를 사용해야 한다.
const char *text = "hello";
void (^blk)(void) = ^{
    printf("%c\n", text[2]);
}
// 정상동작 한다.

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

Block의 모든것(3)  (0) 2017.01.02
Block의 모든것(2)  (0) 2017.01.02
ARC 규칙  (0) 2017.01.01
GCD(Grand Central Dispatch)(2)  (0) 2016.12.19
GCD(Grand Central Dispatch)(1)  (0) 2016.12.19
Posted by 홍성곤
,

ARC 규칙

IOS/Objective-C 2017. 1. 1. 16:03

개요
: 요약하면 프로그래머가 더 이상 "retain", "release"를 호출할 필요가 없다는 것이다. 다음 조건에서 여러분의 소스코드는 자동으로 ARC가 활성화된 상태로 컴파일 된다.
- clang(LLVM 컴파일러) 3.0 이상
- 컴파일 옵션 "-fobjc-arc"
* xCode 4.2부터는 Clang이 기본 컴파일러이고, -fobjc-arc 옵션도 기본으로 켜져있다. 따라서, Xcde 4.2 이상에서 작업하면 추가설정이 필요없다.


1) 소유권 수식어
- Objective-C는 id나 각 객체 타입을 사용하여 객체의 데이터 형을 지정한다.

객체 데이터 형은 NSObject *와 같이 Objective-C 클래스의 포인터 형이다. id은 클래스 이름을 숨길 때 사용한다. id는 C언어에서 void*와 동일하다.
ARC에서 id와 객체 데이터형 변수는 다음 네 가지 소유권 수식어 중 하나를 가져야 한다.

- __strong
- __weak
- __unsafe_unretained
- __autoreleasing

*_weak vs __unsafe_unretained 수식어
: __unsafe_unretained는 이름이 암시하듯 전혀 안전하지 않다. 이 속성을 갖는 변수를 ARC의 자동 메모리 관리에서 제외되기 때문에 직접 변수를 관리해 주어햐 한다. 

id __weak obj1 = nil;

{
    id __strong obj0 = [[NSObject alloc] init];
    obj1 = obj0;
    NSLog(@"A: %@", obj1);
}

NSLog(@"B: %@", obj1);
// 결과
// A: <NSObject: 0x753e180>
// B: (null)
// B를 찍을때는 obj0 변수가 변수범위를 벗어나서 강한참조가 사라지면서 이 객체가 제거될 때, 약한참조도 제거되어 obj1에 자동으로 nil이 대입된다. 

만약 위 코드에서 id __unsafe_unretained obj1 = nil;가 사용되면 B를 찍을때 obj1에 nil이 대입되는 로직이 적용되지 않아서 B를 찍는시점에는 ojb1에는 허상 포인터가 들어가 있을 것이다. 이 허상포인터에 접근하면 크래쉬를 발생시킨다. 

id __unsafe_unretained obj = [[NSObject alloc] init];

__unsafe_unretained 변수는 __weak과 마찬가지로 객체의 소유권을 갖지 않고, 위의 예제에서 객체는 생성되자마자 릴리스 된다. 


2) 컴파일러가 알아서 __autoreleasing을 처리해준다.
- 객체를 생성하지 않고 얻어오려면 alloc/new/copy/mutableCopy 메서드 그룹이 아닌 다른 메서드를 사용해야 한다. 위 4가리 그룹 메서드가 아닌 메서드가 반환하는 객체는 자동으로 오토릴리스 풀에 등록시킨다. 예외적으로 init으로 시작하는 메서드는 반환값을 오토릴리스 풀에 등록시키지 않는다.

- __weak 속성을 갖는 변수가 사용되면 객체는 언제나 오토릴리스 풀에 등록된다. 
id __weak obj1 = obj0;
NSLog(@"class =%@", [obj1 class]);

위 소스코드는 아래와 동일하다.

id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

컴파일러가 첫번째 코드를 두번째 코드로 자동변환하는 이유는 __weak으로 수식되면 강한참조가 없기 때문에 어느 시점에서든 제거될 수 있기 때문이다. 만일 객체가 오토릴리스 풀에 등록되면 @autoreleasepool 블록이 끝나기 전에는 그 객체가 계속 존재하게 된다. 따라서 __weak 변수를 안전하게 사용하기 위해 객체가 자동으로 오토릴리스 풀에 등록된다.

- 더블 포인터 변수는 __autoreleasing 소유권 수식어를 갖는다.(id*, NSObject**)

NSError *error = nil;
NSError **pError = &error;
// 컴파일 오류를 발생시킨다. 객체 포인터를 대입하려면 두 변수의 소유권 수식어가 동일해야 한다.

컴파일 오류를 피하려면 다음과 같이 바꿔줘야 한다.
NSError *error = nil;
NSError *__strong *pError = &error;

코코아 프레임워크에는 수행결과를 인자로 반환하는 메서드들이 많이 있다.
performOperationWithError:(NSError **)error;
같은 메서드 들이다. ARC환경에서는 컴파일러가 다음과 같이 변환한다.
-> perfromOperationWithError:(NSError * __autoreleasing *)error;

NSError *error = nil;
BOOL result = [obj performOperationWithError:*error];
는 컴파일러가 어떻게 변환할까?

NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
로 변환한다.


3) 객체 형의 변수들은 C언어의 구조체 또는 공용체의 멤버가 될 수 없다.

struct Data
{
    NSMuttableArray *array;
}
// 컴파일 에러 발생.

심지어 LLVM 컴파일러 3.0에서는 C 언어 명세의 제한 때문에 C구조체의 생명주기를 관리할 방법이 없다. ARC에서는 컴파일러가 메모리를 관리하기 위해 객체의 생명주기를 알고 관리해야 한다. 만약 C구조체에 객체를 넣길 원한다면 객체를 "void *"로 캐스팅하거나 __unsafe_unretained 소유권 수식어를 사용해서 할 수 있다.

컴파일러는 __unsafe_unretained 소유권 수식어를 사용한 변수를 관리하지 않는다. 메모리 누수 또는 애플리케이션이 강제 종료되지 않게 하려면 소유권을 직접 관리해야 한다.


4) 'id' 그리고 'void *'는 명시적으로 형 변환되어야 한다.

non-ARC 환경에서 'id'에서 'void *'로의 형 변환은 다음과 같이 아무 문제없이 작동한다.
그리고 void*에서 다시 대입한 id 변수를 통해서 메서드 호출도 문제없다.
/* non-ARC */
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];

하지만 위 소스는 ARC 환경에서 컴파일 에러가 발생한다. ARC 환경에서는 id 또는 객체 타입들과 'void *' 사이에서 형 변환을 하기 위해서는 특별한 종류의 형 변환을 사용해야 한다.
단지 할당만 하기 위해서는 __bridge 형 변환을 사용할 수 있다.

- __bridge case
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

void *를 위한 __bridge 형 변환을 사용(void *p = (__bridge void *)obj;)하면 __unsafe_unretained 수식어 변수보다 더 위험하다. 객체의 소유권을 주의깊게 관리하지 않으면 댕글링 포인터 때문에 크래쉬가 난다.

__bridge_retained cast
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

위의 소스는 non-ARC 환경에서 다음과 같이 재작성할 수 있다.
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

__bridge_transfer cast
: 할당이 된 직후에 객체를 릴리즈하게 된다.

id obj = (__bridge_transfer id)p;

위 소스는 non-ARC 환경에서 다음과 같이 재작성할 수 있다.

id obj = (id)p;
[obj retain];
[(id)p release];
// obj 변수는 __strong으로 수식되었기 때문에 retain 되었다.

*Objective-C 객체와 Core Foundation 객체
- Core Foundation 객체는 Core Foundation 프레임워크에서 사용하는 객체로, 대부분 C 언어로 작성되었고 레퍼런스 카운트를 갖고있다. Core Foundation 프레임워크에서 사용하는 CFRetain, CFRelease 함수는 Objective-C에서 non-ARC 일때 사용하는 retain, release 메서드와 동일하다. Core Foundation 객체와 Objective-C 객체는 어느 프레임워크에서 객체를 생성하느냐에 따라 생성하는 방식이 다를 뿐 거의 같다. 일단 객체를 생성하고 나면 두 가지 프레임워크 모두에서 동일하게 사용할 수 있다. 예를 들어 Foundation 프레임워크 API로 생성한 객체여도 Core Foundation 프레임워크 API로 릴리즈할 수 있으며, 그 반대로 가능하다.

엄밀히 얘기해서 Core Foundation 객체와 Objective-C 객체는 동일한 객체다. 따라서 ARC 환경이 아닌 경우에 C 스타일 형 변환 방식으로 객체를 서로 변환할 수 있다. 이런 객체 형 변환 방식은 CPU 비용이 발생하지 않기 때문에 Toll-Free Bridge라고 부른다. 

ARC를 활성화한 환경에서 Objective-C와 Core Foundation 객체 사이의 Toll-Free Bridge 변환을 위해 다음과 같은 함수들이 제공된다.

CFTypeRef CFBridgingRetain(id X)
{
    return (__bridge_retained CFTypeRef)X;
}

id CFBridgingRelease(CFTypeRef X)
{
    return (__bridge_transfer id)X;
}

CFBridgingRetain Function
CFMutableArrayRef cfObject = NULL;

{
    id obj = [[NSMutableArray alloc] init];
    cfObject = CFBridgingRetain(obj);
    CFShow(cfObject);
    printf("retain count = %d\n", CFGetRetainCount(cfObject));
}

printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);
// 결과
// () . 빈 배열을 의미한다.
// retain count = 2
// retain count after the scope = 1

위 소스에서 cfObject = CFBridgingRetain(obj); 대신 cfObject = (__bridge_retained CFMutableArrayRef)obj;를 사용할 수 있다. 선호하는 것을 사용하면 된다.

CFBridgingRelease function
CFMutableArraytRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
printf("retain count = %d\n", CFGetRetainCount(cfObject));

id obj = CFBridgingRelease(cfObject);
printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject));
//결과
// retain count = 1;
// retain count after the cast = 1;

위 소스에서 id obj = CFBridgingRelease(cfObject); 대신 id obj = (__bridge_transfer id)cfObject;로 대체할 수 있다. 선호하는 것을 사용하면 된다.

Property
ARC 환경에서 여러가지 Property 수식어들을 사용할 수 있다.

- assign: __unsafe_unretained 소유권 수식어와 대응.
- copy: __strong 소유권 수식어와 대응.
- retain: __strong 소유권 수식어와 대응.
- strong: __strong 소유권 수식어와 대응.
- unsafe_unretained: __unsafe_unretained 소유권 수식어와 대응.
- weak: __weak 소유권 수식어와 대응.
프로퍼티에 대입하는 것은 소유권 수식어에 해당되는 변수에 대입하는 것과 동일하다.

프로퍼티를 위해 인스턴스 변수를 선언했을 때 이는 프로퍼티와 같은 소유권 수식을 가져야 한다.
@interface SomeObject:NSObject
{
    id obj;
}
@end

@property (nonatomic, weak) id obj;
// 이것은 컴파일 에러를 발생 시킴.
// 멤버 변수를 __weak으로 수식하던가 property를 strong 속성으로 지정하여야 한다.

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

Block의 모든것(2)  (0) 2017.01.02
Block의 모든것(1)  (0) 2017.01.02
GCD(Grand Central Dispatch)(2)  (0) 2016.12.19
GCD(Grand Central Dispatch)(1)  (0) 2016.12.19
Objective-C의 동적 바인딩, 그리고 Message dispather와 Runtime  (0) 2016.12.18
Posted by 홍성곤
,