예~전에 한참 스위프트 공부에 재미들여서 했을적에 서버드리븐 유아이라는 개념을 듣고 언젠간 공부해봐야지 하고 메모해놨던 일이 있다.
지금이 24년이고, 내가 앱에 입문했을때가 18년이니까 대충 20년쯤이지 아니였을까하는데 뭐 하나 정리를 제대로 안해두는 내 성격상에 여태 까맣게 잊고있다가 최근에 flutter를 공부하다 다시 생각이 났다.
개념자체는 엄청 신기하거나 그러진 않고 앱의 화면에 대한 정보를 서버에서 저장해놓고 앱은 해당 정보를 받아와서 그려는식의 웹뷰를 네이티브 형식으로 구현한 느낌이다.
아 물론 이 설명은 내가 슬쩍 검색해보고 나오는 게시글들의 감상이다
이런식이면 이게 웹앱인지 네이티브앱인지 아리송해지긴하는데 솔직히 요새 서버에서 데이터 받아와서 테이블이나 리스트에 뿌려주는거 보면 어처피 이게 그거 아닌가 싶어서 오히려 왜 방법이 스타트업 사이에서 굴러지고 있는 사람들이 주로 쓰고있지않은건가 의문이든다
요즘은 애플의 앱심사가 정말 빨라졌다지만 그래도 aws에서 파이프라인만들어놔서 빌드하면 자동으로 게시가 되는거 웹페이지에 비교하기에는 너무 천지차이이고 이제막 시작하는 앱이면 이곳저곳 버그가 산재해있거나 바뀌는 아이템으로 앱업데이트를 자주해야하는 상황에선 정말 답답하게된다
이런 상황으로 이전에는 앱시작시 팝업 정보가 있는지 서버에 저장해놨다가 팝업정보가 있으면 앱시작시 띄여주고 해당 팝업 선택시 웹페이지로 연결해주는 방식으로 했었는데 (내 이전직장들이 죄다 망해버려서 오랫동안 쓰이진 않았다) 이런것보단 차라리 server driven ui가 맞지않을까 싶다
UI는 네이티브로 고정해두고 내부 아이템들의 서버변화를 줘서하는거의 크게 다르지 않아서 굳이 기존앱의 변경하는 메리트는 없어보인다
그런데도 이 서버드리븐방식의 글을쓰게된이유는...
블로그에 글을 너무 안올리고있고 거의 반년째 일이 없어 날백수생활을 하고있기때문이다.
뭐라도 해야지....
그렇게 난 flutter로 server driven ui의 구현에 대한 테스트앱을 만들어보기로 한다.
1. 아이디어 정리
원래는 하루동안 잠깐만 하려고 했던게 조금 쓰고 이제서야 다시 잡았다.
현 시점이 0번항을 적고 나서 시일이 좀 지난 시점이라 당시 어떤 생각을 했는지 좀 잊어버렸다
살짝 짜둔 코드를 보며 다시 아이디어를 정리하고 가야겠다.
내 앱은 우선
전체 구성은 변경하지 않도록 했다 이것도 잘 생각해보면 앱 구성자체를 코드형태로 서버에 저장했다가
스플래시화면에서 받아서 렌더링하는 방법으로 해도 될듯하긴한데 이렇게 하면 차라리 웹앱으로 하는게 맞지않나 싶어서 이것은 포기했다
그렇다면 어느정도 틀만 만들어두고 해당 틀내용만 받아오는식으로 할 생각인데
대강 각 탭 페이지는 라우트로 만들어두고
각 페이지는 카드로 구성되어 있고 각 카드는 몇 종류로 나눠있으며 해당 카드를 미리 만들어두어( 스위프트 uikit의 테이블셀 처럼 )
페이지 첫 로드시 각 카드의 순서, 종류, 종류에 따른 내부 컨텐츠 값을 받아와서 화면에 그려주는 방식으로 하려고 한다
생각이 깊어질수록 플러터 기본 제공 위젯을 이용하면되는게 아닌가 싶어지기도하는데 이래서 따로 안만들어두고 차라리 웹앱으로 가던가 하는건가....
특정 디자인이 이미 구현되어 있고 꽤나 다른 앱과 다른 유니크한 디자인이라면 일종의 UIkit을 만들어서 제공하는게 있을지도 모르겠다
대기업 앱엔 있을지도?
뭐 아무튼 이번 프로젝트는 방법이 구현이 되는가가 중요하기에 범위자체는 크게 하지 않을거다
많은 앱에서 사용하는 배너용 카드, 단순 글 알림카드?, 사진들어간 좀 큰카드 이렇게 세가지 정도만 화면에서 보여지고
이걸 보여주기위한 넓~직한 카드 읽기용 글카드(사진 포함) -> 단순 글카드와 사진들어간 큰카드 공용으로 쓰고
var newPicture: [String] = []
for i in 0 ..< picture.count {
for _ in 0 ..< k {
newPicture.append(picture[i])
}
}
for line in 0 ..< newPicture.count {
let arr = Array(newPicture[line])
var newLine: [String.Element] = []
for i in 0 ..< arr.count {
for _ in 0 ..< k {
newLine.append(arr[i])
}
}
newPicture[line] = newLine.map({ String($0) }).joined()
}
return newPicture
세로로 늘리고 가로로늘리고를 크게 두가지로 나눠서 처리했는데 이걸 한번에 묶어서 처리를 하는게 인기가 좋나보다.... 다른답들 다 그렇게 처리가 되어있다
2. 정수를 나선형으로 배치하기
이중배열을 시계방향으로 돌아가면서 방문하는 문제이다.
class Position {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
init(_ copy: Position) {
self.x = copy.x
self.y = copy.y
}
}
enum Direction {
case right, left, down, up
}
func solution(_ n:Int) -> [[Int]] {
var map: [[Int]] = Array(repeating: Array(repeating: 0, count: n), count: n)
// 캐릭터 현재 x,y 좌표
let position: Position = .init(x: 0, y: 0)
var checkNumber: Int = 1
func isMoveRight() -> Bool {
let startX = position.x
let startY = position.y
let nextX = startX + 1
// 맵 범위밖
if nextX >= n {
return false
}
// 이미 탐사한 지역
if map[startY][nextX] > 0 {
return false
}
return true
}
func isMoveLeft() -> Bool {
let startX = position.x
let startY = position.y
let nextX = startX - 1
if nextX < 0 {
return false
}
if map[startY][nextX] > 0 {
return false
}
return true
}
func isMoveDown() -> Bool {
let startX = position.x
let startY = position.y
let nextY = startY + 1
if nextY >= n {
return false
}
if map[nextY][startX] > 0 {
return false
}
return true
}
func isMoveUp() -> Bool {
let startX = position.x
let startY = position.y
let nextY = startY - 1
if nextY < 0 {
return false
}
if map[nextY][startX] > 0 {
return false
}
return true
}
func checkMap(position: Position) -> Bool {
if map[position.y][position.x] > 0 {
print("ERROR!!!!!")
return false
}
map[position.y][position.x] = checkNumber
checkNumber += 1
return true
}
// 시작 위치
_ = checkMap(position: position)
// 순서 right down left up
var direction: Direction = .right
while true {
let beforPosition: Position = .init(position)
// 이동 가능 체크
if !(isMoveRight() || isMoveLeft() || isMoveUp() || isMoveDown()) {
break
}
// 이동
switch direction {
case .right:
if isMoveRight() {
position.x += 1
} else {
direction = .down
}
case .down:
if isMoveDown() {
position.y += 1
} else {
direction = .left
}
case .left:
if isMoveLeft() {
position.x -= 1
} else {
direction = .up
}
case .up:
if isMoveUp() {
position.y -= 1
} else {
direction = .right
}
}
if beforPosition.x == position.x && beforPosition.y == position.y {
continue
}
// 맵 수정
_ = checkMap(position: position)
}
return map
}
이동하는걸 각 기능으로 나눠서 방향지정까지 해놨는데
솔직히 너무 과했다 쓸모없다
이렇게 해두면 그야 탐색 순서가 달라져도 써먹을 부분이 많겠지만 너무 오버했다
게임을 구현한다고 생각하니 뭔가 필받아서 머리가 나빠졌나보다
중간에 이동가능체크 라고 넣어둔부분은
추가로 루프 탈출지점을 깜빡해서 나중에 넣은 부분이라 저런다만 저런건 미리 계산해두고 사용하는게 좋을듯하다
아니 아예 인덱스부분만 골라서 숫자 올리는게 더 코드가 짧을지도 모르겠다
3. 정사각형으로 만들기
이중배열을 n형태의 배열로 만드는 문제, 줄수든 줄내의 요소수든 가장 큰값을 기준으로 0을 채워야한다
var maxECount: Int = 0
for e in arr {
maxECount = max(maxECount, e.count)
}
let targetCount: Int = max(arr.count, maxECount)
var resultArr: [[Int]] = Array(repeating: Array(repeating: 0, count: targetCount), count: targetCount)
for line in 0 ..< resultArr.count {
if line >= arr.count {
continue
}
for i in 0 ..< resultArr[line].count {
if i >= arr[line].count {
continue
}
resultArr[line][i] = arr[line][i]
}
}
return resultArr
이번에도 그냥 문제 내용대로
가장 큰수를 구하고 해당 큰수대로 정사각형 배열을 만들어줬다
문제는 기존 배열을 늘리는거지만 새롭게 만들어준거에 복사해주는것으로 대체.
그렇게 기초 트레이닝 문제가 끝!
마지막일자꺼는 왜 5문제가 아닐까....
24일날 시작하여 대부분은 25일까지 완료를 짖고 흥미가 떨어져 도전과제로 풀리는것만 풀다보니
일자로만 11일이 걸렸다.
하루에 day가 두개씩 열리는거보면 사이트에서 노린 기초 트레이닝 일자는 대략 14일 정도 2주가 아니였을까?
"사이트의 노림수대로 일자가 끝난거야 " 라며 자신을 위로하며 기초트레이닝 문제를 마무리한다.
한 이틀 정도만 꽤나 열심히 진행했고 그후로는 그냥 한두문제씩 풀고있는데 끝이보이는것도 신기하다
1. 전국대회선발고사
모든 사람의 등수와 이 사람들의 참가 가능여부를 담은 배열 두개를 넣어 마지막 계산식을 반환하는문제
var trueIndex: [Int] = []
for( i,b) in attendance.enumerated() {
if !b { continue }
trueIndex.append(i)
}
var playerRanks: [Int] = []
for i in trueIndex {
playerRanks.append(rank[i])
}
playerRanks.sort()
let top3PlayerRanks = playerRanks[0..<3]
var top3PlayerIndex: [Int] = []
for tRank in top3PlayerRanks {
let index = rank.firstIndex { rank in
rank == tRank
}
guard index != nil else {
return -1
}
top3PlayerIndex.append(index!)
}
guard top3PlayerIndex.count == 3 else { return -2 }
let a = top3PlayerIndex[0], b = top3PlayerIndex[1], c = top3PlayerIndex[2]
return 10000 * a + 100 * b + c
문제를 읽으면서 순서대로 코드로 옴겼더니 코드가 엄청 길어졌다.
일단 답은 맞아서 타인의 답을 볼수 있게 되었는데 zip을 이용해 원본인덱스와 등수를 저장한후 정렬,필터할 생각은 못했구나
엄청 짧게 가능한거 보면 대단들하다
2. 두 수의 합
대학시절 1학년때 가산기 만들던거를 코드로 만들어봐라 하는 문제이다
/**
어떻게 할까.
그냥 더해버리면 Int최대값을 넘어버린다, uint로 하면되지 않을까 했지만 안되고, longlongint로 하면되지않나? uint64도 넘어버리나보다.
문자열을 잘라서 일부분씩 더하고 넘어가는 자리만 따로 구해서 더하는 방식으로 가야겠다.
0. 네자리수의 문자열을 int형으로 변환후 더하고 자리수가 넘어가는 수와 분리해 넘어가는수, 합쳐진 네자리숫자를 반환하는 함수를 만든다.
1. 문자열의 뒤 네자리씩 자른 배열로만든다
2. 0에서 만든 함수를 돌려서 계산
3. 각 자리수 위치에 맞게 정렬해서 반환
- 한자리로 만들어 테스트해볼까?
*/
// return "\(UInt64(a)! + UInt64(b)!)"
// return (오버, 더해진값)
enum cError: Error {
case outOverRange
}
// 한자리 덧셈
func add(over: Int, a: Int, b: Int) throws -> (Int, String) {
if a > 9 || b > 9 || over > 9 {
throw cError.outOverRange
}
let total: Int = over + a + b
if total < 10 {
return (0, String(total))
}
let totalArr = Array(String(total))
// let over: Int = .init(String(totalArr[..<(totalArr.count - 1)]))!
let befor = String(totalArr[..<(totalArr.count - 1)])
let over = Int(befor)!
return (over, String(totalArr.last!))
}
let aArr = Array(a).map({ String($0) }).reversed().map { $0 }
let bArr = Array(b).map({ String($0) }).reversed().map { $0 }
let maxIndex = max(aArr.count, bArr.count)
var over: Int = 0
var result: [String] = []
for i in 0 ..< maxIndex {
var aNum: Int = 0, bNum: Int = 0
if i < aArr.count {
let str = aArr[i]
aNum = Int(str)!
}
if i < bArr.count {
let str = bArr[i]
bNum = Int(str)!
}
guard let calc = try? add(over: over, a: aNum, b: bNum) else {
return "error"
}
over = calc.0
result.append(calc.1)
}
if over > 0 {
result.append("\(over)")
}
return result.reversed().joined()
만들다 보니 또 엄청길어졌다..........
또 "나는 4자리씩 끊어서 해야지" 라고 마음먹고 시작했으나 막상 1자리가 성공적으로 되니 귀찮아져서 포기
가산기를 만들다보니 디지털논리회로 강의때가 생각나 또 과거회상에 빠져 현재와의 비교되는 과거에 우울해졌다
그때는 이것저것 해보면서 재밌었는데 안타깝다
아무튼 그렇게 day25로 구성된 문제집이 day24,25 밖에 안남아서 내일로 끝이날거다
처음 시작할때 넉넉잡아 일주일정도면 끝나겠지 했던 기초문제가 2주나 걸려버린거 생각하면 누군가 나에게 프로젝트 일정은 너가 생각한거의 두배를 말하면된다라는게 다시금 떠오른다
비가 계속해서 내려서 눅눅함이 사라지지 않는 밤, 사라지지 않는 빗소리 때문인지 머리가 아파서 잊고있었다
자기전에 생각해내어서 다행.
오늘치는 19,20일치인데 20일은 이미 끝나있고 19일치에 두문제가 남았다
어서 두문제만 끝내고 게임이나 해야지
1. 배열 만들기 6
배열 순환해서 조건에 맞는 요소만 뽑아내는 문제
// i의 초기값을 0으로 설정하고 i가 arr의 길이보다 작으면 다음을 반복
var i = 0
var stk: [Int] = []
while i < arr.count {
if stk.isEmpty {
stk.append(arr[i])
i += 1
continue
}
if stk.last! == arr[i] {
_ = stk.popLast()
i += 1
continue
}
stk.append(arr[i])
i += 1
}
guard !stk.isEmpty else { return [-1]}
return stk
눅눅해서 찝찝하고 머리가 지끈지끈 거려서 아무 생각없이 그냥 문제 그대로를 코드로 바꿨더니 하는일에 비해 좀 길어보인다.
그냥 forEach로 돌리거나 filter로 돌려도 될거같기도하고?
2. 무작위로 K개의 수 뽑기
제목은 무작위지만 무작위가 아니라 뭐가 랜덤으로 나왔는지 주어진다. 랜덤도 아니였겠지만...
// var copyArr: [Int] = Set(arr).map {$0}
var copyArr: [Int] = []
for num in arr {
if copyArr.contains(num) {
continue
}
copyArr.append(num)
if copyArr.count >= k {
break
}
}
while copyArr.count < k {
copyArr.append(-1)
}
return copyArr[..<k].map { $0 }
처음엔 그냥 Set으로 중복제거한후 k개수만큼 보내면 되겠다 싶었는데
이게 처음 주어진 배열에서 순서가 변경되면 아웃처리시켜버린다 "저장된 순서대로 주어질 예정이라고 했을 때," 라는 정의를 또 제대로 안읽고 넘겨버려서 그만 또 틀렸다