Основи iCloud та зразок коду [закрито]


85

Як новачок, я борюся з iCloud. Є кілька зразків, але вони, як правило, досить докладні (на форумі розробників є один для iCloud та CoreData, який є масовим). З яблуковими документами все гаразд, але я все ще не бачу загальної картини. Тож, будь ласка, потерпіть зі мною, деякі з цих питань є досить фундаментальними, але на них, можливо, легко відповісти.

Контекст: У мене працює дуже проста програма iCloud (повний зразок коду нижче). Користувачеві показаний лише один UITextView, і його / її введення зберігаються у файлі, який називається text.txt.

введіть тут опис зображення

Файл txt передається в хмару і стає доступним для всіх пристроїв. Працює ідеально, але:

Головна проблема: А як щодо користувачів, які не використовують iCloud?

Коли я запускаю свою програму (див. Код нижче), я перевіряю, чи ввімкнено користувачем iCloud. Якщо iCloud увімкнено, все в порядку. Додаток продовжує і шукає text.txt у хмарі. Якщо його знайдуть, він завантажить його та відобразить користувачеві. Якщо text.txt не знайдено в хмарі, він просто створить новий text.txt і відобразить його користувачеві.

Якщо у користувача не ввімкнено iCloud, нічого не трапиться. Як я можу зробити так, щоб користувачі, які не є iCloud, все ще могли працювати з моїм текстовим додатком? Або я просто ігнорую їх? Чи потрібно мені писати окремі функції для користувачів, які не є iCloud? Тобто функції, в яких я просто завантажую text.txt з папки документів?

Apple пише :

Обробляйте файли в iCloud так само, як і всі інші файли у пісочниці програми.

Однак у моєму випадку більше немає "звичайної" пісочниці додатків. Це в хмарі. Або я завжди спочатку завантажую text.txt з диска, а потім перевіряю за допомогою iCloud, чи є щось більш сучасне?

Пов’язана проблема: Структура файлу - пісочниця проти хмари

Можливо, моя головна проблема - це принципове нерозуміння того, як повинен працювати iCloud. Коли я створюю новий екземпляр UIDocument, мені доведеться переписати два методи. Спочатку - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outErrorотримати файли з хмари, а потім -(id)contentsForType:(NSString *)typeName error:(NSError **)outErrorотримати файли в хмарі.

Чи потрібно мені включати окремі функції, які також зберігатимуть локальну копію text.txt у моїй пісочниці? Чи буде це працювати для користувачів, які не є iCloud? Як я розумію iCloud, він автоматично збереже локальну копію text.txt. Тож не повинно бути потреби в тому, щоб я що-небудь зберігав у «старій» пісочниці мого додатка (тобто, як це було колись у старі часи до iCloud). Зараз моя пісочниця абсолютно порожня, але я не знаю, чи це правильно. Чи слід зберігати там іншу копію text.txt? Це схоже на захаращення моєї структури даних ... оскільки в хмарі є один text.txt, один у пісочниці iCloud на моєму пристрої (який буде працювати, навіть якщо я не в мережі), а третій - у старій добрій пісочниці мій додаток ...


МОЙ КОД: Простий зразок коду iCloud

Це вільно базується на прикладі, який я знайшов на форумі розробників та на відео сесії WWDC. Я роздягнув його до мінімуму. Я не впевнений, що моя структура MVC якась хороша. Модель знаходиться в AppDelegate, що не ідеально. Будь-які пропозиції щодо його покращення вітаються.


РЕДАГУВАТИ: Я спробував витягти головне питання та розмістив його [тут]. 4


ОГЛЯД:

Огляд

Найважливіший біт, який завантажує text.txt із хмари:

//  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

Документ UID

//  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

КОНТРОЛЕР ПЕРЕГЛЯДУ

//
//  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];

}

4
Я б справді запропонував розбити це на кілька питань. Я бачу тут кілька різних питань, і їх важко виділити у стіні тексту, який ви тут маєте. Я поверну це питання назад до простого запитання про те, що робити людям, у яких не ввімкнено iCloud, і розбиваю інші (лише з відповідними частинами вашого зразка коду) на окремі запитання. Це хороші запитання, але я думаю, що їх слід розділити.
Бред Ларсон

@BradLarson Дякую за ваш коментар. Мені шкода, якщо питання (і) трохи заплутані між собою, але я думаю, що основним питанням (як я намагався вказати) є проблема пісочниці програми та пісочниці iCloud. Я надав повний код (це найкоротший приклад коду iCloud, до речі), оскільки я думав, що ЦІЛИЙ контекст життєво важливий, щоб знати, що відбувається ... Але я міг би просто відкрити інше питання і зв’язати його із цим запитанням до отримати ширшу картину.
n.evermind

@BradLarson Добре, я відкрив тут нове запитання: stackoverflow.com/questions/7798555 / ...
n.evermind

Для тих, хто все ще намагається зрозуміти Core Data та iCloud, спробуйте це посилання ossh.com.au/design-and-technology/software-development/…
Duncan

Не слід закривати, це насправді одна з найбільш конструктивних публікацій, яку я бачив у iCloud.
Ендрю Сміт,

Відповіді:


22

Я просто перечитав документи, і виявляється, що мій загальний підхід неправильний. Спочатку слід створити файл у пісочниці, а потім перенести його в хмару. Іншими словами, Apple, схоже, пропонує мені мати постійно три версії одного і того ж файлу: одну в каталозі мого додатка, одну в демонстраційній директорії iCloud мого пристрою (яка також доступна в автономному режимі) і одну в хмара:

Додатки використовують ті самі технології для управління файлами та каталогами в iCloud, що й для локальних файлів та каталогів. Файли та каталоги в iCloud все ще залишаються лише файлами та каталогами. Ви можете відкрити їх, створити, перемістити, скопіювати, читати та писати з них, видаляти або виконувати будь-які інші операції, які ви можете зробити. Єдиною різницею між локальними файлами та каталогами та файлами та каталогами iCloud є URL-адреса, за якою ви отримуєте до них доступ. Замість того, щоб URL-адреси були відносно пісочниці вашого додатка, URL-адреси файлів i каталогів iCloud відносно відповідного каталогу контейнерів iCloud.

Щоб перемістити файл або каталог у iCloud:

Створіть файл або каталог локально в пісочниці програми. Під час використання файлом або каталогом повинен керувати презентатор файлу, наприклад об'єкт UIDocument.

Використовуйте метод URLForUbiquityContainerIdentifier: для отримання URL-адреси для каталогу контейнера iCloud, в якому ви хочете зберігати елемент. Використовуйте URL-адресу каталогу контейнера, щоб створити нову URL-адресу, яка вказує розташування елемента в iCloud. Викличте setUbiquitous: itemAtURL: destinationURL: error: метод NSFileManager для переміщення елемента в iCloud. Ніколи не викликайте цей метод з основного потоку вашого додатка; це може заблокувати ваш основний потік на тривалий проміжок часу або призвести до глухого кута в одному із власників файлів вашого додатка. Коли ви переміщуєте файл або каталог до iCloud, система копіює цей елемент із пісочниці вашого додатка в приватний локальний каталог, щоб його можна було контролювати демоном iCloud. Незважаючи на те, що файл більше не у вашій пісочниці, програма все одно має повний доступ до нього. Хоча копія файлу залишається локальною для поточного пристрою, файл також надсилається до iCloud, щоб його можна було розповсюдити на інших пристроях. Демон iCloud виконує всю роботу, щоб переконатися, що локальні копії однакові. Отже, з точки зору вашої програми, файл просто знаходиться в iCloud.

Усі зміни, які ви вносите у файл чи каталог у iCloud, повинні вноситися за допомогою об’єкта координатора файлів. Ці зміни включають переміщення, видалення, копіювання або перейменування елемента. Координатор файлів гарантує, що демон iCloud не змінює файл або каталог одночасно, а також інформує інших зацікавлених сторін про внесені вами зміни.

Однак, якщо заглибитися трохи глибше в документи щодо setUbiquitous, ви побачите:

За допомогою цього методу можна перемістити файл із поточного місця в iCloud. Для файлів, розташованих у пісочниці програми, це передбачає фізичне видалення файлу з каталогу пісочниці . (Система розширює привілеї пісочниці вашої програми, щоб надати їй доступ до файлів, які вона переміщує до iCloud.) Ви також можете використовувати цей метод для переміщення файлів із iCloud та назад у локальний каталог.

Отже, це означає, що файл / каталог видаляється з локальної пісочниці та переміщується в хмару.


1
посилання на url порушено ...
ngb

5

Я використовую ваш приклад, і мені подобається, що він допомагає мені зрозуміти основи iCloud. Зараз я сперечаюся з вашим запитанням щодо мого власного додатка, який повинен підтримувати існуючих користувачів програми локально збереженим вмістом, які можуть або не можуть використовувати iCloud, створюючи ці випадки, наскільки я можу зрозуміти:

Випадки:

  1. Новий користувач
    • має icloud - створюйте документи в
    • no icloud - створюйте документи локально
  2. Існуючий користувач
    • має icloud
      • щойно доданий - перенесіть локальні документи в icloud
      • не просто додано - відкрити / зберегти документи в icloud
    • немає icloud
      • щойно видалено - перенесіть колишні документи icloud до локальних
      • не просто видалити - відкрити / зберегти документи в локальну

Якщо хтось видалить iCloud - хіба дзвінки на всюдисущу URL-адресу не повернуть нуль? Якщо це так, як мені перенести документи назад у локальне сховище? Наразі я створив користувача pref, але, здається, це трохи обхідне рішення.

Я відчуваю, що тут мені не вистачає чогось очевидного, тому, якщо хтось може це побачити, будь ласка, зателефонуйте.


Слід додати, що мені цікаво, чи існує клас, який обробляє ці випадки, тому я просто використовую це і не маю клопоту про те, де його зберегти.
Earnshavian

Погляньте на developer.apple.com/library/ios/#documentation/DataManagement/..., де подано деякий зразок коду, щоб визначити, чи слід щось розміщувати в локальній пісочниці чи хмарі.
n.evermind

Дякую за це. Я бачив цей документ, але раніше в місії iCloud, тому забув код, який він пропонує. Я спробую адаптувати ваш зразок для підтримки локального та віддаленого. Мені досі незрозуміло, як ми поводимося з користувачем, який вимикає iCloud, оскільки ми втрачаємо всюдисущу URL-адресу, але я отримаю тріщину та поділюся оновленням.
Earnshavian

1
Отже, трохи по-дурному, що ми повинні використовувати URL-адреси для хмари та PATH для локальної пісочниці. Було б непогано, якби iCloud міг обробляти все за нас ... але таким чином, нам в основному потрібно кодувати два різні методи для кожного файлу, який ми відкриваємо.
n.evermind

Я просто перечитав ваш пост. Зараз я зберігаю вподобання користувача (тобто користувач хоче / не хоче використовувати iCloud) у NSUserDefaults. Це те, що пропонує і Apple. Я завжди перевіряю, чи доступний iCloud. Якщо він недоступний, я кажу користувачам увімкнути його, але лише якщо вони прямо не сказали програмі, що не хочуть її використовувати. Інакше це дратує тих, хто не хоче користуватися iCloud. Після того, як я визначив, чи ввімкнено iCloud, я або піду повсюдним маршрутом URL-адреси, і за допомогою UIDocument АБО просто відкрию файли з пісочниці, як у старі добрі часи.
n.evermind

4

Якщо ви хочете, щоб користувачі мали змогу ділитися текстом між пристроями, які працюють до iOS 5.0, вам доведеться зробити те, що всі мали робити перед iCloud, і перемістити інформацію на ваш власний сервер.

Все, що вам дійсно потрібно - це десь сервер, який дозволяє вашій програмі зберігати текстові файли та пов’язувати їх з обліковим записом користувача.

Вам потрібно буде створити обліковий запис, і вам потрібно буде самостійно керувати процесом, переміщуючи нову інформацію на одному пристрої у свою власну «хмару».

Користувачі реєструватимуться з тим самим обліковим записом на інших пристроях, і вам потрібно буде подбати про виявлення, коли інший пристрій перемістив дані у вашу власну хмару, та оновити поточний пристрій новою інформацією.

Очевидно, що для пристроїв iOS 5.0 ви, мабуть, захочете виявити змінені файли для пристроїв до iOS 5.0 у вашій власній хмарі, а також зможете спілкуватися з iCloud.


Дякую. Отже, іншими словами, якщо я не хочу підтримувати пристрої до iOS 5, я просто використовую UIDocument і забуваю про вміст каталогу doc у пісочниці мого додатка.
n.evermind

Практично, хоча, наскільки я можу зрозуміти, у вас все ще буде документ у пісочниці, який UIDocument допоможе вам у посередництві з iCloud, але вам скажуть, коли ви зможете отримати до нього доступ ... Я все ще отримую щоб впоратися з цим матеріалом сам!
Джонатан Ватмау,

3

Здається, ви не так сильно боретеся з проблемою iCloud / notICloud, як проблема iOS5 / notIOS5.

Якщо вашою метою розгортання є iOS5, просто завжди використовуйте структуру UIDocument. Якщо це повсюдно, то ваш NSMetaDataQuery знайде його в хмарі; якщо ні, він знайде його на пристрої.

Якщо, з іншого боку, ви хочете надати попередній доступ до своєї програми до 5.0, тоді вам потрібно буде умовно перевірити, чи запущена iOS 5.0 або вище. Якщо це так, тоді використовуйте UIDocument; якщо ні, то читайте / записуйте дані по-старому.

Мій підхід полягав у написанні умовного методу saveData, який перевіряє наявність iOS5. Якщо він існує, я оновлюю кількість змін (або використовую менеджер скасування). У вашому випадку textViewDidChange викликає цей метод. Якщо ні, тоді він зберігає на диску старий спосіб. При завантаженні трапляється навпаки.


1

Вас бентежить "Поводження з файлами в iCloud так само, як і з усіма іншими файлами у пісочниці програми". Це справедливо для чогось на зразок Keynote та Numbers, де ви зберігаєте купу файлів, і якщо у вас є iCloud, вони починають чарівну синхронізацію.

Однак ви створюєте щось, що залежить від функціональності, подібної до iCloud. Ви не можете дотриматися цього твердження, оскільки ваш додаток залежить від iCloud, щоб бути присутнім, щоб щось працювало так, як це призначено. Вам доведеться або закрити програму, і просто сказати "будь ласка, налаштуйте iCloud, щоб це працювало", або продублювати схожу на iCloud функціональність (свою чи чужу), яку ви завжди можете використовувати, незалежно від цього.


Дякую. Тож, мабуть, я повинен вибрати, робити я лише додаток iCloud чи якийсь гібрид для людей, які користуються функціональністю iCloud. Оскільки iCloud настільки складний, я, як правило, переходжу на додаток, призначений лише для iCloud. Дякую.
n.evermind
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.