it-swarm-id.com

Dasar-dasar iCloud dan contoh kode

Sebagai seorang pemula, saya berjuang dengan iCloud. Ada beberapa sampel, tetapi biasanya cukup rinci (di forum pengembang ada satu untuk iCloud dan CoreData yang masif). The Apple docs OK, tapi saya masih tidak bisa melihat gambaran besarnya. Jadi tolong sampaikan kepada saya, beberapa pertanyaan ini cukup mendasar, tetapi mungkin mudah dijawab.

Konteks: Saya menjalankan aplikasi iCloud yang sangat sederhana (kode sampel lengkap di bawah). Hanya ada satu UITextView yang ditampilkan kepada pengguna dan inputnya disimpan dalam file bernama text.txt.

enter image description here

File txt didorong ke cloud dan tersedia untuk semua perangkat. Bekerja dengan baik, tetapi:

Masalah utama: Bagaimana dengan pengguna yang tidak menggunakan iCloud?

Ketika saya meluncurkan aplikasi saya (lihat kode di bawah), saya memeriksa apakah pengguna telah mengaktifkan iCloud. Jika iCloud diaktifkan, semuanya baik-baik saja. Aplikasi berjalan dan mencari text.txt di cloud. Jika ditemukan, itu akan memuatnya dan menampilkannya kepada pengguna. Jika text.txt tidak ditemukan di cloud, itu hanya akan membuat text.txt baru dan akan menampilkannya kepada pengguna.

Jika pengguna tidak mengaktifkan iCloud, tidak ada yang terjadi. Bagaimana saya memungkinkan pengguna non-iCloud masih dapat bekerja dengan aplikasi teks saya? Atau apakah saya mengabaikannya? Apakah saya perlu menulis fungsi terpisah untuk pengguna non-iCloud? Yaitu. fungsi di mana saya cukup memuat text.txt dari folder dokumen?

Apple menulis :

Perlakukan file di iCloud dengan cara yang sama seperti Anda memperlakukan semua file lain di kotak pasir aplikasi Anda.

Namun, dalam kasus saya tidak ada kotak pasir aplikasi 'normal' lagi. Ada di awan. Atau apakah saya selalu memuat text.txt saya dari disk terlebih dahulu dan kemudian memeriksa dengan iCloud jika ada sesuatu yang lebih mutakhir?

Masalah terkait: Struktur file - Sandbox vs. Cloud

Mungkin masalah utama saya adalah kesalahpahaman mendasar tentang bagaimana seharusnya iCloud bekerja. Ketika saya membuat instance baru dari UIDocument, saya harus menimpa dua metode. Pertama - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError untuk mendapatkan file dari cloud dan kemudian -(id)contentsForType:(NSString *)typeName error:(NSError **)outError untuk mendapatkan file ke cloud.

Apakah saya harus memasukkan fungsi-fungsi terpisah yang juga akan menyimpan salinan text.txt lokal ke kotak pasir saya? Apakah ini akan berfungsi untuk pengguna non-iCloud? Seperti yang saya mengerti iCloud, itu akan menyimpan salinan lokal text.txt secara otomatis. Jadi seharusnya tidak ada kebutuhan bagi saya untuk menyimpan apa pun ke dalam kotak pasir 'lama' dari aplikasi saya (mis. Seperti dulu di masa-masa pra-iCloud). Saat ini, kotak pasir saya benar-benar kosong, tetapi saya tidak tahu apakah ini benar. Haruskah saya menyimpan salinan text.txt lagi di sana? Ini terasa seperti mengacaukan struktur data saya ... karena ada satu text.txt di cloud, satu di sandbox iCloud di perangkat saya (yang akan berfungsi bahkan jika saya sedang offline), dan yang ketiga di sandbox lama yang bagus dari aplikasi saya ...


MY CODE: Kode sampel iCloud sederhana

Ini secara longgar didasarkan pada contoh yang saya temukan di forum pengembang dan pada video sesi WWDC. Saya menanggalkannya sampai minimum. Saya tidak yakin struktur MVC saya bagus. Modelnya ada di AppDelegate yang tidak ideal. Setiap saran untuk membuatnya lebih baik dipersilakan.


EDIT: Saya mencoba mengekstrak pertanyaan utama dan mempostingnya [di sini] . 4


GAMBARAN UMUM:

Overview

Bit paling penting yang memuat text.txt dari cloud:

//  AppDelegate.h
//  iCloudText

#import <UIKit/UIKit.h>

@class ViewController;
@class MyTextDocument;

@interface AppDelegate : UIResponder <UIApplicationDelegate> {
    NSMetadataQuery *_query;
}

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *viewController;
@property (strong, nonatomic) MyTextDocument *document;

@end

//  AppDelegate.m
//  iCloudText

#import "AppDelegate.h"
#import "MyTextDocument.h"
#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;
@synthesize document = _document;

- (void)dealloc
{
    [_window release];
    [_viewController release];
    [super dealloc];
}

- (void)loadData:(NSMetadataQuery *)query {

    // (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document

    if ([query resultCount] == 1) {
        // found the file in iCloud
        NSMetadataItem *item = [query resultAtIndex:0];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"AppDelegate: existing document opened from iCloud");
            } else {
                NSLog(@"AppDelegate: existing document failed to open from iCloud");
            }
        }];
    } else {
        // Nothing in iCloud: create a container for file and give it URL
        NSLog(@"AppDelegate: ocument not found in iCloud.");

        NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
        NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:@"text.txt"];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            NSLog(@"AppDelegate: new document save to iCloud");
            [doc openWithCompletionHandler:^(BOOL success) {
                NSLog(@"AppDelegate: new document opened from iCloud");
            }];
        }];
    }
}

- (void)queryDidFinishGathering:(NSNotification *)notification {

    // (3) if Query is finished, this will send the result (i.e. either it found our text.dat or it didn't) to the next function

    NSMetadataQuery *query = [notification object];
    [query disableUpdates];
    [query stopQuery];

    [self loadData:query];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
    _query = nil; // we're done with it
}

-(void)loadDocument {

    // (2) iCloud query: Looks if there exists a file called text.txt in the cloud

    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
    _query = query;
    //SCOPE
    [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
    //PREDICATE
    NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, @"text.txt"];
    [query setPredicate:pred];
    //FINISHED?
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
    [query startQuery];

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"AppDelegate: app did finish launching");
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    // Override point for customization after application launch.
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease];
    } else {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease];
    }

    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    // (1) iCloud: init

    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"AppDelegate: iCloud access!");
        [self loadDocument];
    } else {
        NSLog(@"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings");
    }


    return YES;
}

@end

UIDocument

//  MyTextDocument.h
//  iCloudText

#import <Foundation/Foundation.h>
#import "ViewController.h"

@interface MyTextDocument : UIDocument {

    NSString *documentText;
    id delegate;

}

@property (nonatomic, retain) NSString *documentText;
@property (nonatomic, assign) id delegate;

@end

//  MyTextDocument.m
//  iCloudText

#import "MyTextDocument.h"
#import "ViewController.h"

@implementation MyTextDocument

@synthesize documentText = _text;
@synthesize delegate = _delegate;

// ** READING **

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"UIDocument: loadFromContents: state = %d, typeName=%@", self.documentState, typeName);

    if ([contents length] > 0) {
        self.documentText = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding];
    }
    else {
        self.documentText = @"";
    }

    NSLog(@"UIDocument: Loaded the following text from the cloud: %@", self.documentText);


    // update textView in delegate...
    if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
        [_delegate noteDocumentContentsUpdated:self];
    }

    return YES;

}

// ** WRITING **

-(id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
    if ([self.documentText length] == 0) {
        self.documentText = @"New Note";
    }

    NSLog(@"UIDocument: Will save the following text in the cloud: %@", self.documentText);

    return [NSData dataWithBytes:[self.documentText UTF8String] length:[self.documentText length]];
}
@end

THE VIEWCONTROLLER

//
//  ViewController.h
//  iCloudText

#import <UIKit/UIKit.h>

@class MyTextDocument;

@interface ViewController : UIViewController <UITextViewDelegate> {

    IBOutlet UITextView *textView;

}

@property (nonatomic, retain) UITextView *textView;
@property (strong, nonatomic) MyTextDocument *document;

-(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument;

@end

//  ViewController.m
//  iCloudText

#import "ViewController.h"
#import "MyTextDocument.h"

@implementation ViewController

@synthesize textView = _textView;
@synthesize document = _document;

-(IBAction)dismissKeyboard:(id)sender {

    [_textView resignFirstResponder];

}

-(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument
{
    NSLog(@"VC: noteDocumentsUpdated");
    _textView.text = noteDocument.documentText;
}

-(void)textViewDidChange:(UITextView *)theTextView {

     NSLog(@"VC: textViewDidChange");
    _document.documentText = theTextView.text;
    [_document updateChangeCount:UIDocumentChangeDone];

}
83
n.evermind

Saya baru saja membaca ulang dokumen dan tampaknya pendekatan umum saya salah. Pertama-tama saya harus membuat file di kotak pasir dan kemudian memindahkannya ke cloud. Dengan kata lain, Apple tampaknya menyarankan bahwa saya harus memiliki tiga versi file yang sama setiap saat: satu di direktori aplikasi saya, satu di direktori iblis iCloud perangkat saya (yang juga dapat diakses jika offline) dan satu di cloud:

Aplikasi menggunakan teknologi yang sama untuk mengelola file dan direktori di iCloud yang mereka lakukan untuk file dan direktori lokal. File dan direktori di iCloud masih berupa file dan direktori. Anda dapat membukanya, membuat, memindahkan, menyalinnya , baca dan tulis darinya, hapus, atau operasi lain yang mungkin ingin Anda lakukan. Satu-satunya perbedaan antara file dan direktori lokal dan file dan direktori iCloud adalah URL yang Anda gunakan untuk mengaksesnya. Alih-alih URL relatif terhadap kotak pasir aplikasi Anda, URL untuk file dan direktori iCloud relatif terhadap direktori wadah iCloud yang sesuai.

Untuk memindahkan file atau direktori ke iCloud:

Buat file atau direktori secara lokal di kotak pasir aplikasi Anda. Saat digunakan, file atau direktori harus dikelola oleh presenter file, seperti objek UIDocument.

Gunakan metode URLForUbiquityContainerIdentifier: untuk mengambil URL untuk direktori kontainer iCloud tempat Anda ingin menyimpan item. Gunakan URL direktori penampung untuk membuat URL baru yang menentukan lokasi item di iCloud. Panggil setUbiquitous: itemAtURL: destinationURL: error: metode NSFileManager untuk memindahkan item ke iCloud. Jangan pernah panggil metode ini dari utas utama aplikasi Anda; melakukan hal itu dapat memblokir utas utama Anda untuk jangka waktu yang lama atau menyebabkan kebuntuan dengan salah satu penyaji file aplikasi Anda sendiri. Saat Anda memindahkan file atau direktori ke iCloud, sistem menyalin item itu dari kotak pasir aplikasi Anda dan ke direktori lokal pribadi sehingga dapat dipantau oleh daemon iCloud. Meskipun file tidak lagi ada di kotak pasir Anda, aplikasi Anda masih memiliki akses penuh ke sana. Meskipun salinan file tetap lokal ke perangkat saat ini, file tersebut juga dikirim ke iCloud sehingga dapat didistribusikan ke perangkat lain. Daemon iCloud menangani semua pekerjaan untuk memastikan bahwa salinan lokalnya sama. Jadi dari perspektif aplikasi Anda, file tersebut hanya ada di iCloud.

Semua perubahan yang Anda buat pada file atau direktori di iCloud harus dilakukan menggunakan objek koordinator file. Perubahan ini termasuk memindahkan, menghapus, menyalin, atau mengganti nama item. Koordinator file memastikan bahwa daemon iCloud tidak mengubah file atau direktori pada saat yang sama dan memastikan bahwa pihak lain yang berkepentingan diberitahu tentang perubahan yang Anda buat.

Namun, jika Anda Menggali sedikit lebih dalam ke dokumen tentang setUbiquitous, Anda akan menemukan:

Gunakan metode ini untuk memindahkan file dari lokasi saat ini ke iCloud. Untuk file yang terletak di kotak pasir aplikasi, ini melibatkan penghapusan file secara fisik dari direktori sandbox. (Sistem memperluas hak istimewa kotak pasir aplikasi Anda untuk memberinya akses ke file yang dipindahkan ke iCloud.) Anda juga dapat menggunakan metode ini untuk memindahkan file dari iCloud dan kembali ke direktori lokal.

Jadi ini tampaknya berarti bahwa file/direktori akan dihapus dari kotak pasir lokal dan dipindahkan ke cloud.

21
n.evermind

Saya telah menggunakan contoh Anda dan saya menyukainya karena membantu saya memahami dasar-dasar iCloud. Sekarang saya berselisih dengan pertanyaan Anda untuk aplikasi saya sendiri yang harus mendukung pengguna aplikasi yang ada dengan konten yang disimpan secara lokal yang mungkin atau mungkin tidak menggunakan iCloud membuat case-case ini sejauh yang saya tahu:

Kasus:

  1. Pengguna baru
    • memiliki icloud - membuat dokumen dalam icloud
    • no icloud - buat dokumen secara lokal
  2. Pengguna yang ada
    • memiliki icloud
      • baru saja ditambahkan - migrasi dokumen lokal ke icloud
      • tidak hanya ditambahkan - buka/simpan dokumen ke icloud
    • tidak ada icloud
      • baru saja dihapus - memigrasikan dokumen icloud sebelumnya ke lokal
      • tidak hanya dihapus - buka/simpan dokumen ke lokal

Jika seseorang menghapus iCloud - bukankah panggilan ke URL di mana-mana akan mengembalikan nol? Jika demikian, bagaimana cara saya memigrasi dokumen kembali ke penyimpanan lokal? Saya akan membuat pengguna pref untuk saat ini tetapi tampaknya sedikit solusi.

Saya merasa seperti kehilangan sesuatu yang jelas di sini jadi jika ada yang bisa melihatnya, silakan berpura-pura.

5
earnshavian

Jika Anda ingin pengguna dapat berbagi teks antara perangkat yang pra-iOS 5.0, Anda harus melakukan apa yang harus dilakukan semua orang sebelum iCloud dan memindahkan informasi ke server Anda sendiri.

Yang Anda butuhkan hanyalah server di suatu tempat yang memungkinkan aplikasi Anda menyimpan file teksnya dan mengaitkannya dengan akun pengguna.

Anda akan membutuhkan pengguna untuk membuat akun dan Anda harus mengelola prosesnya sendiri, memindahkan informasi baru pada satu perangkat ke 'cloud' Anda sendiri.

Pengguna akan mendaftar dengan akun yang sama di perangkat lain dan Anda harus berhati-hati mendeteksi ketika perangkat lain telah memindahkan data ke cloud Anda sendiri, dan memperbarui perangkat saat ini dengan info baru.

Jelas, untuk perangkat iOS 5.0, Anda mungkin ingin mendeteksi file yang diubah untuk perangkat pra-iOS 5.0 di cloud Anda sendiri, dan juga dapat berbicara dengan iCloud.

4

Tampaknya Anda tidak berjuang dengan masalah iCloud/notICloud sebanyak masalah iOS5/notIOS5.

Jika target penyebaran Anda adalah iOS5, maka cukup gunakan struktur UIDocument. Jika ada di mana-mana, maka NSMetaDataQuery Anda akan menemukannya di cloud; jika tidak maka akan menemukannya di perangkat.

Jika, di sisi lain, Anda ingin memberikan akses pra 5.0 ke aplikasi Anda, maka Anda perlu memeriksa secara kondisional untuk melihat apakah iOS yang berjalan adalah 5.0 atau lebih besar. Jika sudah maka gunakan UIDocument; jika tidak maka baca/tulis data dengan cara lama.

Pendekatan saya adalah menulis metode saveData bersyarat yang memeriksa iOS5. Jika ada, saya perbarui jumlah perubahan (atau gunakan undo manager). Dalam kasus Anda, textViewDidChange akan memanggil metode ini. Jika tidak, maka simpan ke disk dengan cara lama. Saat memuat, yang terjadi adalah sebaliknya.

3
Michael

Anda bingung dengan "Perlakukan file di iCloud dengan cara yang sama seperti Anda memperlakukan semua file lain di kotak pasir aplikasi Anda." Ini berlaku untuk sesuatu seperti Keynote dan Numbers tempat Anda menyimpan banyak file, dan jika Anda memiliki iCloud, mereka mulai menyinkronkan secara ajaib.

Namun, Anda sedang membangun sesuatu yang tergantung pada fungsionalitas seperti iCloud. Anda tidak dapat berpegang pada pernyataan itu karena aplikasi Anda bergantung pada iCloud untuk hadir agar segala sesuatu berjalan sebagaimana mestinya. Anda harus menutup aplikasi dan mengatakan "tolong siapkan iCloud agar ini berfungsi" atau duplikat fungsi seperti iCloud (milik Anda atau milik orang lain) yang selalu dapat Anda gunakan, apa pun.

1
Jesper