티스토리 뷰

프로필 화면을 초기화 할 때 데이터를 코드에서 직접 생성해서 초기화 하고 있다. 로컬 데이터가 있으면 파일을 읽어서 초기화 하도록 만들긴 했지만 프로그램에서 직접 처리한다는 점은 변함이 없다. 이 프로필 데이터를 서버에서 받아올 수 있도록 만들어 보자.

 

 

 

서버

서버 개발을 다루는 포스팅이 아니기 때문에 정말 간단하게 서버를 구축하고 통신하는 부분으로 넘어가자.

먼저 서버를 만들어야 한다. 서버는 어떻게 만들어야 하나....

서버를 만들기 위한 여러가지 프레임 워크가 있지만 spring-boot가 가장 먼저 눈에 띄어서 spring-boot로 만들기로 했다. start.spring.io 로 들어가서 spring-boot 프로젝트를 생성하고 다운 받았다.

 

빈 프로젝트를 생성할 수 있다.

 

콘솔에서 mvnw라는 wrapper를 실행시켜서 jar를 만들고 jar를 실행해서 서버를 구동시킬 수도 있지만 IDE를 사용하면 GUI환경에서 작업할 수 있다. spring-boot는 java를 사용하니까 java IDE를 써야 하는데, IntelliJ가 뭔가 좀 더 좋다고 하니 잘 모르지만 IntelliJ를 설치하고 다운받은 demo 프로젝트를 열었다.

 

뭔가 굉장이 smart하고 intelli 한 느낌이다.

 

spring-boot는 rest 서버를 만들기 편하게 많은 annotation을 지원한다. 프로필 정보를 요청할 수 있는 rest url을 만들었다. 이제 외부에서 이 url로 요청하면 프로필 데이터를 돌려줄 수 있다.

 

@GetMapping("/profile")
public String responseProfile(@RequestParam(value = "name", defaultValue = "non")String name) {
	// return profile data
	return name
}

 

프로필 데이터를 저장할 데이터 베이스가 필요하다. 이건 또 어떻게 만드나....

마찬가지로 여러가지 데이터 베이스가 있다. oracleDB가 가장 대중적 이라고 하는데, 로고도 빨갛고 이름도 별로 마음에 들지 않는다. spring-boot와 비슷하게 초록색 로고를 쓰는 mongoDB를 사용하기로 했다. mongoDB를 설치할 때 GUI로 데이터 베이스를 관리할 수 있는 compass라는 툴도 있길래 같이 설치했다.

 

일단 하나만 넣었다.

 

MongoTemplate 이라는, mongoDB 쿼리 결과를 java 객체로 만들어 주는 class가 있다. 쿼리를 만들어서 데이터를 검색하고 검색된 데이터를 반환하도록 했다.

 

@GetMapping ProfileData responseProfile(@RequestParam(value = "name", defaultValue = "non")String name) {
	Query query = new Query(Criteria.where("name").is(name));
	ProfileData data = monoTemplate.findOne(query, ProfileData.class, "profile");

	if (data == null) {
		throw new RuntimeException();
	}
	return data;
}

 

ProfileData는 swift에서 만든 것과 같은 구조를 가진 class이며, 검색이 정상적으로 실행 되었을 때 respons는 아래와 같게 나온다. swift에서 사용하던 JSON이랑 같은 모습이다.

 

이 json을 사용해서 view를 만들면 되겠지?

 

사실 이렇게 구축하는 동안 수많은 시행착오가 있었다. 그리고 네트워크 설정이나 개념에 대해서는 그냥 넘어갔는데 이 부분은 개발환경 설정 같은 카테고리에 새로 포스팅을 하면서 정리해야 될 것 같다.

링크 : 작성중...

 

 

 

URLSession

http 통신을 하기 위한 session을 제공한다. 기본 기능을 제공하는 shared singleton 인스턴스를 사용할 수도 있고, 새로운 session을 만들어서 사용할 수도 있다. 현 시점에서 복잡한 기능은 필요 없기 때문에 shared를 사용한다.

URLSession은 NSData를 보내고 받을 수 있는 DataTask, 파일을 업로드 하거나 다운로드 받을 수 있는 Upload/Download Task, RFC 6455 protocol WebSocket Task. 이렇게 4종류의 task를 실행할 수 있는 API를 제공한다. 여기서 필요한 task를 만들고 실행시킬 수 있는데, JSON String은 NSData와 쌍방 encode/decode가 되기 때문에 DataTask를 사용해서 위에서 만든 서버와 통신 할 수 있다.

 

 

 

URLRequest

DataTask(=URLDataTask)는 URL이나 URLRequest를 필요로 한다. URL은 말 그대로 접속할 주소를 말하며, URLRequest struct는 URL과 http header 정보를 가지고 있다.

 

 

 

URLComponent

URL struct는 URLRequest를 만들기 위한 url정보를 저장한다. 여기에는 port, query 등 다양한 정보가 있는데, URLComponent struct는 이 정보들을 더 쉽게 수정할 수 있도록 한번 더 감싸준다. 예를 들어 query를 추가할 때 URLQueryItem을 추가하면 쉽게 query parameter를 추가할 수 있다.

코드를 보기 전에 서버가 구조를 다시 보자.

 

서버ip/profile 주소로 request parameter를 입력해서 GET요청을 해야 한다.

 

URLComponent부터 다시 위로 올라가면서 하나씩 만들어 보자. requestProfile은 이름을 입력 받고, 그 이름으로 서버에 검색을 요청해서 데이터를 받을 수 있다. 먼저 URLComponent를 만든다.

 

func requestProfile(name: String, completion: @escaping(_: PersonData) -> Void {
	guard let urlStr = (serverURL + "/" + name).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
		return
	}

	var profileURL = URLComponent(string: urlStr)
	profileURL?.querayItems = [URLQueryItem(name: "name", value: name)]

	guard let url = profileURL.url else {
		return
	}

 

서버는 query에 있는 이름으로 데이터 베이스에서 검색하기 때문에 query를 추가했다. 그리고 이제 이 url로 URLRequest를 만든다.

 

// in requestProfile
let request = URLRequest(url: url)
request.httpMethod = "GET"

 

서버에서 GET 요청을 매핑했기 때문에 request도 GET으로 보낸다. 그리고 마지막으로 DataTask를 만들어서 실행한다. 간단한 요청이기 때문에 새로운 session을 만들지 않고 shared session을 사용한다. completion에서는 받은 데이터를 PersonData로 decode하고, completion closure로 전달해서 처리할 수 있도록 한다.

 

// in reqeustProfile
let session = URLSession.shared
let task = session.dataTask(with: request, completion: {data, response, error in
	
	guard let data = data else {
		return
	}

	if let serverData = try ? JSONDecoder.decode(PersonData.self, from: data) {
		completion(serverData)
	}
}
task.resume()

 

 

 

Fatch

requestProfile을 을 만들면서 응답을 받았을 때 decode된 serverData를 받을 수 있는 closure를 호출하도록 했다. URLSession은 main thread가 아니라 새로운 thread에서 실행되기 때문에, 이 응답이 언제 일어날 지 알 수가 없다. 예를 들어 응답을 받았을 때 데이터를 저장하도록 하고, view들은 이 저장된 데이터로 초기화하도록 만든다고 하면 말은 괜찮아 보이지만 경우에 따라 정상적으로 동작하지 않을 수 있다.

 

응답이 view 생성 전에 올 것이라는 보장이 없다.

 

그래서 completion closure를 추가했고, 응답이 왔을 때 실행되는 이 closure에서 view를 다시 fatch할 수 있다. 단, view의 내용은 main thread에서만 수정할 수 있는데, URLSession은 새로운 thread에서 실행된다고 했다. 즉, 이 closure를 호출하는 thread도 main thread가 아니라는 것이다.

DispatchQueue를 사용해서 main thread가 처리할 수 있도록 해준다.

 

override func viewDidLoad() {
	super.viewDidLoad()
	
	requestProfile(name: "사나", completion: {data in
		DispatchQueue.main.async {
			self.fatchViews(with: data)
		}
	}

	// make default views...
}

 

fatchViews는 전달받은 데이터로 다시 내용을 설정한다. 프로필 사진과, 이름은 처음에 기본값으로 그리고 있기 때문에 내용만 변경하고, 상세 정보는 데이터를 기반으로 새롭게 추가한다.

 

func fatchViews(with data: PersonData) {
	profileImage.image = UIImage(named: data.profileImagePath)
	nameLabel.text = data.name
	// ...
	
	// add infos...
}

 

 

 

Result

데이터를 서버에서 받아오니까 지금은 asset에 이미지를 직접 추가해서 쓰고 있는 프로필 사진도 서버에 올라가있는 사진으로 만들어지게 할 수 있을 것 같다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함