본문 바로가기
iOS

iOS 오픈소스 라이브러리 만들기 101

by HaningYa 2020. 4. 18.
728x90

 

오픈소스 라이브러리 만들기 - 야곰닷넷

코코아팟(CocoaPods), 카르타고(Carthage), 스위프트 패키지 매니저(Swift Package Manager, SPM)를 활용하여 나만의 오픈소스 라이브러리(Open Source Library)를 만들어봅니다.

yagom.net

오픈소스 라이브러리를 만들어 볼까 한다. 

 

내용은 안드로이드를 개발한 뒤 iOS를 개발하면서 없어서 불편했던 위젯이나 기능들을 위주로 개발하려고 한다.

 

유용하고 쓸모있고 없고를 떠나서 오픈소스를 만들어 보겠다는 마음이 중요한 것 아니겠나?!

(아니면 말고,,,)

 

먼저 개발해보려 간단하게 생각했던건

  • Toast
  • CheckBox
  • RatingStar
  • radio button
  • radio group
  • floating action button

이정도 이다.

 

iOS 개발은 해왔지만 오픈소스 라이브러리를 개발 하기 위해 먼저 필요한 지식을 학습하고 정리했다.

야곰 닷넷의 "오픈소시 라이브러리" 튜토리얼을 참고했다.

 

오픈소스란

  • 소스코드를 공개해 누구나 특별한 제한 없이 코드를 볼 수 있도록 한 소스코드
  • iOS 오픈소스 예시
 

뱅크 샐러드는 어떤 라이브러리를 쓸까?(iOS 유용한 라이브러리)

많고 많은 라이브러리~ 뱅크 샐러드를 쓰다가 재밌는 걸 발견했다ㅋㅋㅋ 개인설정 --> 오픈소스 라이센스 이용고지 --> 스크롤 하면 채용정보가 나타난다ㅋㅋㅋ 커여운 이모티콘과 함께 뱅크 샐러드의 iOS팀이 반..

haningya.tistory.com

오픈소스 제작의 의미

  • 코드 공개 --> 전 세계인에게 코드리뷰를 받을 수 있다. --> 더 신경쓰고 고민해서 코드를 짜게됨
  • 주니어 개발자에겐 색다른 경력이 될 수 있음
  • 성장의 씨앗(즐거울 일만 가득할것)
  • 실무에서 모듈을 어떻게 더 분리해 볼까 고민하는 내 자신

의존성 관리도구

  • 외부 라이브러리를 사용할 때 프로젝트와 해당 라이브러리의 상관관계를 용이하게 관리해 주는 도구
  • Java : Maven
  • Javasript : Yarn, NPM
  • Cocoa (Touch) : Cocoapods, Carthage, Swift Package Manager
  • 이러한 도구 없이는 외부 라이브러리를 직접 프로젝트에 포팅해야한다. --> 공수가 많이 든다.
  • 시간절약 + 안정성 보장

오픈소스 작성시 주의할 점

  • 라이센스!
  • MIT : 이 소프트웨어를 누구라도 무상으로 제한없이 취급해도 좋다
    1. 단 저작권 표시 및 이 허가 표시를 소프트웨어의 모든 복제물 또는 중요한 부분에 기재해야 한다.
    2. 저자 또는 저작권자는 소프트웨어에 관해서 아무런 책임을 지지 않는다.
  • 타 프로그래머가 내 라이브러리의 모듈 내부의 코드를 어느 수준까지 사용하게 할지 생각해야함
  • [접근제어 참고자료]
 

접근제어 - 야곰닷넷

스위프트 기초 접근제어 객체지향 프로그래밍 패러다임에서 은닉화는 중요한 개념 중 하나입니다. 이번 레슨에서는 이를 구현하기 …

yagom.net

코코아팟이란

  • Swift, Objective-C 언어환경 프로젝트의 의존성 관리 도구
  • 7만개 이상 라이브러리 보유
  • 300만개 이상의 에플리케이션에서 코코아팟 활용중
  • 사용법은 인터넷 찾아보면 자세하게 나옴

코코아팟 라이브러리 만들기

  • 라이브러리를 만들고자 하는 디렉토리로 이동한다
  • 코코아팟 프로젝트를 생성한다.
pod lib create `YOUR_LIBRARY_NAME`

 

라이브러리 프로젝트 생성시 묻는 질문

  1. What platform do you want to use?? : iOS
  2. What language do you want to use?? : Swift
  3. Would you like to include edmo application with your library? :YES
    (이 라이브러리에 대한 화면 스크린샷이 필요하면 YES 를 고른다)
  4. Which testing frameworks will you use?? None
    (None 할 경우 Apple 의 XCTest를 사용하면 된다.)
  5. Would you like t odo view based testing ?? No
    (Yes 할 경우 페이스북 스냅샷 기반 테스트 오픈소스 FBSnapShot TestCase 포함해줌)
gimtaehyeong-ui-MacBookPro:opensourceTutorial kimtaehyeong$ pod lib create SimpleAlert
Cloning `https://github.com/CocoaPods/pod-template.git` into `SimpleAlert`.
Configuring SimpleAlert template.

------------------------------

To get you started we need to ask a few questions, this should only take a minute.

2020-04-18 02:22:12.007 defaults[22714:3598137] 
The domain/default pair of (org.cocoapods.pod-template, HasRunBefore) does not exist
If this is your first time we recommend running through with the guide: 
 - https://guides.cocoapods.org/making/using-pod-lib-create.html
 ( hold cmd and double click links to open in a browser. )

 Press return to continue.


What platform do you want to use?? [ iOS / macOS ]
 > iOS

What language do you want to use?? [ Swift / ObjC ]
 > Swift

Would you like to include a demo application with your library? [ Yes / No ]
 > Yes

Which testing frameworks will you use? [ Quick / None ]
 > None

Would you like to do view based testing? [ Yes / No ]
 > ㅜNo

Running pod install on your new library.

Analyzing dependencies
Downloading dependencies
Installing SimpleAlert (0.1.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `SimpleAlert.xcworkspace` for this project from now on.
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

[!] Automatically assigning platform `iOS` with version `9.3` on target `SimpleAlert_Example` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.

 Ace! you're ready to go!
 We will start you off by opening your project in Xcode
  open 'SimpleAlert/Example/SimpleAlert.xcworkspace'

To learn more about the template see `https://github.com/CocoaPods/pod-template.git`.
To learn more about creating a new pod, see `https://guides.cocoapods.org/making/making-a-cocoapod`.

프로젝트가 후다닥 만들어 졌다.

초기 프로젝트

podspec

  • 라이브러리에 대한 정보를 담고 있다.
  • 소스를 가져와야 하는곳
  • 사용할 파일
  • 적용할 빌드 설정
  • 이름
  • 버전
  • 라이브러리에 대한 설명
  • 일반적인 메타데이터
  • 기본 항목
 

CocoaPods Guides

CocoaPods is fully open-sourced, so it depends on community contributions to get better. If you're looking to start working on CocoaPods, this is the place to start.

guides.cocoapods.org

이렇게 생겼다.

#
# Be sure to run `pod lib lint SimpleAlert.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
    s.name             = 'SimpleAlert'
    s.version          = '0.1.0'
    s.summary          = 'A short description of SimpleAlert.'
    
    # This description is used to generate tags and improve search results.
    #   * Think: What does it do? Why did you write it? What is the focus?
    #   * Try to keep it short, snappy and to the point.
    #   * Write the description between the DESC delimiters below.
    #   * Finally, don't worry about the indent, CocoaPods strips it!
    
    s.description      = <<-DESC
    This library will be used for cocoapods guide
    DESC
    
    s.homepage         = 'https://github.com/uuzaza@naver.com/SimpleAlert'
    # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
    s.license          = { :type => 'MIT', :file => 'LICENSE' }
    s.author           = { 'haningya' => 'uuzaza@naver.com' }
    s.source           = { :git => 'https://github.com/uuzaza@naver.com/SimpleAlert.git', :tag => s.version.to_s }
     s.social_media_url = 'haningya.tistory.com'
    
    s.ios.deployment_target = '13.0'
    
    s.source_files = 'SimpleAlert/Classes/**/*'
    s.frameworks = 'UIKit'
    s.resource_bundles = {
        'SimpleAlert' => ['SimpleAlert/Assets/*.png']
    }
    
    # s.public_header_files = 'Pod/Classes/**/*.h'
    # s.frameworks = 'UIKit', 'MapKit'
    # s.dependency 'AFNetworking', '~> 2.3'
end

Github에 저장소를 생성한다.

remte repo 에 프로젝트를 업로드 한다.

gimtaehyeong-ui-MacBookPro:opensourceTutorial kimtaehyeong$ cd SimpleAlert/
gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ ls
Example			README.md		SimpleAlert.podspec
LICENSE			SimpleAlert		_Pods.xcodeproj
gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ git remote add origin https://github.com/KimTaeHyeong17/SimpleAlert.git
gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ git push -u origin master
Enumerating objects: 34, done.
Counting objects: 100% (34/34), done.
Delta compression using up to 12 threads
Compressing objects: 100% (28/28), done.
Writing objects: 100% (34/34), 13.57 KiB | 3.39 MiB/s, done.
Total 34 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To https://github.com/KimTaeHyeong17/SimpleAlert.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

오오오 항상 라이브러리 쓰기만 해봤는데 신기하다.

 

이제 본격적으로 라이브러리 만들어 보잣

 

그전에 프로젝트 구성을 보면

  • SimpleAlert 프로젝트와 Pods 프로젝트로 나뉨
  • SimpleAlert 프로젝트 하위의 Example for SimpleAlert 는 데모 애플리케이션 구현하는 영역
    (라이브러리 사용 입장에서 테스트)
  • 개발하는 곳은 ReplaceMe.Swift 파일임(친절함)

*접근 제어자

  • open
  • public
  • internal(기본값)
  • fileprivate
  • private
  • (열린마음) open -> public -> internal -> fileprivate -> private (제한적)

예제 Alert 코드

[출처]

 

DevYeom/YYSimpleAlert

A simple alert for the open source library distribution guide. - DevYeom/YYSimpleAlert

github.com

import UIKit

@available(iOS 13.0, *)
open class YYSimpleAlert: UIView {
    private var contentView: UIView!
    private var titleLabel: UILabel!
    private var lineView: UIView!
    private var confirmButton: UIButton!

    private var titleText: String?
    private var confirmText: String?
    private var completion: (() -> Void)?

    public convenience init(title: String, confirm: String, completion: (() -> Void)?) {
        self.init(frame: CGRect.zero)

        self.titleText = title
        self.confirmText = confirm
        self.completion = completion
    }

    override public init(frame: CGRect) {
        super.init(frame: frame)

        contentView = UIView()
        titleLabel = UILabel()
        lineView = UIView()
        confirmButton = UIButton(type: .custom)

        self.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        lineView.translatesAutoresizingMaskIntoConstraints = false
        confirmButton.translatesAutoresizingMaskIntoConstraints = false
    }

    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    open func setAttribute() {
        self.backgroundColor = UIColor.black.withAlphaComponent(0.7)

        contentView.layer.cornerRadius = 14.0
        contentView.backgroundColor = .white

        lineView.backgroundColor = .lightGray

        titleLabel.text = titleText ?? "Title"

        confirmButton.setTitle(confirmText ?? "confirm", for: .normal)
        confirmButton.setTitleColor(.blue, for: .normal)
        confirmButton.addTarget(self, action: #selector(confirmAction), for: .touchUpInside)
    }

    open func show(in superview: UIView) {
        setAttribute()

        superview.addSubview(self)
        self.topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
        self.leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
        self.trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
        self.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true

        self.addSubview(contentView)
        contentView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        contentView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        contentView.heightAnchor.constraint(equalToConstant: 150.0).isActive = true
        contentView.widthAnchor.constraint(equalToConstant: 270.0).isActive = true

        contentView.addSubview(titleLabel)
        titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0).isActive = true

        contentView.addSubview(lineView)
        lineView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        lineView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        lineView.heightAnchor.constraint(equalToConstant: 0.5).isActive = true

        contentView.addSubview(confirmButton)
        confirmButton.topAnchor.constraint(equalTo: lineView.bottomAnchor).isActive = true
        confirmButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        confirmButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        confirmButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        confirmButton.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
    }

    @objc
    private func confirmAction() {
        self.removeFromSuperview()
        completion?()
    }
}

 

테스트 할 수 있는 Example for SimpleAlert 에서 사용해 보면 

//
//  ViewController.swift
//  SimpleAlert
//
//  Created by uuzaza@naver.com on 04/18/2020.
//  Copyright (c) 2020 uuzaza@naver.com. All rights reserved.
//

import UIKit
import SimpleAlert
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func actionAlert(_ sender: Any) {
        if #available(iOS 13.0, *) {
            YYSimpleAlert(title: "와 이게 되네", confirm: "확인버튼도 커스텀할 수 있네") {
                print("completion handler 까지!?!")
            }.show(in: self.view)
        } else {
            // Fallback on earlier versions
        }
    }
}

 

잘된닷!!

오옷

 

코코아팟 라이브러리 배포하기

  • .podspec을 검사해야 한다!
pod spec lint
  • 검증에 실패 할 경우 빨간색 글씨로 알려준다.

  1. 설명이 충분하지 못하다
  2. URL 에 접속할수가 없다
  3. haningya.tistory.com을 validate 하는데 문제가 있다 --> https://haningya.tistory.com/으로 수정한다.
  4. error! git repo 에 접근할 수 없다?? --> 왜죠 --> 원격 저장소에 버전 태그가 없어서 생긴다고 하신다.
  5. 아니 내 에러는 그게 아니다. github 주소가 잘못됫으니 spec 을 고쳐뿐다

https://haningya.tistory.com/

 

 

[github - tag 처음 사용해봤다]

gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ git tag 0.1.0
gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ git push origin 0.1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/KimTaeHyeong17/SimpleAlert.git
 * [new tag]         0.1.0 -> 0.1.0
gimtaehyeong-ui-MacBookPro:SimpleAlert kimtaehyeong$ 

요로코롬 태그를 달아준다.

 

가입도 완료했다.

아앗

누가 SimpleAlert 라는 pod를 이미 만들었나보다... 이름좀 특이하게 할껄 그랬다.

 

만약 배포가 완료되면 github 레포에 pod 뱃지가 활성화 된다.ㅎㅎ

 

코코아팟 라이브러리 튜토리얼은 여기서 끝 이제 내 라이브러리를 만들어 볼 차례다.

 

좋은 튜토리얼 만들어 주셔서 감사합니다 :) follow 하고 가겠습니닷

 

DevYeom - Overview

🍎 iOS Developer & I love Swift 🕊. DevYeom has 6 repositories available. Follow their code on GitHub.

github.com

 

728x90

댓글