본문 바로가기

iOS/니카내카

[니카내카] 모듈(프레임워크)로 쪼개보자. (Resource/Util Framework)

728x90

프로젝트에서 사용되는 자원을 어떻게 관리할까 고민을 하다가 원래 생각한 방법은 아래와 같다.

  1. 프로젝트 내부에서 관리 (Extension .. 등으로)
  2. 오픈소스 라이브러리 사용

 

그런데 둘 다 결국 프로젝트 내부의 코드가 많아졌기 때문에 아예 프로젝트 외부에서 관리하면 어떨까? 라는 생각에 .. (+ 싹에서 배운 것을 적용하기 위해) 모듈화하기로 했다.

 

모듈화라고 해서 뭔가 되게 거창한 것 같지만 .. 그냥 프레임워크 만든다고 생각하면 된다 ㅋㅋ

 

 

✔️ 프레임워크를 어떻게 만드는지,

✔️ Util 관리하는 프레임워크를 어떻게 만드는지

✔️ Resource 관리하는 프레임워크를 어떻게 만드는지

✔️ 결과적으로 프로젝트 내에서 어떻게 적용하는지 

... 를 알아보도록 하자 !!


프레임워크를 만들어보자.

지난번 글에서 한번 다룬 적이 있지만 (접근제한자를 다루면서 ... ) 이번에 한번 더 정리해보자 ~ ..

 

프레임워크를 만들기 위해서는 다음과 같이 new project > Framework를 선택한 뒤

 

iOS App 프로젝트를 만드는 것처럼 이름을 작성하고 Next, Next .. (Next Level)를 눌러주면 된다.

 

그리고 iOS App 프로젝트를 만들 때 필요한 파일들을 만들고 관련된 파일들끼리 폴더링 하듯 같은 방식으로 진행하면 된다.

 

나는 크게 두가지 프레임워크를 만들어서 사용했는데 Util 프레임워크와 Resource 프레임워크를 만들었다.

 

 

Util Framework

✅ BaseView/BaseViewController를 관리하는 파일과 -> Base 폴더

✅ View,  ViewController 등의 재사용시 필요한 identifier를 설정하는 파일, -> Protocol 폴더

✅ 그리고 UIKit를 확장해서 사용하는 코드들을 모아두고 사용했다. -> UIKit+Extension

 

 

🔴 여기서 주의할 것은 접근제한자이다. 🔴

iOS App 프로젝트에서 프레임워크 속 코드에 접근하는 것은 외부에서 접근하는 것이다.

그래서 private, fileprivate 등과 같은 제한자를 사용할 경우 iOS App 프로젝트 파일에서 사용할 수 없으므로 이를 주의해서 접근 제한자를 작성해야 한다.

 

 

예를 들어서 만약 앱 프로젝트에서 extension으로 사용했다면, 접근제한자를 생략(= internal)해도 되지만,

프레임워크에서 사용할 경우 public으로 명시해야 한다.

 

예시 코드 ⬇️

import UIKit

extension UIView {
    public func addSubviews(_ views: UIView...) {
        views.forEach {
            addSubview($0)
        }
    }
    
    public func makeRound(radius: CGFloat = 10) {
        layer.cornerRadius = radius
        clipsToBounds = true
    }
    
    public func makeShadow(_ color: UIColor, _ opacity: Float, _ offset: CGSize, _ radius: CGFloat) {
        layer.shadowColor = color.cgColor
        layer.shadowOpacity = opacity
        layer.shadowOffset = offset
        layer.shadowRadius = radius
    }
}

 


Resource Framework

Resource를 관리하는 프레임워크는 조금 더 절차가  필요하다.

Util의 경우 접근 제한자를 주의해서 코드를 작성하면 되지만, Resource의 경우는 관련 파일들이 존재한다.

 

예를 들어 컬러의 경우는 컬러 셋이 존재하고, 이미지의 경우 .jpeg 또는 .png 등의 파일들이 있고,

이 파일에 읽어와서 코드로 접근할 수 있도록 해야한다.

 

 

이게 무슨 소리인가.

할 수 있는데 !!

 

위와 같이 리소스 관련 프레임워크에 먼저 리소스 파일을 넣는다.

 

 

이미지의 경우 위와 같이 이미지 셋을 넣으면 되고 컬러 셋의 경우 아래와 같이 프로젝트에서 사용할 컬러 셋을 넣으면 된다.

 

 

그리고 나서 코드로 이 파일들을 읽어오고, 외부에서 접근할 수 있도록 작성하면 된다.

 

 

위에서 추가한 해당 이미지/컬러들은 외부에서 코드로 불러와 사용하는 경우가 있으므로, 외부에서 접근할 수 있도록 코드를 만들어야 한다.

Resouces 프레임워크는 R 이라는 타입으로 접근하여 사용할 예정이기 때문에 Resource 프레임워크 안에 R.swift 파일을 만들고 아래와 같이 코드를 추가해준다.

 

import Foundation

public class R {
    static let bundle = Bundle(for: R.self)
}

 

리소스를 프레임워크로 만들었고 이미지를 불러올 때 Resources 프레임워크의 Bundle 위치를 알아야 사용할 수 있으므로,

내부에서 사용할 bundle을 만든다.

 

그리고 이미지를 외부에서 접근할 수 있도록 코드를 추가해준다.

 

import UIKit

public extension R {
    enum Image { }
}

public extension R.Image {
    static var btnBack: UIImage { .load(name: "btn_back") }
    static var btnClose: UIImage { .load(name: "btn_close") }
    static var btnDelete: UIImage { .load(name: "btn_delete") }
    static var btnLocation: UIImage { .load(name: "btn_location") }
    static var btnSearch: UIImage { .load(name: "btn_search") }
    static var btnSetting: UIImage { .load(name: "btn_setting") }
    
    static var imgLogo: UIImage { .load(name: "img_logo") }
    
    static var imgSocarLogo: UIImage { .load(name: "socar_logo")}
    static var imgGreencarLogo: UIImage { .load(name: "greencar_logo")}
}

extension UIImage {
    static func load(name: String) -> UIImage {
        guard let image = UIImage(named: name, in: R.bundle, compatibleWith: nil) else {
            assert(false, "\(name) 이미지 로드 실패")
            return UIImage()
        }
        return image
    }
}

 

같은 방식으로 컬러도 추가한다.

import UIKit

public extension R {
    enum Color { }
}

public extension R.Color {
    static var black100: UIColor { .load(name: "black100") }
    static var black200: UIColor { .load(name: "black200") }
    
    static var blue100: UIColor { .load(name: "blue100") }
    static var green100: UIColor { .load(name: "green100") }
    
    static var gray100: UIColor { .load(name: "gray100") }
    static var gray200: UIColor { .load(name: "gray200") }
    static var gray300: UIColor { .load(name: "gray300") }
    static var gray400: UIColor { .load(name: "gray400") }

    static var white: UIColor { .load(name: "white")}
}

extension UIColor {
    static func load(name: String) -> UIColor {
        guard let color = UIColor(named: name, in: R.bundle, compatibleWith: nil) else {
            assert(false, "\(name) 컬러 로드 실패")
            return UIColor()
        }
        return color
    }
}

 


프레임워크를 넣어보자.

이렇게 프레임워크를 만들고 땡 ! 이 아니라 사용하고자 하는 프로젝트 내에 넣어줘야 한다.

 

먼저 위와 같이 프로젝트 바로 아래에 Add Files to 로 

 

프로젝트 파일을 선택해서 넣으면 된다. 

(위의 사진에서 비활성화 된 이유는 이미 나는 들어가 있기 때문이다.)

 

주의할 점은 폴더를 선택하는 것이 아니라 프로젝트 파일, pbx 파일을 선택해야 한다는 것이다 !!!

 

그리고 나서, General > Framsworks ~ 로 간 뒤 + 버튼을 누르고

 

열심히 만들었던 Util 프레임워크와 Resource 프레임워크를 넣으면 된다 !!

 


프레임워크를 사용해보자.

이제 드디어 사용을 할 시간 !!!!

 

사용할 곳으로 이동해서

원하는 프레임워크를 import 한 뒤에

 

리소스의 경우 이미지, 색상 등에 적용할 수 있다.

R로 접근하여 원하는 리소스를 설정하면 된다.

 

나는 BaseView/BaseViewController 등도 Util Framework로 분리했고, 화면전환 코드도 해당 프레임워크 속 UIKit+Extension으로 분리했기 때문에 대부분의 ViewController에서 NiCarNaeCar_Util 를 import 해서 사용하고 있다.

 

 


그래서 왜 이렇게 분리하는데??

 

일단, 이번 프로젝트의 규모가 그렇게 크지 않았기 때문에 리소스를 R.swift 또는 SwiftGen을 사용해서 관리하기에는 오버스펙이라고 느껴졌다.

그리고 앱 프로젝트 내로 코드를 쓰면 로직 관련 코드가 아닌 리소스 파일/코드들로 용량이 커지는 것은 피하고 싶었다.

 

+ 싹에서 프레임워크를 배웠기 때문에 이를 적극 활용하고 싶었다.

 

.. 그리고 먼저 모듈화를 해 본 taekki가 한번 해보라고 했기에 .. 등등으로 .. 시도해보았다.

 

 

만들고 적용하면서 느낀 것은 

🔴 초기 세팅은 시간이 조금 걸리지만 

🟢 리소스를 메인 프로젝트와 분리해서 적용할 수 있고 중복되는 코드 및 이미지 파일을 줄일 수 있어서 좋은 것 같다.

 

 

_

참고로 폰트가 없는 이유는 모르겠움. 

폰트는 plist에 넣어줘야 하는데 그걸 외부에서 넣어주고 다시 메인 플젝에서 어케 갖고 오는지 모르겠음 !!!!

알려종. 제발.