Skip to content

Commit

Permalink
Add Codable extensions & clean code
Browse files Browse the repository at this point in the history
  • Loading branch information
tattn committed Dec 17, 2017
1 parent 2ae4398 commit eab309e
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 3 deletions.
9 changes: 8 additions & 1 deletion Sources/NSObject+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@

import Foundation

public extension NSObjectProtocol {
public protocol ClassNameProtocol {
static var className: String { get }
var className: String { get }
}

public extension ClassNameProtocol {
public static var className: String {
return String(describing: self)
}
Expand All @@ -18,6 +23,8 @@ public extension NSObjectProtocol {
}
}

extension NSObject: ClassNameProtocol {}

public extension NSObjectProtocol {
public var describedProperty: String {
let mirror = Mirror(reflecting: self)
Expand Down
2 changes: 1 addition & 1 deletion Sources/NibInstantiatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public protocol NibInstantiatable {
static var instantiateIndex: Int { get }
}

public extension NibInstantiatable where Self: NSObjectProtocol {
public extension NibInstantiatable where Self: NSObject {
public static var nibName: String { return className }
public static var nibBundle: Bundle { return Bundle(for: self) }
public static var nibOwner: Any? { return self }
Expand Down
27 changes: 27 additions & 0 deletions Sources/Safe.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Safe.swift
// SwiftExtensions
//
// Created by Tatsuya Tanaka on 20171218.
// Copyright © 2017年 tattn. All rights reserved.
//

import Foundation

public struct Safe<Wrapped: Decodable>: Codable {
public let value: Wrapped?

public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.value = try container.decode(Wrapped.self)
} catch {
self.value = nil
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
2 changes: 1 addition & 1 deletion Sources/StoryboardInstantiatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public protocol StoryboardInstantiatable {
static var instantiateType: StoryboardInstantiateType { get }
}

public extension StoryboardInstantiatable where Self: NSObjectProtocol {
public extension StoryboardInstantiatable where Self: NSObject {
public static var storyboardName: String {
return className
}
Expand Down
32 changes: 32 additions & 0 deletions Sources/StringTo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// CodableStringTo.swift
// CodableExtensionPack
//
// Created by Tatsuya Tanaka on 20171030.
// Copyright © 2017年 tattn. All rights reserved.
//

import Foundation

public struct StringTo<T: LosslessStringConvertible>: Codable {
public let value: T

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)

guard let value = T(stringValue) else {
throw DecodingError.dataCorrupted(
.init(codingPath: decoder.codingPath,
debugDescription: "The string cannot cast to \(T.self).")
)
}

self.value = value
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.description)
}
}
16 changes: 16 additions & 0 deletions SwiftExtensions.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
24296C121FE6DCC900FB0D0C /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C111FE6DCC900FB0D0C /* URLTests.swift */; };
24296C181FE6F30600FB0D0C /* ScopeMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C171FE6F30600FB0D0C /* ScopeMethod.swift */; };
24296C1A1FE6F35900FB0D0C /* ScopeMethodTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C191FE6F35900FB0D0C /* ScopeMethodTests.swift */; };
24296C1C1FE7014900FB0D0C /* Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C1B1FE7014900FB0D0C /* Safe.swift */; };
24296C1E1FE7018A00FB0D0C /* SafeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C1D1FE7018A00FB0D0C /* SafeTests.swift */; };
24296C201FE7035200FB0D0C /* StringTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C1F1FE7035200FB0D0C /* StringTo.swift */; };
24296C221FE7037C00FB0D0C /* StringToTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24296C211FE7037C00FB0D0C /* StringToTests.swift */; };
244F63E51FE0310E00E3C372 /* SwiftExtensions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 244F63DB1FE0310E00E3C372 /* SwiftExtensions.framework */; };
244F63EA1FE0310E00E3C372 /* NSObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244F63E91FE0310E00E3C372 /* NSObjectTests.swift */; };
244F63EC1FE0310E00E3C372 /* SwiftExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 244F63DE1FE0310E00E3C372 /* SwiftExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -73,6 +77,10 @@
24296C111FE6DCC900FB0D0C /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = "<group>"; };
24296C171FE6F30600FB0D0C /* ScopeMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopeMethod.swift; sourceTree = "<group>"; };
24296C191FE6F35900FB0D0C /* ScopeMethodTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopeMethodTests.swift; sourceTree = "<group>"; };
24296C1B1FE7014900FB0D0C /* Safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Safe.swift; sourceTree = "<group>"; };
24296C1D1FE7018A00FB0D0C /* SafeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeTests.swift; sourceTree = "<group>"; };
24296C1F1FE7035200FB0D0C /* StringTo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringTo.swift; sourceTree = "<group>"; };
24296C211FE7037C00FB0D0C /* StringToTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringToTests.swift; sourceTree = "<group>"; };
244F63DB1FE0310E00E3C372 /* SwiftExtensions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftExtensions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
244F63DE1FE0310E00E3C372 /* SwiftExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftExtensions.h; sourceTree = "<group>"; };
244F63DF1FE0310E00E3C372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -162,6 +170,8 @@
244FB4D31FE6D9F5006A60EF /* Dictionary+.swift */,
244FB4D71FE6DC4F006A60EF /* URL+.swift */,
24296C171FE6F30600FB0D0C /* ScopeMethod.swift */,
24296C1B1FE7014900FB0D0C /* Safe.swift */,
24296C1F1FE7035200FB0D0C /* StringTo.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand All @@ -185,6 +195,8 @@
244FB4D51FE6DAE8006A60EF /* DictionaryTests.swift */,
24296C111FE6DCC900FB0D0C /* URLTests.swift */,
24296C191FE6F35900FB0D0C /* ScopeMethodTests.swift */,
24296C1D1FE7018A00FB0D0C /* SafeTests.swift */,
24296C211FE7037C00FB0D0C /* StringToTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -329,13 +341,15 @@
2452136E1FE2D40500561F9D /* UIColor+.swift in Sources */,
244FB4D01FE6D630006A60EF /* Operator+.swift in Sources */,
24296C181FE6F30600FB0D0C /* ScopeMethod.swift in Sources */,
24296C1C1FE7014900FB0D0C /* Safe.swift in Sources */,
2412C0501FE6A52400C3D555 /* Array+.swift in Sources */,
2412C04A1FE6979500C3D555 /* UIView+.swift in Sources */,
245213661FE03E9700561F9D /* UICollectionView+.swift in Sources */,
24FA56561FE6C24A004CC906 /* UIImage+.swift in Sources */,
244FB4D81FE6DC4F006A60EF /* URL+.swift in Sources */,
2452135E1FE0351300561F9D /* UITableView+.swift in Sources */,
244FB4D41FE6D9F5006A60EF /* Dictionary+.swift in Sources */,
24296C201FE7035200FB0D0C /* StringTo.swift in Sources */,
249F760C1FE4AB0800421BD9 /* NibInstantiatable.swift in Sources */,
244FB4CC1FE6D3C2006A60EF /* TargetedExtension.swift in Sources */,
);
Expand All @@ -350,11 +364,13 @@
245213701FE2D4C100561F9D /* UIColorTests.swift in Sources */,
2452137C1FE35E3500561F9D /* StoryboardInstantiatableTests.swift in Sources */,
244FB4D21FE6D6F3006A60EF /* OperatorTests.swift in Sources */,
24296C1E1FE7018A00FB0D0C /* SafeTests.swift in Sources */,
244FB4D61FE6DAE8006A60EF /* DictionaryTests.swift in Sources */,
245213641FE0388500561F9D /* UITableViewTests.swift in Sources */,
244FB4CE1FE6D46D006A60EF /* TargetedExtensionTests.swift in Sources */,
2412C04E1FE6A4E600C3D555 /* ArrayTests.swift in Sources */,
2412C0431FE650AE00C3D555 /* NibInstantiatableTests.swift in Sources */,
24296C221FE7037C00FB0D0C /* StringToTests.swift in Sources */,
2412C05B1FE6B6A900C3D555 /* StringTests.swift in Sources */,
24296C121FE6DCC900FB0D0C /* URLTests.swift in Sources */,
24296C1A1FE6F35900FB0D0C /* ScopeMethodTests.swift in Sources */,
Expand Down
61 changes: 61 additions & 0 deletions Tests/SafeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// SafeTests.swift
// CodableExtensionPackTests
//
// Created by Tatsuya Tanaka on 20171128.
// Copyright © 2017年 tattn. All rights reserved.
//

import XCTest
import SwiftExtensions

class SafeTests: XCTestCase {

override func setUp() {
super.setUp()
}

override func tearDown() {
super.tearDown()
}

func testFailableArray() {
let json = """
[
{"name": "Taro", "age": 20},
{"name": "Hanako", "age": "にゃーん"}
]
""".data(using: .utf8)!

struct User: Decodable {
let name: String
let age: Int
}

let users = try! JSONDecoder().decode([Safe<User>].self,
from: json)

XCTAssertEqual(users[0].value?.name, "Taro")
XCTAssertEqual(users[0].value?.age, 20)
XCTAssertNil(users[1].value)
}

func testFailableURL() {
let json = """
{"url": "https://foo.com", "url2": "invalid url string"}
""".data(using: .utf8)!

struct Model: Decodable {
let url: Safe<URL>
let url2: Safe<URL>
}

let model = try! JSONDecoder().decode(Model.self,
from: json)

XCTAssertEqual(model.url.value?.absoluteString, "https://foo.com")
XCTAssertNil(model.url2.value)
}

}

52 changes: 52 additions & 0 deletions Tests/StringToTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// CodableStringToTests.swift
// CodableExtensionPackTests
//
// Created by Tatsuya Tanaka on 20171030.
// Copyright © 2017年 tattn. All rights reserved.
//

import XCTest
import SwiftExtensions

class StringToTests: XCTestCase {

// https://developer.yahoo.co.jp/webapi/shopping/shopping/v1/itemsearch.html
let json = """
{
"int": "100",
"customClass": "https://store.shopping.yahoo.co.jp/try3/4905524907230.html"
}
"""
struct Root: Codable {
let int: StringTo<Int>
let customClass: StringTo<CustomClass>

struct CustomClass: LosslessStringConvertible, Codable {
var description: String

init?(_ description: String) {
self.description = description
}
}
}

override func setUp() {
super.setUp()
}

override func tearDown() {
super.tearDown()
}

func testExample() {
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
let root = try! decoder.decode(Root.self, from: data)
XCTAssertEqual(root.int.value, 100)
XCTAssertEqual(root.customClass.value.description, "https://store.shopping.yahoo.co.jp/try3/4905524907230.html")
}

}

0 comments on commit eab309e

Please sign in to comment.