swift - Outlets cannot be connected to repeating content. 에러가 뜬다면?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


문제 상황

Outlet from the TableViewController to the UILabel is invalid. Outlets cannot be connected to repeating content.

주로 CustomCell을 만들때 발생하는 에러이다.
해당 아울렛을 내가 만들어 준 커스텀셀과 연결되지 않았다는 것을 의미하는 것으로 스토리보드에서 각각 셀을 지정해주면 된다.

[Identity Inspector] > [Custom Class] > [Class 설정]

swift - Hashable

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Hashable

A type that provides an integer hash value.
정수 Hash 값을 제공하는 타입의 프로토콜

  • set 또는 dictionary의 key로 hashable을 준수하는 모든 타입을 사용할 수 있다.
    • swift에서 dictionary는 Dictionary<Key Type, Value Type> 형태로 쓰인다.
    • 이때 유일한 제약사항은 Key Type이 반드시 해시가능한 타입이어야 한다(Hashable)
    • 즉, 그 자체로 유일하게 표현이 가능한 방법을 제공해야한다는 뜻 이다.
    • swift의 기본타입(String, Int, Double…)은 기본적으로 해시가능한 것들로 dictionary의 key type으로 사용가능하다.
    • 또한 swift의 enum(열거형) 또한 해시 가능하다.

표준 라이브러리의 많은 타입들 또한 hashable을 준수한다.

stirng, integer, floating-point, boolean values, set이 기본적으로 hash value를 제공하며(이때 set은 hashable한 것만 들어갈 수 있다) 자신만의 사용자 정의 타입도 hash가 가능할 수 있다. associated valuew없이 열거형(enum)을 정의하면, hashable 준수가 자동으로 적용되고, hash value 프로퍼티를 추가해 사용자 정의 타입에 hashable 준수를 추가할 수도 있다.

타입의 hash value 프로퍼티에 의해 제공되는 hash값은 동일하게 비교되는 임의의 두 인스턴스에 대해 동일한 정수이다.

즉, 같은 타입의 인스턴스인 a와 b의 경우 a==b이면 a.hashvalue == b.hashvalue가 된다는 것을 의미한다.
그러나 반대의 경우에는 true가 아닐 수도 있다. 동일한 hash값을 가진 두개의 인스턴스가 서로 동일할 필요는 없기 때문이다.

hash 값은 프로그램 실행에 따라 동일하지 않을 수 있다. 향후 실행에 사용할 hash값을 저장하지 않아야 한다

Conforming to the Hashable Protocol

set은 hashable한 것만 들어갈 수 있으며 dictionary의 key역시 hashable하다고 했다.
사용자 정의 타입을 set에 넣거나 dictionary key로 만들고 싶다면 타입이 hashable을 준수하면 된다.

사용자 정의 타입의 hashable과 equatable 요구사항은 타입이 다음 조건을 충족시킬 때 컴파일러에서 자동으로 합성(synthesized)해준다.

  • 구조체의 경우, 저장 프로퍼티는 모두 hashable을 준수해야 한다.
  • 열거형의 경우, 모든 associated values은 모두 hashable을 준수해야 한다.
    • associated values가 없는 열거형은 정의없이(hash value) hashable을 준수한다.

타입의 hashable 준수를 커스터마이즈하거나, 위에서 말한 기준에 맞지 않는 형식으로 hashable을 채택하거나, 또는 이미 존재하는 타입을 hashable 준수하도록 확장하는 경우에는 사용자 지정 타입에 hashvalue 프로퍼티를 구현한다. 타입이 hashable 및 equatable 프로토콜의 의미론적 요구사항(semetic requirements)을 충족시키려면 타입의 equatable 준수를 사용자 정의하여 일치시키는 것이 좋다.

예 - 버튼 그리드의 위치

버튼 그리드의 위치를 설명하는 GridPoint 타입을 보자. GridPoint의 초기선언은 아래와 같다.

struct GridPoint {
  var x: Int
  var y: Int
}

이제 여기서 사용자가 탭한 grid point의 set을 만들고 싶다고 가정해보자.

GridPoint타입은 아직 해시가능하지 않기에, set의 element 타입으로 사용할 수 없다.
hashable을 준수하려면 == 연산자와 hashvalue 프로퍼티를 제공해야하기 때문이다.

extension GridPoint: Hashable {
  var hashValue: Int {
    return x.hashValue ^ y.hashValue &* 16777619
  }

  static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
    return lhs.x == rhs.x && lhs.y == rhs.y
  }
}

근데 이제 swift 4.1부터는 변경사항이 생겼다고 한다. hashable 하고싶으면 바로

struct GridPoint: Hashable {
  var x = Int
  var y = Int
}

var zehyeSet = Set<GridPoint>()
var zehyeDict: [GridPoint: Int] = [:]

즉 hashValue를 만들어주지 않았음에도 불구하고 컴파일러가 자동으로 만들어준 것이다. 이게 가능한 이유는

/// A point in an x-y coordinate system.
struct GridPoint: Hashable {
  var x = Int
  var y = Int
}

GridPoint타입의 저장프로퍼티가 모두 hashable한 값들이기 때문이다.(구조체의 경우~~~)

따라서 hashable 프로토콜을 사용자 정의 타입에서 채택만 하면 이제 set에 들어갈수도 dictionary의 key로도 들어갈 수 있음을 의미한다.


Set, Dictionary key가 되려면 왜 hashable 해야할까?

해시함수는 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다.
이 용도중 하나로는 해시테이블이라는 자료구조에 사용되며 매우 빠른 데이터 검색을 위한 컴퓨터 소프트웨어에 널리 사용된다.
해시 함수는 큰 파일에서 중복되는 레코드를 찾을 수 있기에 데이터베이스 검색이나 테이블 검색 속도를 가속할 수 있다.

즉, swift의 set과 dictionary key에는 순서가 없다. hashable의 정의가 “정수 hash값을 제공하는 타입” 인데 이 정수 hash가 있기 때문에 우리가 찾으려는 원소를 더 빨리 찾을 수 있게 되는 것이다.

swift - Equatable

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Equatable

Equatable은 프로토콜이다.

즉, Equatable이라는 약속이 있다는 것을 의미하며, 이를 채택하면 이를 준수해야한다는 것이다.

A type that can be compared for value equality.
값이 동일한 지 어떤지를 비교할 수 있는 타입

즉, Equatable 프로토콜을 준수하는 타입은 등호 연산자(==) 또는 같지 않음 연산자(!=)를 사용해 동등성을 비교할 수 있다. swift 표준 라이브러리의 대부분 기본 데이터타입은 Equatable을 따른다. 따라서 Equatable 프로토콜은 어떤 타입이 채택하는 것으로 이때 타입에는 클래스, 구조체, 열거형 등이 있을 것이다.

뿐만 아니라 Int, String, Double, Boole 등 기본적인 데이터타입도 Equatable을 따르고 있다.

var some = 1
var other = 2

if some == other {
  // code
} else {
  // code
}

위 코드는 Int 이지만 String, Double, Float에도 다 적용이 될 것이다.

이것이 가능한 이유는 결국 이 타입 모두 Equatable이라는 프로토콜을 따르고 있기 때문이다. 그렇기 때문에 이들이 똑같은지 아닌지를 확인할 수 있는 것이다. 그렇다면 만약 우리가 아주 복잡한 클래스나 구조체를 만들어 비교하고 싶다면 어떻게 해야할까?

즉, 클래스, 구조체 이들도 타입인데 이들이 가진 인스턴스를 비교하려면 어떻게 해야할까?

class A {
  var aNum : Int
  init(_ aNum: Int) {
    self.aNum = aNum
  }
}

if A(1) == A(2) // error

즉 xcode는 A(1)와 A(2)가 같은지 확인할 수 없다.
A(1).aNum == A(2).aNum 이 같은지 아닌지는 확인할 수 있을지라도 말이다.

A(1).aNum == A(2).aNum은 Int이고 Int는 Equatable을 따르고 있기 때문에 확인할 수 있다.

근데 이때 나는 죽어도 A(1) == A(2) 이를 확인하고 싶다면 그떄 Equatable을 사용하는 것이다.

// 클래스A는 Equatable을 채택하고 준수함으로써 동일한지 안한지 판별이 가능해짐
class A: Equatable {
  var aNum : Int
  init(_ aNum: Int) {
    self.aNum = aNum
  }
}

그러면 이때 위와 같은 에러가 발생할 것이다.

위 에러는 우리가 테이블뷰/컬렉션뷰 에서 자주 봤던 클래스A가 Equatable 프로토콜을 준수하고있지 않다 는것을 알려주는 에러다.

즉, Optional이 아닌 메소드가 있는 것으로 생각하면 쉽다.

public protovol Equatable {
  public static func ==(lhs: Self, rhs: Self) -> Bool {

  }
}

이제 해당 코드를 우리 클래스 코드에 복사 붙여넣기 해보자.

class A: Equatable {
  var aNum : Int
  init(_ aNum: Int) {
    self.aNum = aNum
  }
  public static func ==(lhs: Self, rhs: Self) -> Bool {

  }
}

그러면 또 아래와 같은 에러가 등장할 것이다.

위 에러는 말 그대로 Self가 아니라 A인것같다고 말해주는 에러이다. 따라서 Self가 A이긴 하지만 이를 파라미터 안에서 사용하지 못하는 것으로 보이기에 A로 바꿔줘보자!

class A: Equatable {
  var aNum : Int
  init(_ aNum: Int) {
    self.aNum = aNum
  }
  public static func ==(lhs: A, rhs: A) -> Bool {

  }
}

그러면 또 위와 같이 리턴값이 없다는 에러가 나오게 된다. 메소드 안에서 값을 비교해줘야 한다.

class A: Equatable {
  var aNum : Int
  init(_ aNum: Int) {
    self.aNum = aNum
  }
  public static func ==(lhs: A, rhs: A) -> Bool {
    return lhs.aNum == rhs.aNum
  }
}

즉, 클래스 A의 프로퍼티인 aNum이 값은지 같지 않은지를 판별해서 리턴해주도록 한다.

이때 aNum은 Int타입인데 Equatable을 준수하기 때문에 당연히 비교가 가능한 것이고 그 결과가 Bool로 나오게 되는 것이다.

이제 그럼 우리가 궁극적으로 원했던 비교를 해보자.

if A(1) == A(2) {
  print("same")
} else {
  print("different")
}

// different

매우 잘 되는 것을 볼 수 있을 것이다!

Delegation, Notification, KVO

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Delegation, Notification center, KVO

Delegation, Notification center, KVO는 iOS를 만들다보면 자주 나오는 패턴들이다. KVO를 제외하면 실제 매우 자주 사용해본 경험도 있을 것인데, 대부분의 경우 View와 VC 간 또는 각각의 것들 사이에서 소통이 필요할 때 사용했을 것이다. 어떤 이벤트가 A에서 발생하면 B에 알려주어 적절한 조치를 취하도록 하는 것.

그렇다면 이 세가지 패턴은 왜 만들어지게 되었을까?

하나의 객체가 다른 객체와 소통은 하되 묶이기(coupled)는 싫어서..

세 패턴 모두 특정 이벤트가 일어나면 원하는 객체에 알려주어 해당되는 처리를 하는 방법을 가지고 있다. 어플리케이션의 특성 상 객체 간 소통은 필수적이다. 하지만 한 객체는 그 자체로 존재하면서 소통하고 싶을 뿐 다른 객체에 종속되어 동작하는 것은 재사용성독립된 기능 요소 측면에서 바람직하지 않다. 대표적으로 VC는 한 View를 관리하는 독립적인 객체로 존재해야지 소통을 위해 다른 VC에 묶여 동작하는 것은 올지 않은 방식이다. 따라서 이때 Delegation, Notification, KVO를 사용한다.

Delegation

Delegate은 보통 프로토콜을 정의하여 사용한다. Protocol이란 일종의 기능 명세서 같은 것으로 Delegate로 지정된 객체가 해야 하는 메소드들의 원형을 적어 놓는다. Delegate 역할을 하려는 객체는 이 Protocol을 따르며 원형만 있던 메소드들의 구현을 한다. 이렇게 세팅 후 이전 객체는 어떤 이벤트가 일어났을 시 delegate로 지정한 객체에 알려줄 수 있다.

Notification

Notification center라는 싱글턴 객체를 통해

KVO

GitHub API 시작하기 for iOS

|