programing

iOS 다운로드 및 앱 내부 이미지 저장

stoneblock 2023. 8. 22. 21:46

iOS 다운로드 및 앱 내부 이미지 저장

웹 사이트에서 이미지를 다운로드하여 앱에 영구 저장할 수 있습니까?정말 모르겠습니다만, 제 앱에 좋은 기능이 될 것입니다.

여기에 나와 있는 다른 답변들이 효과가 있을 것이라는 것은 사실이지만, 실제로는 프로덕션 코드에서 사용되어야 하는 솔루션이 아닙니다.(적어도 수정 없이는 안 됨)

문제

이러한 응답의 문제는 해당 응답이 그대로 구현되고 백그라운드 스레드에서 호출되지 않으면 이미지를 다운로드하고 저장하는 동안 메인 스레드를 차단한다는 것입니다.이거 큰일났어요.

메인 스레드가 차단되면 이미지 다운로드/저장이 완료될 때까지 UI 업데이트가 수행되지 않습니다.이것이 의미하는 바를 예로 들어 UIAactivity를 추가한다고 가정합니다.Indicator사용자에게 다운로드가 여전히 진행 중임을 보여주는 앱 보기(이 답변 내내 이를 예로 사용) 및 대략적인 제어 흐름:

  1. 다운로드 시작을 담당하는 개체가 로드되었습니다.
  2. 활동 표시기에 애니메이션을 시작하라고 말합니다.
  3. 를 하려면 를 사용합니다.+[NSData dataWithContentsOfURL:]
  4. 방금 다운로드한 데이터(이미지)를 저장합니다.
  5. 활동 표시기에 애니메이션을 중지하도록 지시합니다.

이것은 합리적인 제어 흐름처럼 보일 수 있지만 중요한 문제를 위장하고 있습니다.

메인(UI) 스레드에서 활동 표시기의 startAnimating 메서드를 호출하면 다음 번에 메인 실행 루프가 업데이트될 때까지 이 이벤트에 대한 UI 업데이트가 실제로 수행되지 않으며, 여기서 첫 번째 주요 문제가 발생합니다.

이 업데이트가 발생하기 전에 다운로드가 트리거되고 동기화 작업이므로 다운로드가 완료될 때까지 기본 스레드를 차단합니다(저장에도 동일한 문제가 있음).이렇게 하면 실제로 활동 표시기가 애니메이션을 시작할 수 없습니다.그런 다음 활동 표시기의 stopAnimating 메서드를 호출하고 모든 것이 정상일 것으로 예상하지만 그렇지 않습니다.

이 시점에서, 여러분은 아마도 다음과 같은 것을 궁금해하는 여러분 자신을 발견할 것입니다.

활동 표시기가 표시되지 않는 이유는 무엇입니까?

글쎄요, 이렇게 생각해 보세요.표시기를 시작하라고 하지만 다운로드가 시작되기 전에는 기회가 없습니다.다운로드가 완료되면 표시기에 애니메이션을 중지하도록 지시합니다.전체 작업을 통해 메인 스레드가 차단되었기 때문에 실제로 표시되는 동작은 시작하라는 지시자와 즉시 중지하라는 지시자 사이에 큰 다운로드 작업이 있었음에도 불구하고 더 많이 표시됩니다.

최상의 시나리오에서는 이 모든 것이 사용자 환경의 저하를 초래합니다(아직도 매우 좋지 않음).작은 이미지만 다운로드하고 다운로드는 거의 즉각적으로 이루어지기 때문에 이것이 큰 문제가 아니라고 생각하더라도 항상 그렇지는 않습니다.일부 사용자의 인터넷 연결 속도가 느리거나 서버 측에서 다운로드를 즉시/전혀 시작할 수 없는 문제가 있을 수 있습니다.

두 경우 모두 다운로드 작업이 완료되기를 기다리거나 서버가 요청에 응답하기를 기다리는 동안 앱은 UI 업데이트를 처리하거나 이벤트를 터치할 수 없습니다.

즉, 메인 스레드에서 동시에 다운로드하면 현재 다운로드가 진행 중임을 사용자에게 알리는 어떤 것도 구현할 수 없습니다.그리고 터치 이벤트는 메인 스레드에서도 처리되기 때문에 취소 버튼을 추가할 가능성이 사라집니다.

그런 다음 최악의 경우 다음과 같은 내용의 충돌 보고서를 받기 시작합니다.

예외 유형: 00000020 예외 코드: 0x8badf00d

코드 예 코 로 쉽 식 할 있 니 습 다 수 별 외 드 게 다 니 ▁exception ▁by으로 쉽게 식별할 수 .0x8badf00d그것은 "나쁜 음식을 먹었다"고 읽을 수 있습니다.이 예외는 워치독 타이머에 의해 발생합니다. 워치독 타이머의 업무는 메인 스레드를 차단하는 장시간 실행되는 작업을 감시하고 너무 오래 지속되면 문제가 되는 앱을 제거하는 것입니다.논쟁의 여지가 있지만, 이것은 여전히 열악한 사용자 환경 문제이지만, 만약 이것이 발생하기 시작한다면, 앱은 나쁜 사용자 환경과 끔찍한 사용자 환경 사이의 선을 넘었습니다.

다음은 동기식 네트워킹에 대한 Apple의 기술 Q&A(간단히 요약)에서 이러한 현상이 발생할 수 있는 원인에 대한 몇 가지 더 많은 정보입니다.

네트워크 응용 프로그램에서 감시 시간 초과 충돌의 가장 일반적인 원인은 메인 스레드의 동기식 네트워킹입니다.여기에는 네 가지 요인이 있습니다.

  1. 동기식 네트워킹 - 네트워크 요청을 수행하고 응답 대기를 차단합니다.
  2. 주 스레드 — 동기식 네트워킹은 일반적으로 이상적이지 않지만 주 스레드에서 동기식 네트워킹을 수행하면 특정 문제가 발생합니다.메인 스레드는 사용자 인터페이스 실행을 담당합니다.메인 스레드를 상당한 시간 동안 차단하면 사용자 인터페이스가 허용할 수 없을 정도로 응답하지 않게 됩니다.
  3. long timeout — 네트워크가 사라지면(예: 사용자가 터널로 들어가는 열차에 탑승한 경우) 일부 시간 초과가 만료될 때까지 보류 중인 네트워크 요청이 실패하지 않습니다.

...

  1. watchdog — 사용자 인터페이스의 응답성을 유지하기 위해 iOS에는 watchdog 메커니즘이 포함되어 있습니다.응용 프로그램이 특정 사용자 인터페이스 이벤트(시작, 일시 중단, 재개, 종료)에 제때 응답하지 못하면 감시 프로그램은 응용 프로그램을 종료하고 감시 시간 초과 충돌 보고서를 생성합니다.워치독이 제공하는 시간은 공식적으로 문서화되지 않지만 항상 네트워크 시간 초과보다 작습니다.

이 문제의 한 가지 까다로운 측면은 네트워크 환경에 크게 의존한다는 것입니다.네트워크 연결이 양호한 사무실에서 항상 응용 프로그램을 테스트하면 이러한 유형의 충돌이 발생하지 않습니다.그러나 모든 종류의 네트워크 환경에서 애플리케이션을 실행할 최종 사용자에게 애플리케이션을 배포하기 시작하면 이러한 충돌이 일반적으로 발생합니다.

이제, 제공된 답변이 문제가 될 수 있는 이유에 대해 횡설수설하는 것을 멈추고 몇 가지 대안적인 해결책을 제시하기 시작하겠습니다.이러한 예에서 작은 이미지의 URL을 사용해 보았으므로 고해상도 이미지를 사용할 때 더 큰 차이를 알 수 있습니다.


솔루션

UI 업데이트 처리 방법과 함께 다른 답변의 안전한 버전을 보여주는 것으로 시작하겠습니다., UIImageView, UIAactivity에 있다고 예 중 첫 입니다. 표기및보documentsDirectoryURL문서 디렉토리에 액세스하는 방법입니다.생산 코드에서는 코드 재사용성을 개선하기 위해 NSURL의 범주로 문서 디렉터리에 액세스하는 고유한 방법을 구현할 수도 있지만, 이러한 예에서는 이 방법으로도 괜찮습니다.

- (NSURL *)documentsDirectoryURL
{
    NSError *error = nil;
    NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
                                                        inDomain:NSUserDomainMask
                                               appropriateForURL:nil
                                                          create:NO
                                                           error:&error];
    if (error) {
        // Figure out what went wrong and handle the error.
    }
    
    return url;
}

또한 이러한 예에서는 시작하는 스레드가 주 스레드라고 가정합니다.다른 비동기 작업의 콜백 블록과 같은 곳에서 다운로드 작업을 시작하지 않으면 기본 동작이 될 수 있습니다.View 컨트롤러의 라이프사이클 방법(예: viewDidLoad, viewWillAppear: 등)과 같은 일반적인 위치에서 다운로드를 시작하면 예상되는 동작이 생성됩니다.

첫 에서는 이첫번예는사다다니용합음을에서째▁the다니▁will사▁use▁this를 사용할 것입니다.+[NSData dataWithContentsOfURL:]몇 가지 주요 차이점이 있습니다.먼저, 이 예에서 우리가 하는 첫 번째 통화는 활동 표시기에 애니메이션을 시작하라고 지시하는 것입니다. 그러면 이것과 동기식 예제 사이에 즉각적인 차이가 있습니다.즉시 dispatch_async()를 사용하여 글로벌 동시 대기열을 전달하여 실행을 백그라운드 스레드로 이동합니다.

이 시점에서 이미 다운로드 작업이 크게 향상되었습니다.이제 dispatch_async() 블록 내의 모든 것이 메인 스레드에서 발생하므로, 인터페이스가 더 이상 잠기지 않고 앱이 터치 이벤트에 자유롭게 응답할 수 있습니다.

여기서 주목해야 할 점은 이미지 다운로드/저장이 성공적으로 완료될 때까지 이 블록 내의 모든 코드가 백그라운드 스레드에서 실행된다는 것입니다. 이때 작업 표시기에 애니메이션을 중지하도록 지시하거나 새로 저장된 이미지를 UIImageView에 적용해야 합니다.어느 쪽이든 UI에 대한 업데이트입니다. 즉, 이러한 업데이트를 수행하려면 dispatch_get_main_queue()를 사용하여 메인 스레드를 다시 디스패치해야 합니다.그렇지 않으면 정의되지 않은 동작이 발생하여 UI가 예기치 않은 시간 후에 업데이트되거나 충돌이 발생할 수 있습니다.UI 업데이트를 수행하기 전에 항상 주 스레드로 다시 이동해야 합니다.

// Start the activity indicator before moving off the main thread
[self.activityIndicator startAnimating];
// Move off the main thread to start our blocking tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Create the image URL from a known string.
    NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
    
    NSError *downloadError = nil;
    // Create an NSData object from the contents of the given URL.
    NSData *imageData = [NSData dataWithContentsOfURL:imageURL
                                              options:kNilOptions
                                                error:&downloadError];
    // ALWAYS utilize the error parameter!
    if (downloadError) {
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
            NSLog(@"%@",[downloadError localizedDescription]);
        });
    } else {
        // Get the path of the application's documents directory.
        NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
        // Append the desired file name to the documents directory path.
        NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"GCD.png"];

        NSError *saveError = nil;
        BOOL writeWasSuccessful = [imageData writeToURL:saveLocation
                                                options:kNilOptions
                                                  error:&saveError];
        // Successful or not we need to stop the activity indicator, so switch back the the main thread.
        dispatch_async(dispatch_get_main_queue(), ^{
            // Now that we're back on the main thread, you can make changes to the UI.
            // This is where you might display the saved image in some image view, or
            // stop the activity indicator.
            
            // Check if saving the file was successful, once again, utilizing the error parameter.
            if (writeWasSuccessful) {
                // Get the saved image data from the file.
                NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                // Set the imageView's image to the image we just saved.
                self.imageView.image = [UIImage imageWithData:imageData];
            } else {
                NSLog(@"%@",[saveError localizedDescription]);
                // Something went wrong saving the file. Figure out what went wrong and handle the error.
            }
            
            [self.activityIndicator stopAnimating];
        });
    }
});

에 표시된 방법은 조기에 취소할 수 없고, 다운로드 진행 상태에 대한 아무런 정보도 제공하지 않으며, 인증 문제를 처리할 수 없으며, 특정 시간 초과 간격을 지정할 수 없다는 점을 고려하면 여전히 이상적인 솔루션이 아닙니다(여러 가지 이유).아래에서 더 나은 옵션 중 몇 가지를 다루겠습니다.

이 예에서는 iOS 7 이상을 대상으로 하는 앱에 대한 솔루션만 다룰 것입니다. iOS 8이 현재 주요 릴리스이며, Apple이 N 및 N-1 버전만 지원하는 것을 제안하고 있습니다.이전 iOS 버전을 지원해야 하는 경우 NSURL 연결 클래스와 1.0 버전의 AF 네트워킹을 확인하는 것이 좋습니다.이 답변의 수정 내역을 살펴보면, ASIHTTPRequest가 더 이상 유지 관리되지 않으며, 새로운 프로젝트에 사용되어서는 안 된다는 점에 유의해야 하지만 NSURLConnection 및 ASIHTTPRequest를 사용한 기본적인 예를 찾을 수 있습니다.


NSURL 세션

iOS 7에 도입된 NSURL 세션부터 시작하여 iOS에서 네트워킹을 수행할 수 있는 편의성을 크게 향상시킵니다.NSURLSession을 사용하면 콜백 블록으로 비동기 HTTP 요청을 쉽게 수행하고 대리인과 함께 인증 문제를 처리할 수 있습니다.그러나 이 클래스가 정말 특별한 이유는 응용 프로그램이 백그라운드로 전송되거나 종료되거나 충돌이 발생하더라도 다운로드 작업이 계속 실행될 수 있도록 허용하기 때문입니다.여기 그것의 사용법에 대한 기본적인 예가 있습니다.

// Start the activity indicator before starting the download task.
[self.activityIndicator startAnimating];

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use a session with a custom configuration
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create the download task passing in the URL of the image.
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    // Get information about the response if neccessary.
    if (error) {
        NSLog(@"%@",[error localizedDescription]);
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
        });
    } else {
        NSError *openDataError = nil;
        NSData *downloadedData = [NSData dataWithContentsOfURL:location
                                                       options:kNilOptions
                                                         error:&openDataError];
        if (openDataError) {
            // Something went wrong opening the downloaded data. Figure out what went wrong and handle the error.
            // Don't forget to return to the main thread if you plan on doing UI updates here as well.
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"%@",[openDataError localizedDescription]);
                [self.activityIndicator stopAnimating];
            });
        } else {
            // Get the path of the application's documents directory.
            NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
            // Append the desired file name to the documents directory path.
            NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"NSURLSession.png"];
            NSError *saveError = nil;
            
            BOOL writeWasSuccessful = [downloadedData writeToURL:saveLocation
                                                          options:kNilOptions
                                                            error:&saveError];
            // Successful or not we need to stop the activity indicator, so switch back the the main thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                // Now that we're back on the main thread, you can make changes to the UI.
                // This is where you might display the saved image in some image view, or
                // stop the activity indicator.
                
                // Check if saving the file was successful, once again, utilizing the error parameter.
                if (writeWasSuccessful) {
                    // Get the saved image data from the file.
                    NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                    // Set the imageView's image to the image we just saved.
                    self.imageView.image = [UIImage imageWithData:imageData];
                } else {
                    NSLog(@"%@",[saveError localizedDescription]);
                    // Something went wrong saving the file. Figure out what went wrong and handle the error.
                }
                
                [self.activityIndicator stopAnimating];
            });
        }
    }
}];

// Tell the download task to resume (start).
[task resume];

이것으로부터 당신은 알아차릴 것입니다.downloadTaskWithURL: completionHandler: method가 합니다. 여기서 method는 NSURLSessionDownloadTask입니다.-[NSURLSessionTask resume]이 호출됩니다.이것은 실제로 다운로드 작업을 시작하도록 지시하는 방법입니다.즉, 다운로드 작업을 스핀업하고 필요한 경우 시작을 보류할 수 있습니다(필요한 경우).이는 또한 태스크에 대한 참조를 저장하는 한 태스크를 활용할 수 있다는 것을 의미합니다.cancel그리고.suspend필요한 경우 작업을 취소하거나 일시 중지하는 방법.

NSURLSessionTasks의 정말 멋진 점은 KVO를 조금만 사용하면 해당 바이트 수 값을 모니터링할 수 있다는 것입니다.예상 수신 및 수신 바이트 수 속성을 NSByteCountFormater에 입력하고 사람이 읽을 수 있는 단위(예: 100KB)로 사용자에게 쉽게 다운로드 진행률 표시기를 만듭니다.

그러나 NSURL 세션에서 벗어나기 전에 다운로드 콜백 블록의 여러 다른 지점에서 메인 스레드로 다시 dispatch_async해야 하는 추악함을 피할 수 있습니다.이 경로로 이동하도록 선택한 경우 대리자와 대리자 대기열을 지정할 수 있는 초기화 프로그램으로 세션을 초기화할 수 있습니다.이렇게 하려면 콜백 블록 대신 대리자 패턴을 사용해야 하지만 백그라운드 다운로드를 지원하는 유일한 방법이기 때문에 유용할 수 있습니다.

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:self
                                                 delegateQueue:[NSOperationQueue mainQueue]];

AF 네트워킹 2.0

AF네트워킹에 대해 들어본 적이 없다면 IMHO가 네트워킹 라이브러리의 전부입니다.이것은 Objective-C를 위해 만들어졌지만 Swift에서도 작동합니다.저자의 말을 빌리자면:

AF네트워킹은 iOS와 Mac OS X를 위한 즐거운 네트워킹 라이브러리입니다.Cocoa에 내장된 강력한 고급 네트워킹 추상화를 확장하여 Foundation URL Loading System 위에 구축되었습니다.잘 설계되고 기능이 풍부한 API를 사용하는 모듈형 아키텍처를 갖추고 있습니다.

AFNetworking 2.0은 iOS 6 이상을 지원하지만, 이 예에서는 NSURL 세션 클래스 주변의 모든 새 API를 사용하기 때문에 iOS 7 이상이 필요한 AFHTTP Session Manager 클래스를 사용합니다.이는 위의 NSURL 세션 예제와 많은 코드를 공유하는 아래 예제를 읽으면 분명해집니다.

하지만 몇 가지 차이점을 지적하고 싶습니다.먼저 자신의 NSURL 세션을 만드는 대신 내부적으로 NSURL 세션을 관리하는 AFURL Session Manager 인스턴스를 만듭니다.이렇게 하면 다음과 같은 편리한 방법을 활용할 수 있습니다.-[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]이 방법의 흥미로운 점은 이 방법을 사용하면 특정 대상 파일 경로, 완료 블록 및 NS 진행률 포인터 입력을 사용하여 다운로드 작업을 상당히 간결하게 만들 수 있으며 다운로드 진행률에 대한 정보를 확인할 수 있다는 것입니다.여기 예가 있어요.

// Use the default session configuration for the manager (background downloads must use the delegate APIs)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use AFNetworking's NSURLSessionManager to manage a NSURLSession.
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create a request object for the given URL.
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
// Create a pointer for a NSProgress object to be used to determining download progress.
NSProgress *progress = nil;

// Create the callback block responsible for determining the location to save the downloaded file to.
NSURL *(^destinationBlock)(NSURL *targetPath, NSURLResponse *response) = ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    // Get the path of the application's documents directory.
    NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
    NSURL *saveLocation = nil;
    
    // Check if the response contains a suggested file name
    if (response.suggestedFilename) {
        // Append the suggested file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
    } else {
        // Append the desired file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"AFNetworking.png"];
    }

    return saveLocation;
};

// Create the completion block that will be called when the image is done downloading/saving.
void (^completionBlock)(NSURLResponse *response, NSURL *filePath, NSError *error) = ^void (NSURLResponse *response, NSURL *filePath, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // There is no longer any reason to observe progress, the download has finished or cancelled.
        [progress removeObserver:self
                      forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
        
        if (error) {
            NSLog(@"%@",error.localizedDescription);
            // Something went wrong downloading or saving the file. Figure out what went wrong and handle the error.
        } else {
            // Get the data for the image we just saved.
            NSData *imageData = [NSData dataWithContentsOfURL:filePath];
            // Get a UIImage object from the image data.
            self.imageView.image = [UIImage imageWithData:imageData];
        }
    });
};

// Create the download task for the image.
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request
                                                         progress:&progress
                                                      destination:destinationBlock
                                                completionHandler:completionBlock];
// Start the download task.
[task resume];

// Begin observing changes to the download task's progress to display to the user.
[progress addObserver:self
           forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
              options:NSKeyValueObservingOptionNew
              context:NULL];

인스턴스의 중 에 이 "NSProgress"를 .-[NSObject observeValueForKeyPath:ofObject:change:context:].레이블을 이 경우 다운로드 진행률을 표시하기 위해 진행률 레이블을 업데이트하는 방법에 대한 예를 포함했습니다.정말 쉬워요. 메서드 NSProgress가 .localizedDescription진행 상황 정보를 사람이 읽을 수 있는 현지화된 형식으로 표시합니다.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    // We only care about updates to fractionCompleted
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
        NSProgress *progress = (NSProgress *)object;
        // localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
        self.progressLabel.text = progress.localizedDescription;
    } else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

프로젝트에서 AF네트워킹을 사용하려면 설치 지침을 따르고 다음을 수행해야 합니다.#import <AFNetworking/AFNetworking.h>.

알라모파이어

마지막으로, 알라모파이어를 사용한 마지막 예를 보여드리겠습니다.이것은 스위프트의 네트워킹을 케이크 워크로 만드는 도서관입니다.저는 이 샘플의 내용에 대해 자세히 설명할 인물이 부족합니다. 하지만 이 샘플은 마지막 예시와 거의 같은 역할을 합니다. 단지 논쟁의 여지가 있을 정도로 더 아름다운 방식으로 말입니다.

// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
    var error: NSError?
    // Get the documents directory
    let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
        inDomain: .UserDomainMask,
        appropriateForURL: nil,
        create: false,
        error: &error
    )
    
    if let error = error {
        // This could be bad. Make sure you have a backup plan for where to save the image.
        println("\(error.localizedDescription)")
    }
    
    // Return a destination of .../Documents/Alamofire.png
    return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}

Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
    .validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
    .validate(contentType: ["image/png"]) // Require the content type to be image/png.
    .progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
        // Create an NSProgress object to represent the progress of the download for the user.
        let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
        progress.completedUnitCount = totalBytesRead
        
        dispatch_async(dispatch_get_main_queue()) {
            // Move back to the main thread and update some progress label to show the user the download is in progress.
            self.progressLabel.text = progress.localizedDescription
        }
    }
    .response { (request, response, _, error) in
        if error != nil {
            // Something went wrong. Handle the error.
        } else {
            // Open the newly saved image data.
            if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
                dispatch_async(dispatch_get_main_queue()) {
                    // Move back to the main thread and add the image to your image view.
                    self.imageView.image = UIImage(data: imageData)
                }
            }
        }
    }

캐시 기능이 있는 비동기식 다운로드 이미지

캐시 기능이 있는 비동기식 다운로드 이미지

백그라운드에서 이미지를 다운로드하는 데 사용할 수 있는 저장소가 하나 더 있습니다.

할 수 , 앱번내에들 서 아 없 사 수 있 할 습 니 용 다 만 지 수 저 할 장 무 것 도 니 있 다 습 ' ▁use ▁you ▁app ▁can 수 ▁the , ▁you ▁but ▁anything ▁inside 앱 ▁bundle s+[NSData dataWithContentsOfURL:]앱의 문서 디렉터리에 이미지를 저장합니다. 예:

NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];

정확하게 영구적인 것은 아니지만 적어도 사용자가 앱을 삭제할 때까지 유지됩니다.

그게 메인 컨셉입니다.재미있게 놀아요 ;)

NSURL *url = [NSURL URLWithString:@"http://example.com/yourImage.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingString:@"/yourLocalImage.png"];
[data writeToFile:path atomically:YES];

지금은 IO5를 사용하고 있기 때문에 더 이상 디스크에 이미지를 쓸 필요가 없습니다.
이제 코어 데이터 이진 속성에 "외부 저장소 허용"을 설정할 수 있습니다.사과 릴리스 노트에 따르면 다음을 의미합니다.

이미지 미리 보기와 같은 작은 데이터 값은 데이터베이스에 효율적으로 저장할 수 있지만 큰 사진이나 다른 미디어는 파일 시스템에서 직접 처리하는 것이 가장 좋습니다.이제 관리되는 개체 속성의 값을 외부 레코드로 저장할 수 있도록 지정할 수 있습니다. setAllows 참조외부 이진 데이터 스토리지:활성화된 경우 Core Data는 데이터를 직접 데이터베이스에 저장할지 또는 URI를 관리하는 별도의 파일에 저장할지 여부를 값별로 휴리스틱하게 결정합니다.이 옵션을 사용하는 경우 이진 데이터 속성의 내용을 기준으로 쿼리할 수 없습니다.

다른 사람들이 말했듯이, 사용자 인터페이스를 차단하지 않고 배경 스레드에서 사진을 다운로드해야 하는 경우가 많습니다.

이 경우 제가 가장 좋아하는 해결책은 다음과 같은 블록이 있는 편리한 방법을 사용하는 것입니다. (credit -> iOS: 이미지를 비동기적으로 다운로드하는 방법(그리고 UITableView 스크롤을 빠르게)

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               if ( !error )
                               {
                                   UIImage *image = [[UIImage alloc] initWithData:data];
                                   completionBlock(YES,image);
                               } else{
                                   completionBlock(NO,nil);
                               }
                           }];
}

그리고 그것을 이렇게 부릅니다.

NSURL *imageUrl = //...

[[MyUtilManager sharedInstance] downloadImageWithURL:[NSURL URLWithString:imageURL] completionBlock:^(BOOL succeeded, UIImage *image) {
    //Here you can save the image permanently, update UI and do what you want...
}];

광고 배너를 다운로드하는 방법은 다음과 같습니다.큰 이미지나 여러 이미지를 다운로드하는 경우에는 백그라운드에서 수행하는 것이 가장 좋습니다.

- (void)viewDidLoad {
    [super viewDidLoad];

    [self performSelectorInBackground:@selector(loadImageIntoMemory) withObject:nil];

}
- (void)loadImageIntoMemory {
    NSString *temp_Image_String = [[NSString alloc] initWithFormat:@"http://yourwebsite.com/MyImageName.jpg"];
    NSURL *url_For_Ad_Image = [[NSURL alloc] initWithString:temp_Image_String];
    NSData *data_For_Ad_Image = [[NSData alloc] initWithContentsOfURL:url_For_Ad_Image];
    UIImage *temp_Ad_Image = [[UIImage alloc] initWithData:data_For_Ad_Image];
    [self saveImage:temp_Ad_Image];
    UIImageView *imageViewForAdImages = [[UIImageView alloc] init];
    imageViewForAdImages.frame = CGRectMake(0, 0, 320, 50);
    imageViewForAdImages.image = [self loadImage];
    [self.view addSubview:imageViewForAdImages];
}
- (void)saveImage: (UIImage*)image {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent: @"MyImageName.jpg" ];
    NSData* data = UIImagePNGRepresentation(image);
    [data writeToFile:path atomically:YES];
}
- (UIImage*)loadImage {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent:@"MyImageName.jpg" ];
    UIImage* image = [UIImage imageWithContentsOfFile:path];
    return image;
}

url에서 비동기식으로 이미지를 다운로드한 다음 objective-c:-> 원하는 위치에 저장하는 코드가 있습니다.

    + (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
        {
            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
            [NSURLConnection sendAsynchronousRequest:request
                                               queue:[NSOperationQueue mainQueue]
                                   completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                       if ( !error )
                                       {
                                           UIImage *image = [[UIImage alloc] initWithData:data];
                                           completionBlock(YES,image);
                                       } else{
                                           completionBlock(NO,nil);
                                       }
                                   }];
        }

AF 네트워킹 라이브러리를 사용하여 이미지를 다운로드하고 해당 이미지가 UI 테이블 보기에서 사용 중인 경우 cellForRowAt의 아래 코드를 사용할 수 있습니다.인덱스 경로

 [self setImageWithURL:user.user_ProfilePicturePath toControl:cell.imgView]; 
 
-(void)setImageWithURL:(NSURL*)url toControl:(id)ctrl
{
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        if (image) {
            if([ctrl isKindOfClass:[UIButton class]])
            {
                UIButton btn =(UIButton)ctrl;
                [btn setBackgroundImage:image forState:UIControlStateNormal];
            }
            else
            {
                UIImageView imgView = (UIImageView)ctrl;
                imgView.image = image;
            }

} } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"No Image"); }]; [operation start];}

NSURLSessionDataTask를 사용하여 UI를 차단하지 않고 이미지를 다운로드할 수 있습니다.

+(void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
 {
 NSURLSessionDataTask*  _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error != nil)
        {
          if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
                {
                    completionBlock(NO,nil);
                }
         }
    else
     {
      [[NSOperationQueue mainQueue] addOperationWithBlock: ^{
                        dispatch_async(dispatch_get_main_queue(), ^{
                        UIImage *image = [[UIImage alloc] initWithData:data];
                        completionBlock(YES,image);

                        });

      }];

     }

                                            }];
    [_sessionTask resume];
}

다음은 이미지 또는 일반적으로 파일을 다운로드하고 저장하기 위한 Swift 5 솔루션입니다.Alamofire:

func dowloadAndSaveFile(from url: URL) {
    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        documentsURL.appendPathComponent(url.lastPathComponent)
        return (documentsURL, [.removePreviousFile])
    }
    let request = SessionManager.default.download(url, method: .get, to: destination)
    request.validate().responseData { response in
        switch response.result {
        case .success:
            if let destinationURL = response.destinationURL {
                print(destinationURL)
            }
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
}

언급URL : https://stackoverflow.com/questions/6238139/ios-download-and-save-image-inside-app