100 iOS RSS Parsing, Thread
** 한겨레 신문사의 RSS(Rich Summary Site)를 파싱해서 출력
=> RSS : 빠른 속도로 업데이트 되는 데이터를 실시간으로 제공해서 클라이언트가 원하는 형태로 출력하도록 해주는 서비스 - 데이터 포맷은 대부분 XML을 이용
=> 한겨레 신문사 RSS URL : http://www.hani.co.kr/rss/
=> 데이터 구조를 확인해서 어떤 데이터를 추출할 것인지 결정
title과 link 태그의 내용만 추출 - 첫번째 나오는 데이터는 사용하지 않는다.
=> 데이터를 ㅇ떻게 저장
1. 모든 데이터를 각각의 변수에 저장
title1, link1, title2, link2...
2. 데이터를 배열에 모아서 사용
titles = [String]
links = [String]
3. 행 단위 구조의 자료구조를 만들어서 배열에 모아서 사용
Struct haniVO{
var title:String
var link:String
}
data = [haniVO]
=> link 들을 테이블 뷰에 출력하고 셀을 클리갛면 link를 WebView를 이용해서 출력
=> XML 파싱을 하는 방법
1. DOM(Document Object Model) Parsing
=> DOM : 문서 객체 모델로 변역하는데 html 내의 태그를 자바스크립트에서 사용하기 위해서 객체화한 것
document.getElementById가 이 작업을 한것
=> DOM Parsing : 문서 전체를 트리 형태로 메모리에 펼쳐 놓고 원하는 항목을 찾아내는 방식 - 메모리 ㅏㅅ용량은 많지만 파싱 석도가 빠르고 편집이 가능
=> 모든 언어에서 방법이 유사하다 - id, class, tag 이름을 가지고 찾아서 사용
2. SAX Parsing
=> 문서를 하나하나 읽어가면서 콜백메소드를 호출하고 파싱해 나가는 방식
=> 메모리 사용량이 적다는 장점이 있지만 속도는 DOM보다 느리고 문서 전체를 파악할 수 없기 때문에 편집을 할 수 없다.
=> 거의 모든 언어에서 제공하고 방법이 유사
3. 예전에는 SAXParsing을 많이 했는데 최근에는 DOM 파싱을 많이 한다.
=> 실습
1. 필요한 클래스들을 생성
1) title 과 link를 저장할 구조체 파일을 생성 - Hani.swift
import Foundation
//데이터를 저장하기 위한 용도의 구조체
struct Hani{
var title : String!
var link : String!
}
=> 뷰 컨트롤러를 생성할 때는 전체 화면을 TableView나 CollectionView를 사용할 것인지 결정해서 UIViewController 또는 UITableViewController 나 UICollectionViewController로 부터 상속을 받을 것인지를 결정
2) title을 테이블에 출력할 ViewController 클래스를 생성
=> UITableViewController 클래스로부터 상속받는 클래스를 생성 - TitleViewController
3) link를 WebView에 출력할 ViewController클래스를 생성
=> UIViewController 클래스로부터 상속받는 클래스르 생성 : LinkViewController
2. 화면에 배치하고 디자인 - Main.storyboard에서 수행
1) TableViewController를 배치하고 Class 속성과 Storyboard ID 속성을 수정 : TitleViewController
=> 추가한 뷰 컨트롤러를 시작 뷰 컨트롤러로 설정 : 다섯 번째 인스텍터의 is Initial View Controller 항목을 체크
=> 추가한 뷰 컨트롤러를 선택하고 [Editor]
2) ViewController 를 추가하고 Class속성과 storyboardID를 수정 : LinkViewController
=> 앞에 추가한 TableViewController를 선택하고 control을 누른 채 추가한 뷰 컨트롤러에 드래그 앤 드랍 한 후 push를 선택
=> WebView를 1개 배치하고 LinkViewController.swift 파일에 변수 연결 : webview
수정을 하거나 데이터를 변경할 계획이 있으면 변수를 생성
이벤트를 사용해야 하는 View는 변수를 만들고 이벤트를 선택해서 메소드를 생성
Touch Up Inside : 클릭
Value Changed : 입력된 값이나 선택된 값이 변경되었을 때
Did End On Exit : Return을 눌렀을 때
3) WKWebView(웹페이지, HTML, PDF 출력가능) 를 사용하는 경우 수행해야 하는 작업
=> WebKit Framework를 모듈에 추가해 주어야 한다.
프로젝트를 선택하고 오른쪽에 모듈을 선택 한 후 framework 란의 + 를 눌러서 추가
=> WKWebView를 사용하는 파일에 import
import WebKit
4) 일반 프로젝트의 경우는 이 단계에서 실행
=> 변수 연결이나 이벤트에 따른 메소드 연결을 확인
5) RootViewController.swift 파일에 새로 만든 뷰 컨트롤러를 출력할 수 있도록 작업
=> viewDidLoad(뷰 컨트롤러가 새로 만들어질 때 호출되는 메소드) 에서 배열의 데이터를 추가하는 코드를 작성
override func viewDidLoad() {
super.viewDidLoad()
self.title = "서버 연동 & 로컬 저장"
titles.append("XML Parsind")
subtitles.append("한겨레 신문사 RSS 출력")
}
=> 셀을 선택하는 메소드에 새로 만든 뷰 컨트롤러를 출력하는 코드를 작성
//셀을 선택했을 때 호출되는 메소드
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
let titleViewController = self.storyboard?.instantiateViewController(withIdentifier: "TitleViewController") as! TitleViewController
self.navigationController?.pushViewController(titleViewController, animated: true)
}
}
3. 한겨레 RSS 파싱해서 title 을 테이블 뷰 컨트롤러에 출력하기 - TitleViewController에서 작업
1) 테이블 뷰에 출력할 데이터를 저장할 배열을 생성 : Hani 의 배열
class TitleViewController: UITableViewController {
//파싱 결과를 저장할 배열 - 이 배열의 데이터가 테이블 뷰에 출력
var haniList = Array<Hani>()
//파싱 도중에 사용할 임시 변수를 생성
var hani : Hani!
var content : String!
2) 뷰 컨트롤러 코드 바깥에 XMLParserDelegate 프로토콜을 conform한 extension을 추가하고 파싱하는 메소드들을 작성
//XMLParsing을 위한 extension(클래스의 기능을 확장하기 위한 개념)
//각 프로토콜의 conform하는 메소드를 별도로 분리해서 구현하면 가독성이 높아진다.
extension TitleViewController : XMLParserDelegate{
//하나의 객체를 의미하는 태그가 item이고 임시변수가 hani, 실제 저장할 속성은 title, link
//태그가 열릴 때 호출되는 메소드
//elementName 이 태그 이름이고
//attributeDict 가 모든 속성을 저장할 NSDictionary
func parser(_ parser:XMLParser, didStartElement elementName:String, namespaceURI:String?, qualifiedName qName:String?, attributes attributeDict:[String:String] = [:]){
//임시 객체의 메모리 할당을 하고 속성을 읽는 작업을 수행
if elementName == "item"{
hani = Hani()
}
}
//태그 안의 내용을 만났을 때 호출되는 메소드
//foundCharacters 가 태그 안의 문자열
func parser(_ parser: XMLParser, foundCharacters string: String) {
//태그 안의 내용을 임시 변수에 저장
content = string
}
//태그가 닫힐 때 호출되는 메소드
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
//세부 속성인 겨웅네는 임시 객체의 각 프로퍼티에 데이터를 저장하고 객체에 해당하는 태그이면 이 경우는 객체를 배열에 저장
//하나의 객체를 닫는 태그가 나온 경우는 객체를 배열에 저장
//객체를 초기화
if elementName == "item"{
haniList.append(hani)
hani = nil
}
//저장하고자 하는 속성 태그가 나오는 경우에는 앞에서 읽은 내용을 각 속성에 자장하면 된다.
else if elementName == "title"{
if hani != nil{
hani.title = content
}
}else if elementName == "link"{
if hani != nil{
hani.title = content
}
}
}
}
3) viewDidLoad 메소드에서 파싱을 수행하는 코드를 추가
override func viewDidLoad() {
super.viewDidLoad()
//타이틀 설정
self.title = "한겨레"
//파싱할 URL을 생성
let url = URL(string:"http://www.hani.co.kr/rss/")
//URL의 데이터를 파싱하는 객체를 생성
let xmlParser = XMLParser(contentsOf: url!)
//파싱 시작
xmlParser?.delegate = self
xmlParser?.parse()
}
4) 실행하고 로그를 확인
020-08-26 12:07:54.234716+0900 ServerUse[816:17038] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
2020-08-26 12:07:54.234845+0900 ServerUse[816:17038] Cannot start load of Task <5EAC7CA8-E489-468A-ABEC-77F609D3CFB8>.<0> since it does not conform to ATS policy
2020-08-26 12:07:54.235052+0900 ServerUse[816:17036] NSURLConnection finished with error - code -1022
=> 위의 로그가 출력이 되면 서버가 보안 설정이 안된 경우다.
이 경우에는 Info.plist 파일에 보안 설정이 안된 서버에도 접속할 수 있도록 코드를 추가해 주어야 합니다.
5) Info.plist 파일에 ATS 설정이 되지 않은 서버에도 접속할 수 있도록 해주는 설정을 추가
=>파일을 열 때 파일을 선택하고 마우스 오른쪽을 클릭해서 [Open As] - [Source Code]를 실행
<!-- ATS 설정이 되지 않은 서버에도
접속할 수 있도록 설정 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
6) 테이블 뷰 출력 관련 메소드 수정
//테이블 뷰 출력 관련 메소드
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return haniList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if cell == nil{
cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
}
//데이터 찾아오기
let temp = haniList[indexPath.row]
cell!.textLabel!.text = temp.title
cell!.accessoryType = .disclosureIndicator
return cell!
}
}
4. TitleViewController에서 셀을 선택했을 때 셀에 해당하는 Link를 LinkViewController의 WebView를 이용해서 출력하기
1) LinkViewController.swift 파일에 TitleViewController로 부터 넘겨받을 데이터 변수를 생성
class LinkViewController: UIViewController {
//상위 뷰 컨트롤러로부터 념겨주는 데이터를 저장할 변수
var link : String!
2) LinkViewController.swift 파일의 viewDidLoad 메소드에 link를 웹뷰에 출력하는 코드를 작성
class LinkViewController: UIViewController {
//상위 뷰 컨트롤러로부터 념겨주는 데이터를 저장할 변수
var link : String!
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
//문자열 주소 -> URL -> URLRequest
let url = URL(string: link)
let urlRequest = URLRequest(url: url!)
webView.load(urlRequest)
}
3) TitleViewController.swift 파일에 셀을 선택했을 때 호출되는 메소드를 재정의
//셀을 선택했을 때 호출되는 메소드
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//행 번호에 해당하는 데이터 찾기
let temp = haniList[indexPath.row]
//하위 뷰 컨트롤러 객체 생성
let linkViewController = self.storyboard?.instantiateViewController(withIdentifier: "LinkViewController") as! LinkViewController
//데이터 넘겨주기
linkViewController.link = temp.link
//푸시
self.navigationController?.pushViewController(linkViewController, animated: true)
}
** XML(eXtensible Markup Language) 와 JSON(JavaScript Object Notation)
=> 데이터포맷의 일종
=> SOAP에서는 XML을 이용하고 RESTful에서는 JSON이용
=> 여러 디바이스의 요청을 처리하기 위해서 서버를 별도로 만드는 것이 아니라 하나의 서버에서 데이터를 제공해서 여려 디바이스의 요청을 처리하기 위한 개념에서 출발
이름은 이즈라핀이고 직업은 사냥꾼 이라는 데이터를 표현한다면...
이즈라핀사냥꾼 : 각 항목의 글자수가 일정 - fwf
이름,직업
이즈라핀,사냥꾼
모신악사.악마사냥꾼 : , 로 구분 - csv(고정된 데이터를 제공할 때 가장 많이 사용)
<Persons>
<Person name="이즈라핀">
<job>사냥꾼</job>
</Person>
<Person name="모신악사">
<job>악마사냥꾼</job>
</Person>
</Persons>
=> XML 형식으로 표현
[{name:"이즈라핀", job:"사냥꾼"}, {name:"모신악사", job:"악마사냥꾼"}]
=> json 형식으로 표현
=> 최근의 언어들은 JSONParsing을 쉽게 한다 - JavaScript, Kotlin, Swift, Python
이 언어들은 함수의 매개변수로 JSON문자열을 대입하면 바로 파싱해서 객체화 해준다.
** Swift에서 JSON 생성과 파싱
1. 파싱
JSONSerialization.JSONObject(with data:Data, options opt:JSONSerialization.ReadingOptions = [])throws -> Any
=> Data의 구성에 따라 Array, Dictionary, Strin으로 강제 형변환해서 사용하면 된다.
=> 예외처리를 반드시 해야한다.
2. 생성
JSONSerialization.data(withJSONObject obj ; Any, options opt:JSONSerialization.ReadingOptions = [])throws -> Any
** 영화목록
http://cyberadam.cafe24.com/movie/list
{"count":4262,"list":[{"movieid":4264,"title":"바이러스 격리구역","subtitle":"Infected","pubdate":"2013","director":"필립 매시즈위츠|","actor":"딜라란 마틴|보 린튼|유지니아 쿠즈미나|아드리안 부|아디아 딘|니나 케이트|","genre":"공포","rating":10.0,"thumbnail":"REP_02310042540_1_1_0138.jpg","link":"https://movie.naver.com/movie/bi/mi/basic.nhn?code=104605"}
=> 전체는 Object
count : 전체 데이터 개수
list : 데이터 목록
{moved: 정수, title:문자열, subtitle:문자열, pubdate:문자열, director:문자열, actor:문자열, genre:문자열, rating:실수, thumbnail:문자열, link:문자열}
=> thumbnail의 경우는 cyberadam.cafe24.com/movieimage/파일명 을 설정하면 이미지 파일의 전체 경로
** 위의 영화목록 API에서 데이터를 가져와서 테이블 뷰에 섬네일 이미지, 제목, 장르, 평점을 출력
1. 4가지 항목을 저장할 자료구조를 생성 - DTO(Class, Struct, Tuple) | Dictionary
=> iOS 포트폴리오만 하는 경우는 tuple이나 struct를 사용 - tuple을 권장
import Foundation
//class는 참조형이고 struct는 값형
struct Movie{
var movieId:Int!
var title:String!
var genre:String
var rating:Double!
var link:String!
var thumbnail:String!
}
2. UI 생성
1) 메인 데이터를 출력할 UITableViewController로 부터 상속받는 클래스를 생성 : MovieListViewController
2) 기존의 cell로는 4가지를 출력할 수 없어서 사용자 정의 셀 클래스를 생성 : UITableViewCell로 부터 상속받는 MovieCell
=> 테이블 뷰 나 컬렉션 뷰를 사용할 때는 대부분 셀 클래스를 생성해서 출력
3) Main.storyboard 파일에 TableViewController를 배치하고 Class 속성과 Storyboard ID를 설정
4) 추가한 테이블 뷰 컨트롤러의 셀을 선택하고 Class속성과 identifier 속성을 설정
5) 셀의 높이를 조절하고 ImageView 1개 그리고 Label 3개를 배치
=> imageView에는 thumbnail이미지를 출력하고 Label 에는 타이틀, 장르, 평점을 출력
6) 셀의 각 뷰에 변수를 연결
ImageView -> imgThumbnail
Label -> lblTitle, lblGenre, lblRating
3. 화면 출력을 위한 RootViewController.swift 파일 작업
1) viewDidLoad 메소드에 배열에 문자열을 추가
titles.append("JSON Parsing")
subtitles.append("영화 목록 출력")
2) 셀을 선택하면 호출되는 메소드에 추가
//셀을 선택했을 때 호출되는 메소드
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
let titleViewController = self.storyboard?.instantiateViewController(withIdentifier: "TitleViewController") as! TitleViewController
self.navigationController?.pushViewController(titleViewController, animated: true)
}else if indexPath.row == 1{
let movieListViewController = self.storyboard?.instantiateViewController(withIdentifier: "MovieListViewController") as! MovieListViewController
self.navigationController?.pushViewController(movieListViewController, animated: true)
}
}
4. MovieListViewController 데이터를 가져와서 출력하는 코드를 작성
1) 테이블 뷰에 출력할 데이터 배열을 생성
class MovieListViewController: UITableViewController {
//테이블 뷰에 출력할 데이터 배열
var movieList = Array<String>()
2) viewDidLoad 메소드에 데이터를 다운로드 받아서 파싱하는 코드를 작성
//테이블 뷰에 출력할 데이터 배열
var movieList = Array<Movie>()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "영화목록"
//동기적으로 데이터를 다운로드 받기
let url = URL(string:"http://cyberadam.cafe24.com/movie/list")
let data = try! Data(contentsOf:url!)
//NSLog("\(data)")
//다운로드 받은 데이터를 JSON 파싱
let result = try! JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
//객체 안의 list 라는 이름의 배열 가져오기
let list = result["list"] as! NSArray
//배열 순회
for index in 0...(list.count-1){
//배열 안의 객체 가져오기
let item = list[index] as! NSDictionary
//각 요소를 꺼내서 Movie 에 저장
let movieid = (item["movieid"] as! NSNumber).intValue
let title = item["title"] as! String
let genre = item["genre"] as! String
let rating = (item["rating"] as! NSNumber).doubleValue
let link = item["link"] as! String
let thumbnail = item["thumbail"] as! String
let movie:Movie = Movie(movieId: movieid, title: title, genre: genre, rating: rating, link: link)
movieList.append(movie)
}
}
3) 출력하는 메소드 수정 및 작성
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//직접 작성한 MovieCell 생성
let cellIdentifier = "MovieCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! MovieCell
//행 번호에 해당하는 데이터 찾기
let movie = movieList[indexPath.row]
//데이터를 출력
cell.lblTitle.text = movie.title
cell.lblGenre.text = movie.genre
cell.lblRating.text = "\(movie.rating!)"
//이미지를 다운로드 받아서 출력
let imageURL = URL(string: "http://cyberadam.cafe24.com/movieimage/\(movie.thumbnail!)")
NSLog(imageURL!.description)
let imageData = try! Data(contentsOf:imageURL!)
let image = UIImage(data: imageData)
cell.imgThumbnail.image = image
return cell
}
//셀의 높이를 설정하는 메소드
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
** Thread
1. 작업의 종류
=> Process : 실행 중인 프로그램, method를 호출하는 것이 하나의 Process를 실행하는 것 메소드의 수행이 종료될 때까지 다른 작업을 수행할 수 없다.
동기적으로 수행한다.
=> Thread : Process 내의 실행 단위, 하나가 실행 중에도 다른 Thread로 제어권 이동이 가능한 것
수행 중에도 다른 작업을 수행할 수 있다.
비동기적으로 수행한다.
=> Task : Process와 Thread를 합친 단어
2. Common GateWay Interface & Application Server 방식의 차이
=> CGI는 클라이언트의 요청을 받아서 프로그램을 실행시키는 구조 - 프로세스 형태로 실행
CGI 대표적인 형태가 Perl
=> Application Server방식은 클라이언트의 요청을 받아서 객체가 필요한 메소드를 스레드 형태로 실행하는 방식
Java, PHP, asp,net(C#)
Java 나 PHP로 만들어진 프로그램을 스레드 형태로 동작하도록 해주는 소프트웨어가 WAS(Web Application Server)
3. 스레드 용어
1) Daemon Thread(Background Thread) : Daemon Thread가 아닌 스레드가 없으면 자동으로 종료되는 스레드
2) Main Thread : 애플리케이션이 시작하면 자동으로 만들어지는 스레드로 GUI 프로그래밍에서는 Main Thread에서만 UI를 갱신할 수 있다.
GUI 프로그래밍을 할 때 Main Thread에게 어떻게 메시지를 전달해서 출력할 것인가 하는 것은 중요
Android는 Handler와 AsyncTask
3) MultiThread : 동작 중인 스레드가 2개 이상인 경우
4) Critical Section(임계영역) : 공유 데이터를 사용하는 코드 영역
5) Mutual Exclusion(상호 배제) : 임계 영역에서는 다른 스레드가 사용 중인 공유 자원을 수정하면 안된다.
6) Synchronized(동기화) : 순서대로 사용하도록 해서 상호 배제 문제를 해결하는 것
7) Semaphore : 공유자원이 여러개일 때 공유 자원을 사용할 때 p연산을 그리고 반납할 대는 s연산을 수행하는 형태로 공유 자원을 관리하는 기법
4. 모바일에서의 스레드
=> 모바일은 이동 중에 네트워크를 사용하기 때문에 불안정
=> 일반적인 동기식으로 네트워크를 사용하게 되면 너무 오랜시간 기다리거나 작업을 수행하지 못하는 경우가 발생할 수 있다.
=> 모바일 운영체제에서는 네트워크 기능을 사용할 때 비동기식으로 사용하는 것을 권장
안드로이드에서는 네트워크 기능을 이용할 때 스레드를 사용하지 않으면 예외가 발생
iOS에서는 마켓에서 reject
=> 네트워크 문제 해결책의 다른 방법은 로컬에 데이터를 저장해두고 네트워크가 되지 않을 때 로컬의 데이터를 출력하는 것도 고려해봐야 한다.
** iOS의 네트워크 처리
=> 네트워크 기능을 비동기식으로 처리하는 방법
1) Thread 클래스 이용
2) 비동기 방식으로 동작하는 URLSession 과 URLConnection API 이용
3) Alamofire 와 같은 외부 라이브러리를 이용 : Web Server와의 통신을 간단하게 처리할 수 있도록 만들 API, iOS에서 웹 서버와의 통신은 대부분 직접하지 않고 이 라이브러리를 이용
=> 외부 라이브러리를 사용할 때는 라이브러리 허용 여부 그리고 버전을 확인하면서 사용해야 한다.
2. iOS에서 스레드를 생성하는 방법
1) Thread 클래스로부터 상속받는 클래스를 생성
2) main 메소드를 오버라이딩해서 스렏로 동작할 코드를 작성
3) 인스턴스를 생성하고 start()를 호출
=> UIViewController로 부터 상속받는 클래스 생성 - ThreadViewController
2) Main.storyboard에 ViewController를 추가하고 Class 속성과 Storyboard ID 속성을 변경
3) RootViewController 클래스에서 ThreadViewController를 출력하는 코드를 작성
//셀을 선택했을 때 호출되는 메소드
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
let titleViewController = self.storyboard?.instantiateViewController(withIdentifier: "TitleViewController") as! TitleViewController
self.navigationController?.pushViewController(titleViewController, animated: true)
}else if indexPath.row == 1{
let movieListViewController = self.storyboard?.instantiateViewController(withIdentifier: "MovieListViewController") as! MovieListViewController
self.navigationController?.pushViewController(movieListViewController, animated: true)
}else if indexPath.row == 2{
let threadViewController = self.storyboard?.instantiateViewController(withIdentifier: "ThreadViewController") as! ThreadViewController
self.navigationController?.pushViewController(threadViewController, animated: true)
}
}
4) ThreadViewController.swift 파일에 인스턴스 변수를 2개 선언
=> ImageView 와 Label
//화면에 출력할 이미지 뷰 와 레이블
var imageView : UIImageView!
var imageName : UILabel!
5) Thread 클래스로 부터 상속받는 클래스 생성
//url에서 데이터를 다운로드 받아서 Image로 변환한 후 ImageView에 출력하는 스레드
class ImageDownloader : Thread{
var imageView : UIImageView!
var url:URL!
override func main(){
let imageData = try! Data(contentsOf: url)
let image = UIImage(data: imageData)
imageView.image = image
}
}
6) viewDidLoad 메소드에서 스레드를 생성해서 시작하는 코드와 초기화 코드를 작성
override func viewDidLoad() {
super.viewDidLoad()
//imageView 배치
let frame = UIScreen.main.bounds
imageView = UIImageView(frame:CGRect(x:0, y:0, width:300, height: 300))
imageView.center = CGPoint(x:frame.width/2, y:frame.height/2)
self.view.addSubview(imageView)
//스레드를 생성하고 시작
let downloader = ImageDownloader()
downloader.imageView = imageView
downloader.url = URL(string: "http://img.hani.co.kr/imgdb/resize/2018/0518/00502318_20180518.JPG")
downloader.start() //스레드시작
//label 배치
imageName = UILabel(frame: CGRect(x:0, y:0, width:300, height: 50))
imageName.center = CGPoint(x:imageView.center.x, y:imageView.center.y + 200)
imageName.textAlignment = .center
imageName.text = "수지"
self.view.addSubview(imageName)
}
=> 동작은 제대로 하는 것처럼 보이지만 이미지를 출력하고 예외가 발생하고 앱은 중단 된다.
메인 스레드를 제외한 스레드에서 화면 갱신을 하면 예외 발생
** Main Thread 동작
1. performSelector(onMainThread aSelector:#selector(메소드), with arg:Any?, waitYntilDonwait:Bool) 을 호출하면 메소드가 메인 스레드에 의해서 동작
2. OperationQueue 사용 - 권장 : 안드로이드의 Handler를 사용하는 것과 같은 개념
OperationQueue.main.addOperation{
내용
}
3. 스레드에서 다운로드 받아서 출력하는 부분 수정 - main 메소드 수정
override func main(){
let queue = OperationQueue()
queue.addOperation{
let imageData = try! Data(contentsOf: self.url)
let image = UIImage(data: imageData)
OperationQueue.main.addOperation{
self.imageView.image = image
}
}
}