야곰님의 Swift Programming을 공부하며 혼자 끄적였던 것들을 옮긴다.

Swift를 공부하는 다른 개발자들에게도 0.1만큼의 도움이 되었으면 좋을 것 같다는 마음으로 시작!

 

Swift

- 고차원적 언어

- ARC(자동 참조 카운팅)으로 메모리를 관리한다

  - 자바 기반의 GC와 비슷한 역할을 하지만, 전혀 다르다고 말할 수 있다. 이에 대해서는 이후에 작성할 것임

- Object-C의 동적 객체 모델과 매개변수를 도입했다

 

특징

  • 안전성
    • guard, 옵셔널, 오류처리 등으로 보다 안전하다
    • 타입에 대해 엄격하다 -> 개발자에게는 오히려 불편하지만, 프로그램 관점으로 본다면 안전하다
  • 신속성
  • FP(함수형 프로그래밍) 패러다임을 적용
  • POP(프로토콜 지향 프로그래밍)

함수형

  • 대규모 병렬처리에 유용하다
  • 상태의 변화없이 데이터를 처리할 수 있다
    • 함수형이 아니라면 포인터, 레퍼런스 등이 변경되어 함수 내부 처리에 영향을 준다
      • 즉, 순수함수를 작성하기 힘들다.
  • 함수를 일급시민으로 다룬다.
    • 전달인자(매개변수)로 전달 가능
    • 동적 프로퍼티로 할당 가능
    • 변수나 데이터 구조 내에 할당 가능
    • 반환 값으로 반환 가능
func sum(first: Int) -> (Int) -> Int{
	return { second in
    	return first+second
    }
}

sum(first:10)(5)

해당 메소드처럼 하나의 매개변수만 담을 수 있게 하는 기법을 커링이라 한다.

 

커링이란?

- 여러개의 매개변수를 갖는 함수를 매개변수 하나를 갖는 함수의 나열로 표현

 

프로토콜 지향 프로그래밍 POP

- 뒤~에서 설명

 

 

 

 

 

 

 

 

 

 

 

 

우선 Swift의 메모리 관리에 대해서 먼저 알아보자

-> 순서가 뒤죽박죽이긴 한데, 이후에 ARC에 대해서도 따로 적을 것이다.

 

Swift는 이전에 Object-C에서 파생된 언어이기 때문에, 메모리 관리를 비슷하게 따라가지만, 개발자에게 좀 더 편하게 바뀌었다.

 

MRC -> ARC 로 변화된 것이다.

이로써 개발자들은 일일히 객체들의 참조 카운트를 retain/release 할 필요가 없어졌다.

 

물론 ARC를 통해 모든 Dangling Pointer들이 처리되면 좋겠지만 아쉽게도 아니다. -> 이후에 적을 것...

 


알아보기로 한 autorelease를 알아보자.

AutoRelease란?

  • 참조 카운트를 나중으로 미루기 위한, 카운트가 나중에 감소되는 것을 보장받는 기법
  • 객체의 참조 카운트를 감소시킬 때, release 대신 autorelease를 사용하면 예약이 된다.
    • 실제로 release를 수행하면 카운트가 바로 줄지만, autorelease는 직후에는 감소되지 않는다.
  • autoRelease된 객체들은 autoReleasePool에 등록된다. -> 간단한게 쓰레드풀을 떠올리면 될 것 같다.
    • 이 autoRelease이 해제될 때, 내부의 객체들이 모두 release된다!!
    • 해당 autoreleasePool은 foundation Framework에 있다.
func useManyImages() {
    let filename = pathForResourceInBundle

    for _ in 0 ..< 5 {
        for _ in 0 ..< 1000 {
            let image = UIImage(contentsOfFile: filename)
        }    
    }
}

 

이미지를 5천번 가져오는 반복문을 예로 들어보자.

메소드가 종료되기 전까지는 생성된 5000개의 image가 메모리 공간에 유지되며 함수가 종료될 때, 해당 image들을 가르키는 참조 카운트가 줄어들며 release된다.

-> 즉, 함수가 끝나기 전까지 5000개의 이미지가 메모리 안에 존재한다는 말!! -> 만약 iOS 폰 내에서 많은 메모리를 사용하고 있고, 메모리가 부족하다면 원치 않게 App이 죽는 경우가 생길 수도 있다.

 

이러한 상황을 막기 위해 AutoRelease를 사용한다.

func useManyImages() {
    let filename = pathForResourceInBundle

    for _ in 0 ..< 5 {
      autoreleasepool {
        for _ in 0 ..< 1000 {
            let image = UIImage(contentsOfFile: filename)
        }   
      }
    }
}

똑같이 이미지를 5천번 가져오는 반복문이다.

 

이미지 1천번을 가져오는 for문을 autoRelease로 감싸고 해당 블럭을 5회 진행한다.

-> 

이미지 1000개를 메모리에 올리고, autoReleasePool이 비워지면서 해당 1000개가 메모리에서 내려가고

이미지 1000개를 메모리에 올리고, autoReleasePool이 비워지면서 해당 1000개가 메모리에서 내려가고

이미지 1000개를 메모리에 올리고, autoReleasePool이 비워지면서 해당 1000개가 메모리에서 내려가고

이미지 1000개를 메모리에 올리고, autoReleasePool이 비워지면서 해당 1000개가 메모리에서 내려가고

이미지 1000개를 메모리에 올리고, autoReleasePool이 비워지면서 해당 1000개가 메모리에서 내려가고

방식으로 작동할 것으로 예상된다.

 

메모리 사용량 그래프를 보면 쉽게 이해할 수 있다.

 

Not AutoRelease
AutoRelease

사용하지 않았을 때, 순간 메모리 사용량이 43MB을 사용했지만, 사용했을 때는, 순간 최고 사용량이 14MB로 현저히 낮은 것을 볼 수 있다.

물론 실제 코드에 사용된다면 내에서 해당 이미지들을 사용하고, 유지하기 위해 다른 참조카운트를 증가시키면 결과는 달라질 수 있지만

우선은 AutoReleasePool의 역할을 알아보았다.

 

참조 : stackoverflow.com/questions/25860942/is-it-necessary-to-use-autoreleasepool-in-a-swift-program

 

 

앞선 포스팅에 이어서 작성된다.

앞선 블로그(https://wlgusdn700.tistory.com/28) 에 가면 iOS에서 화면 캡처하는 것을 알 수 있고 그 캡처된 이미지를 UIImage로 불러올 수 있다.

 

나는 이 캡처된 UIImage를 인스타 스토리에 공유할 것이다!

당연히 카카오/구글/페북 developer 사이트에 방법은 나와있다!

developers.facebook.com/docs/instagram/sharing-to-stories 이곳에 가면 있다.

근데 왜 적냐? 코드가 Objective-C 다.... 안드로이드는 java -> kotlin 이 사실상 문법이 비슷비슷해서 이해할 수 있겠는데...

Ob-C를 내가 어떻게 아냐고....ㅜ

사실 Ob-C를 볼줄이라도 아는 사람들은 곧바로 Swift로 변환이 가능할 것이다. 나도 Objective-C 문법 블로그를 찾아 보면서 변환했다.

 

func backgroundImage(backgroundImage: UIImage) {
        if let urlScheme = URL(string: "instagram-stories://share") {
            if UIApplication.shared.canOpenURL(urlScheme) {
                let pasteboardItems = [["com.instagram.sharedSticker.stickerImage": backgroundImage.pngData(),
                                        "com.instagram.sharedSticker.backgroundImage": backgroundImage.pngData()]]

                let pasteboardOptions = [UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(60 * 5)]

                UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)

                UIApplication.shared.open(urlScheme as URL, options: [:], completionHandler: nil)
            } else {
                print("인스타 앱이 깔려있지 않습니다.")
            }
        }
    }

코드는 굉장히 간단하다

url로 인스타 앱 중 스토리로 공유하는 화면?을 열것이다.

우선 해당 url을 iOS App에서 열수 있는지 확인을 한다. 인스타 앱이 안깔려있어도 못 여는 것 같다.

pasteboardItems 에는 좀 요상한 자료구조가 담긴다. Array(Dictionary) 이런 형태다.

그리고 문자열에서 유추할 수 있듯, sticker와 background가 있는데, 말그래도 배경과 스티커라고 생각하면 된다.

 

pasteboardOptions 등 외의 코드는 는 이번주내로 다시 알아볼 것이다.

 

중요한 점!

Instagram의 맞춤 URL 스키마를 허용 리스트에 등록

앱에서 Instagram의 맞춤 URL 스키마를 사용하려면 허용 리스트에 추가해야 합니다. 이를 위해서는 앱의 Info.plist에 있는 LSApplicationQueriesSchemes 키에 instagram-stories를 추가합니다.

 

라고 Facebook Developer 에 적혀있다. 따라하면 된다.

Info.plist 에 들어가서 LSApplicationQueriesSchemes를 추가하고 해당 value에 "instagram-stories"를 넣으면 된다.

** value는 Array형태로 넣어야한다

 

안드로이드에서는 manifest에 Internet만 허용해주면 대부분의 url에는 접근이 가능했던 것 같은데, iOS는 따로따로 지정해줘야하나보다... 확실히 apple이 보안/정보 측면에서 강도 높게 설정하고 유지하고 있다고 다시 생각하게 되었다.

 

 

'iOS > SwiftUI' 카테고리의 다른 글

[iOS] SwiftUI 에서 Screen Capture 후 Share  (2) 2021.01.13

사이드 프로젝트를 진행하면서 화면을 캡처한 후에 공유하는 기능이 필요해서 본격적인 개발 전에 기능 구현 정도는 해봐야겠어서 미리 겪어본 것들을 적는다.

확실히 몇년 사이에 구글링만해도 없는 정보가 없는 것 같다. 하지만 SwiftUI로 적용되는 예제나 문서, 글은 확실히 적다...

나 스스로도 나중에 필요할 때, 검색하다 내 블로그를 들어올 수 있도록 작성하는 것이 목표이다.

어쩃든 시작

 

 

우선 Swift로 Screen Capture 하는 법을 찾아보면 UIView를 확장하여 적용된 코드들이 많이 나온다.

하지만 SwiftUI에서는 UIKit을 사용하지 않으려고 하기 때문에, 굳이 굳이 SwiftUI로의 전환을 하는 데에 생각보다 많은 시간을 쏟았다.

 

func takeCapture() -> UIImage {
        var image: UIImage?
        guard let currentLayer = UIApplication.shared.windows.first { $0.isKeyWindow }?.layer else { return UIImage() }

        let currentScale = UIScreen.main.scale

        UIGraphicsBeginImageContextWithOptions(currentLayer.frame.size, false, currentScale)

        guard let currentContext = UIGraphicsGetCurrentContext() else { return UIImage() }

        currentLayer.render(in: currentContext)

        image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return image ?? UIImage()
    }

우선 화면을 Capture하여 UIImage로 뽑아주는 함수이다.

 

currentLayer 에 UIApplication.shared.keyWindow.layer 로 적용되어 있는 코드들이 많은데 keyWIndow가 deprecate되면서 다른 방법을 찾아보았다.

프로젝트에 중요한 기능이기에 일단 구현하여 실행을 해본 후에 공부하려고 맘 먹었다.(원래 이러면 안되는데... 시간이 부족해서 ㅜㅜ)

 

func saveInPhoto(img: UIImage) {
        UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil)
    }

    func sharePicture(img: UIImage) {
        let av = UIActivityViewController(activityItems: [img], applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
    }

이어서 사진을 사진 iOS App에 저장하는 함수와 UIActivityVC를 사용하여 Share하는 함수이다.

 

이 코드들을 적용해서 캡처된 사진을 보면 사진이 뿌옇게 나오는 현상이 있었다.

그래서 UIImage의 background나 opacity 같은 속성을 건드려야 하나? 라고 생각하고 있던 찰나

버튼을 누르는 순간 바로 캡처가 되는 것인가? 라고 생각되어서 버튼을 누르고 0.5초 뒤에 캡처되게 구현하였다.

 

당연히 DispatchQueue.main.asyncAfter 를 이용해서 딜레이를 주니 정상적으로 캡처가 되었다.

 

TODO: 이번주 내로 저 메소드들에 있는 한 라인/속성 들이 무엇을 의미하는 지 공부해서 수정할 것이다.

 

p.s : 이번달 내로 2020 회고도 작성해보고 싶다... 2020년이 제일 바쁜 해였어서?!

 

+ Recent posts