[iOS] Naver BoostCourse - MusicPlayer

kpk0616·2022년 8월 28일
0

iOS

목록 보기
3/4
post-thumbnail

처음 iOS 개발을 접하는 입장에서 Swift 문법이나 앱 개발에 필요한 개념, 사용되는 메소드 등에 대한 지식이 전무한 상태인지라 필요한 지식을 기록하고 습득하기 위해 해당 코드가 왜 이렇게 작성되었는지 분석하는 시간을 가졌습니다.

# 1

guard let

guard let soundAsset: NSDataAsset = NSDataAsset(name: "sound") else {
            print("음원 파일 에셋을 가져올 수 없습니다")
            return
        }

guard let 으로 지정한 변수는 if let 과는 다르게 전역변수로 사용 가능하다. 또한 else 문에서 return, break, continue, throw 와 같이 상위 코드 블록을 종료하는 코드가 필수적으로 들어가야 한다.

참고

https://velog.io/@dev-lena/guard-let과-if-let의-차이점
https://jud00.tistory.com/entry/오늘의-Swift-지식-if-let-과-guard-let의-차이는

NSDataAsset

guard let soundAsset: NSDataAsset = NSDataAsset(name: "sound") else {
            print("음원 파일 에셋을 가져올 수 없습니다")
            return
        }

개체의 콘텐츠는 연결된 디바이스 속성을 가진 하나 이상의 파일 집합으로 저장이 되는데 (assets), 이 sets 는 NSDataAsset 을 통해 on-demand 리소스로 사용할 수 있도록 태그를 지정할 수 있다.

# 2

var player: AVAudioPlayer!
    
...

do {
            try self.player = AVAudioPlayer(data: soundAsset.data)
            self.player.delegate = self
        } catch let error as NSError {
            print("플레이어 초기화 실패")
            print("코드 : \(error.code), 메세지 : \(error.localizedDescription)")
        }

do - try - catch

error handling 을 위해 swift 에서 사용하는 방식이다.
예제 코드는 아래와 같다.

do {
    // Create audio player object
    audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
            
    // Play the sound
    audioPlayer?.play()
}
catch {
    // Couldn't create audio player object, log the error
    print("Couldn't create the audio player for file \(soundFilename)")
}

참고

https://codewithchris.com/swift-try-catch/

player 가 아닌 self.playser 로 사용하는 이유는?

self 란 보통 클래스나 구조체 자신을 가리킬 때 사용한다.
아래 예제에서 self.name 은 Student class 의 name 변수를 의미하고 name 은 매개변수로 받아온 변수 name 을 의미한다.

class Student {
	var name = ""
    func setName(name: String) -> () {
    	self.name = name
    }
}

self.player.delegate = self 에서 self.playervar player: AVAudioPlayer! 로 정의된 변수 player 를 가리키게 된다. 그런데 player 만으로도 가리킬 수 있는 변수임에도 불구하고 self.player 로 명시해주는 이유는 무엇일까?
인스턴스는 클래스 외부에서만 접근할 수 있기 때문에 클래스 내부에서는 어느 인스턴스에 할당된 것인지 알기 힘들다.
이러한 이유 때문에 인스턴스 이름 대신 self 키워드를 이용해 자신의 인스턴스라는 것을 표현한다.
self 키워드는 생략이 가능하며, 실제로 생략을 많이 한다.
하지만 만약 프로퍼티와 일반 변수의 이름이 같을 경우 구분을 위해 self 를 꼭 써주어야한다.

참고

delegate

Delegation은 클래스 또는 구조체가 다른 유형의 인스턴스로 책임을 전달 또는 위임할 수 있도록 하는 디자인 패턴이다.
이 디자인 패턴은 delegate라고 알려진 위임 기능을 제공하도록 보장하는 프로토콜을 정의함으로써 실행이 된다.
AVAudioPlayer 클래스에 해당하는 변수 player 를 새로 만들어주었으니, 이 변수가 자신의 메소드를 사용할 수 있도록 하기 위해 delegate 를 이용해 위임해준다.

참고

catch let error as NSError

앞에 NS 가 붙은 타입은 대부분 Objective-C 의 타입이다.
do-try-catch 구문에서 catch 에서 catch 뒤에 매개변수 패턴 등을 명시하지 않으면 암묵적으로 error 라는 이름으로 Swift의 Error 타입의 인스턴스가 전달되는데, 이 Error 타입의 인스턴스를 ObjectiveC 의 NSError 타입으로 브릿징(Bridging)_ 하여 사용하기 위해 작성한 것이다.

참고

https://yagom.net/forums/topic/기초적인-swift-문법-질문드립니다-ㅠㅠ/

AVAudioPlayer

AVAudioPlayer 를 사용하기 위해서는 AVFoundation 프레임워크를 import 해야한다. (import AVFoundation)
AVAudioPlayer(contentsOf:) 또는 AVAudioPlayer(data:) 로 플레이어를 생성할 수 있다.
네트워크 상 존재하는 파일일 경우 URL로부터 Data 를 추출하고 AVAudioPlayer(data:) 로 생성할 수 있다. getDataFrom(url:) 메서드를 사용하면 된다.

참고

https://yurimac.tistory.com/57

# 3

self.progressSlider.maximumValue = Float(self.player.duration)
        // self.player.duration 값이 얼마?
        self.progressSlider.minimumValue = 0
        self.progressSlider.value = Float(self.player.currentTime)

AVAudioPlayer 의 속성

duration 은 플레이어 오디오의 총 지속 시간(초 단위) 이다. 따라서 progressSlider 의 최대값을 player 의 duration 으로 설정해준 것이다.
progressSlider 의 현재 값은 player 의 currentTime 으로 지정해 나타낼 수 있다.

참고

https://developer.apple.com/documentation/avfaudio/avaudioplayer/1388395-duration

# 4

// TimeLabel 업데이트 함수
    func updateTimeLabelText(time: TimeInterval) {
        let minute: Int = Int(time / 60)
        let second: Int = Int(time.truncatingRemainder(dividingBy: 60))
        let milisecond: Int = Int(time.truncatingRemainder(dividingBy: 1) * 100)
        
        let timeText: String = String(format: "%02ld:%02ld:%02ld", minute, second, milisecond)
        
        self.timeLabel.text = timeText
    }

TimeInterval

초 단위를 의미한다.

truncatingRemainder(dividingBy:)

truncatingRemainder(dividingBy:) 은 소수점이 있는 Double 이나 Float 의 나머지 값을 구할 때에도 사용되는데, truncating division 을 이용해 주어진 값으로 나눈 값의 나머지를 반환한다.

# 5

// 타이머 생성 및 실행 함수

func makeAndFireTimer() {
		// 타이머 생성
        self.timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [unowned self] (timer: Timer) in
            
            if self.progressSlider.isTracking { return }
            
            self.updateTimeLabelText(time: self.player.currentTime)
            self.progressSlider.value = Float(self.player.currentTime)
        })
        // 타이머 실행
        self.timer.fire()
    }

scheduledTimer(withTimeInterval:repeats:block:)

타이머를 생성하고 타이머를 현재 실행 루프에서 디폴트 모드로 스케쥴한다.

Parameters

  • interval : 타이머 실행(firings) 사이의 시간(초)이다. interval 이 0.0 보다 작거나 같으면 음수가 아닌 값인 0.0001 초를 선택한다.
  • repeats : true 인 경우 타이머는 무효화될 때까지 반복적으로 리스케쥴한다. false 인 경우 타이머가 실행(fire) 된 후 무효화된다.
  • block : 타이머가 실행될 때 실행할 블록이다. 하나의 NSTimer 파라미터만을 사용하며 return value 가 없다.

return value

지정된 매개변수에 따라 구성된 새로운 NSTimer 객체를 리턴한다.

isTracking

유저가 스크롤을 시작하기 위해 콘텐츠를 터치했는지 여부를 나타내는 boolean 값이다.

위의 makeAndFireTimer 함수는 타이머를 생성 및 실행하는 함수이다.
타이머 생성 시 사용자가 스크롤을 시작하면 타이머 생성을 중단하고, timelabel 의 텍스트와 progressSlider 의 값을 player 의 현재 시간으로 초기화한다.

참고

https://developer.apple.com/documentation/uikit/uiscrollview/1619413-istracking

# 6

// 타이머 무효화 함수
    func invalidateTimer() {
        self.timer.invalidate()
        self.timer = nil
    }

nil

Siwft 에서 nil 은 특정 타입에 대한 값의 부재를 의미한다.
Object-C 와 Swift 에서 쓰이는 것으로, C 언어의 NULL 과 유사하지만 다르다.
Object-C 의 nil 은 Object-C 객체의 클래스의 부재를 나타내고 (존재하지 않는 객체에 대한 포인터), Swift 의 nil 은 Objective-C 객체의 부재를 나타낸다 (포인터가 아닌 특정 타입에 대한 값의 부재를 보여줌).

import Foundation

let str1 = "123a"
let str2 = "123"

print(Int(str1) == nil ? 0 : 1)
print(Int(str2) == nil ? 0 : 1)

// 출력 결과
0
1

문자열을 Int 형으로 변환했을 때, Int 형으로 변환할 수 없는 문자열이 포함된 str1 의 경우에는 0을 출력하는 모습을 볼 수 있다.

참고

https://seolhee2750.tistory.com/10

# 7

func addPlayPauseButton() {
        let button: UIButton = UIButton(type: UIButton.ButtonType.custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        
        self.view.addSubview(button)
        
        button.setImage(UIImage(named: "button_play"), for: UIControl.State.normal)
        button.setImage(UIImage(named: "button_pause"), for: UIControl.State.selected)
        
        button.addTarget(self, action: #selector(self.touchUpPlayPauseButton(_:)), for: UIControl.Event.touchUpInside)

...

translatesAutoresizingMaskIntoConstraints

translatesAutoresizingMaskIntoConstraints 는 UIView 의 인스턴스 프로퍼티이다.
런타임 시 뷰를 추가, 삭제하기 위해 Suto Layout 을 코드로 제약 조건을 생성, 추가, 삭제 및 적용할 수 있는데 그러기 위해 translatesAutoresizingMaskIntoConstraints 값을 false 로 주어야한다.
뷰의 autoresizing mask 가 constraint 를 기반 레이아웃 시스템으로 변환할지 말지 여부를 나타내는 boolean 값이다.

참고

https://ios-development.tistory.com/672

view.addSubview

addSubview(_:) 는 리시버의 subview 리스트의 맨 끝에 뷰를 추가한다.
맨 끝에 추가된 뷰는 다른 subview 들보다 위에 나타난다.

참고

https://developer.apple.com/documentation/uikit/uiview/1622616-addsubview

addTarget(_:action:for:)

타겟 객체와 메서드를 컨트롤과 연결한다.

parameters

  • target : 메서드가 호출되는 객체이다.
  • action : 호출할 작업 메서드를 식별하는 셀렉터이다. 이름이 일치하는 셀렉터를 지정할 수 있다. nil 이 아니어야 한다.
  • controlEvents : 액션 메서드가 호출되는 컨트롤별 이벤트를 지정하는 비트 마스크이다. 항상 하나 이상의 상수를 지정해야한다. 가능한 상수 목록은 UIContro.Event 를 참조하면 된다.

참고

https://developer.apple.com/documentation/uikit/uicontrol/1618259-addtarget

#selector

셀렉터는 메서드를 식별할 수 있는 고유한 이름이다. Swift 에서는 구조체 (struct) 타입이며 컴파일 타임에 지정된다.
UIKit 내부에 Objective-C 런타임으로 실행되는 메서드가 셀렉터를 파라미터로 전달받을 때, 전달에 필요한 셀렉터 인스턴스 생성을 위해 사용한다.

참고

https://woozzang.tistory.com/120

UIControl.Event.touchUpInside

손가락이 컨트롤 가능한 바운드 내에 있는 터치업 이벤트이다.

# 8

...
        let centerX: NSLayoutConstraint
        centerX = button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
        
        let centerY: NSLayoutConstraint
        centerY = NSLayoutConstraint(item: button, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 0.8, constant: 0)
        
        let width: NSLayoutConstraint
        width = button.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.5)
        
        let ratio: NSLayoutConstraint
        ratio = button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1)
        
        centerX.isActive = true
        centerY.isActive = true
        width.isActive = true
        ratio.isActive = true
        
        self.playPauseButton = button
    }

NSLayoutConstraint

constraint 기반 레이아웃 시스템이 충족해야하는 두 UI 객체 간의 관계를 나타낸다.
item1.attribute1 = multiplier × item2.attribute2 + constant 와 같이 선형 방정식 형태여야한다.

참고

http://minsone.github.io/mac/ios/nslayoutconstraint
https://developer.apple.com/documentation/uikit/nslayoutconstraint

centerXAnchor

뷰 프레임의 수평 중심을 나타내는 레이아웃 앵커이다.

.isActive = true

iOS 8부터는 isActive 속성을 설정해 제약 조건을 활성화해야한다. 제약 조건을 포함하는 배열을 전달해 여러 제약 조건을 활성화할 수도 있다.

profile
가능한 한 빨리 틀렸음을 증명하려고 노력합니다.그래야만 발전을 찾을 수 있기 때문입니다.

0개의 댓글