“dynamic” in Swift

https://cocoacasts.com/what-does-the-dynamic-keyword-mean-in-swift-3/ 해석

  1. dynamic dispatch는 objective c 런타임이 특정 메소드나 함수의 어떤 구현부를 쓸지를 런타임에 결정하는 것이다. 그래서 상속이 된 부모 클래스와 자식 클래스가 있고 자식 클래스가 함수를 override 하고 있다면 런타임에 자식 클래스의 구현부를 사용할 수 있게 된다.
  2. Swift 런타임은 어쩔 수 없을 때만 dynamic dispatch를 사용한다. 컴파일할 때 어떤 구현부를 쓸지 정할 수 있다면 정한다. 이건 약간 속도를 빠르게 한다. 가능만 하다면 static이나 virtual dispatch를 사용한다.
  3. iOS 개발에서 많은 프레임워크들은 objective c로 되어 있다. Core Data나 Key-Value Observing를 포함한 많은 것들은 dynamic dispatch를 사용한다.
  4. dynamic으로 class의 property 선언구에 추가하는 것은 이 property를 접근할 때는 dynamic dispatch를 써야한다는 의미다. dynamic 키워드를 사용하게 되면 암시적으로 objc를 쓰는 효과가 생긴다. 왜냐하면 Objective c runtime에 dynamic dispatch가 되려면 objective c에 노출이 되어야 하기 때문이다.
  5. dynamic은 class에서만 가능하다. 왜냐면 struct나 enum은 상속이 안되기 때문에 컴파일 타임에도 어떤 구현부를 쓸지 다 알 수 있기 때문이다.
  6. Key-value observing으로 짰을 때 @objc 안 하고 dynamic만 해도 compile은 되지만 앱에서 구동할 때 크래시가 난다
    1.  Fatal error: Could not extract a String from KeyPath Swift.KeyPath<.UserActionManager, Swift.Optional<Swift.Int>>

Why exceptions are not caught in Swift

in Swift, it doesn’t support to throw exception and catch exception, but objective-C supports them.

Even the codes are written in swift, some APIs like JSONSerialization.data are from objective-c and throws exceptions. The exceptions are not “catch”ed in swift.

 

 

@try {

if (completeHandler != nil) {

completeHandler(result);

}

} @catch (NSException *exception) {

if (errorHandler) {

errorHandler(error);

}

}

 

customRequest.url(completeHandler: { (data) in

                                do {

                                    let data = try JSONSerialization.data(withJSONObject: “”, options: JSONSerialization.WritingOptions.prettyPrinted)

//when error occurs here -> it goes to catch block below.

//It generates exception “NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top-level type in JSON write” -> it goes to @catch block ahead.

                                    StatusManager.save(data: data)

                                } catch {

                                    errorHandler(error)

                                }

                            }

        },

                          errorHandler: {(error) in

                            errorHandler(error)

        })

static func save(data: Data) {

        let decoder = JSONDecoder()

        do {

            let status = try decoder.decode(status.self, from: data)

//when error occurs here catch block below is executed

        } catch {

            Crashlytics.sharedInstance().recordError(error)

        }

    }

 

how to make codable class (to use in objective c)

@objc class BlindReason: NSObject, Codable {

let title: String

let reasonDescription: String

init(title: String, reasonDescription: String) {

self.title = title

self.reasonDescription = reasonDescription

}

private enum CodingKeys: String, CodingKey {

case title, reasonDescription = “description”

}

}

 

 

if let data = try? JSONSerialization.data(withJSONObject: blindReasons, options: JSONSerialization.WritingOptions.prettyPrinted) {

let decoder = JSONDecoder()

if let reasons = try? decoder.decode([BlindReason].self, from: data) {

//do something

}

}

}

 

  • When the class has not codable property. like when the property type is custom class written in objective-c.
  1. Implementing NSCoding does not make the class codable
  2. You cannot write “init(from decoder: Decoder) throws” in extension

Ways

  1. Remove the property from CodingKeys. -> for Category
  2. Call custom initializer of the class customizing decoding initializer -> for Brand
    1. It can not decoded as [String: Any] json format. Because you cannot use “Any”(contextual type) decoding

Category.h

@interface Category : NSObject

Brand+extensions.swift

extension Brand {

    enum CodingKeys: String, CodingKey {

        case index

        case name

        case property2

    }

    

    static func decode(brandContainer: KeyedDecodingContainer<BrandItem.CodingKeys>) throws -> BrandItem {

        let index = try brandContainer.decode(Int.self, forKey: .index)

        let name = try brandContainer.decode(String.self, forKey: .name)

        let property2 = try brandContainer.decode(String.self, forKey: .property2)

        let brandDictionary = [“index”: index, “name”: name, “property2”: property2] as [String : Any]

        let brand = Brand(data: brandDictionary)

        return brandItem!

    }

}

Product.swift

class Product: Decodable {

    let index:Int

    var name:String = “”

    var categoryList: [Category] = [Category]()

    

    var brand: Brand = Brand()

    var image: URL!

    

    enum CodingKeys: String, CodingKey {

        case index

        case brand = “brand”

    }

    

    required init(from decoder: Decoder) throws {

        let values = try decoder.container(keyedBy: CodingKeys.self)

        index = try values.decode(UInt.self, forKey: .index)

let brandContainer = try values.nestedContainer(keyedBy: BrandItem.CodingKeys.self, forKey: .brand)

let index = try brandContainer.decode(Int.self, forKey: .index)

        let name = try brandContainer.decode(String.self, forKey: .name)

        let property2 = try brandContainer.decode(String.self, forKey: .property2)

        let brandDictionary = [“index”: index, “name”: name, “alias”: alias] as [String : Any]

        brand = BrandItem(data: brandDictionary)

    }

  • When you have nullable value on JSON and you cannot use optional to work with objective-C

Product.swift

class Product: Decodable {

    var encryptedProductId:String = “”

    let index:UInt

   init(index: UInt) {

        self.index = index

    }

}

Event.swift

@objc class Event: NSObject, Decodable {

    let index: Int

    let product: Product

    

    enum CodingKeys: String, CodingKey {

        case index

        case product

    }

    

    required init(from decoder: Decoder) throws

    {

        let values = try decoder.container(keyedBy: CodingKeys.self)

        index = try values.decode(Int.self, forKey: .index)

        if let tempProduct = try? values.decode(Product.self, forKey: .product) {

            product = tempProduct

        } else {

            product = Product(index: 0)

        }

    }

}