12

Tôi đã có một doozey của một vấn đề mà tôi đã làm việc trên một vài tuần. Nó liên quan đến hiệu suất giao diện người dùng nói lắp bất cứ khi nào tôi lưu bối cảnh đối tượng được quản lý dữ liệu lõi của mình. Tôi đã nhận được như xa như tôi có thể một mình và đang tìm kiếm một số trợ giúp.Bối cảnh được quản lý đối tượng bối cảnh Staggers UI Animation

Tình huống

Ứng dụng của tôi sử dụng hai trường hợp NSManagedObjectContext. Một thuộc về đại biểu ứng dụng và có một điều phối viên lưu trữ liên tục gắn liền với nó. Cái còn lại là con của MOC chính và thuộc về một đối tượng Class, được gọi là PhotoFetcher. Nó sử dụng NSPrivateQueueConcurrencyType vì vậy tất cả các hoạt động thực hiện trên MOC này diễn ra trong một hàng đợi nền.

Ứng dụng của chúng tôi tải xuống dữ liệu JSON biểu thị dữ liệu về ảnh từ API của chúng tôi. Để lấy dữ liệu từ API của chúng tôi, trình tự các bước như sau xảy ra:

  1. Xây dựng một đối tượng NSURLRequest và sử dụng giao thức NSURLConnectionDataDelegate để xây dựng dữ liệu trả về từ yêu cầu, hoặc xử lý lỗi.
  2. Sau khi tải về các dữ liệu JSON là hoàn chỉnh, thực hiện một khối trên hàng đợi MOC thứ của nào sau đây:
    1. Phân tích các JSON sử dụng NSJSONSerialization vào trường lớp Foundation.
    2. Lặp lại dữ liệu được phân tích cú pháp, chèn hoặc cập nhật các đối tượng trong ngữ cảnh nền khi cần. Thông thường, điều này dẫn đến khoảng 300 thực thể mới hoặc được cập nhật.
    3. Lưu ngữ cảnh nền. Điều này lan truyền các thay đổi của tôi cho MOC chính.
    4. Thực hiện một khối trên MOC chính để lưu đó là ngữ cảnh. Điều này là để dữ liệu của chúng tôi được lưu vào đĩa, một cửa hàng SQLite. Cuối cùng, gọi lại cho một đại biểu thông báo cho họ biết rằng phản hồi đã được chèn hoàn toàn vào kho dữ liệu lõi.

Mã để lưu MOC nền trông giống như sau:

[AppDelegate.managedObjectContext performBlock:^{ 
    [AppDelegate saveContext]; //A standard save: call to the main MOC 
}]; 

Khi bối cảnh đối tượng chính tiết kiệm, nó cũng tiết kiệm một số hợp lý của ảnh JPEG đã được tải về từ cuối cùng thời gian một bối cảnh đối tượng chính lưu xảy ra. Hiện tại, trên iPhone 4, chúng tôi đang tải xuống 15 hình ảnh 200x200 JPEG ở mức nén 70% hoặc tổng cộng khoảng 2MB dữ liệu.

Sự cố

Tính năng này hoạt động và hoạt động tốt. Vấn đề của tôi là khi bối cảnh nền tiết kiệm, NSFetchedResultsController đang chạy trong trình điều khiển chế độ xem của tôi chọn các thay đổi được truyền đến MOC chính. Nó chèn các ô mới vào số PSTCollectionView, một bản sao mã nguồn mở của UICollectionView. Trong khi nó chèn các ô mới, bối cảnh chính lưu và ghi những thay đổi đó vào đĩa. Điều này có thể thực hiện, trên iPhone 4 chạy iOS 5.1, ở bất kỳ đâu từ 250-350ms.

Trong thời gian đó thứ ba của một giây, ứng dụng hoàn toàn không phản hồi. Ảnh động đang diễn ra trước khi lưu được tạm dừng và không có sự kiện người dùng mới nào được gửi đến vòng lặp chạy chính cho đến khi quá trình lưu hoàn tất.

Tôi đã chạy ứng dụng của mình trong Công cụ bằng Trình tạo thời gian để xác định nội dung nào đang chặn chuỗi chính của chúng tôi. Thật không may, kết quả đã được khá mờ đục. Đây là dấu vết ngăn xếp nặng nhất mà tôi nhận được từ Dụng cụ.

Instruments Heaviest Stack Trace

xuất hiện để được tiết kiệm thông tin cập nhật đến cửa hàng dai dẳng, nhưng tôi không thể chắc chắn. Vì vậy, tôi đã xóa mọi cuộc gọi đến saveContext vì vậy MOC sẽ không chạm vào đĩa và cuộc gọi chặn trên chủ đề chính vẫn còn tồn tại.

Các dấu vết, dưới dạng văn bản, trông như thế này:

Symbol Name 
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] 
-[NSManagedObjectContext executeFetchRequest:error:] 
    -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] 
    _perform 
    _dispatch_barrier_sync_f_invoke 
    _dispatch_client_callout 
     __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke_0 
     -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] 
     -[NSManagedObjectContext executeFetchRequest:error:] 
     -[NSPersistentStoreCoordinator executeRequest:withContext:error:] 
      -[NSSQLCore executeRequest:withContext:error:] 
      -[NSSQLCore objectsForFetchRequest:inContext:] 
      -[NSSQLCore newRowsForFetchPlan:] 
      -[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:] 
       -[NSSQLiteConnection execute] 

gì tôi đã cố gắng

Trước khi chúng tôi chạm vào mã Core Data, điều đầu tiên chúng tôi làm là tối ưu hóa hình ảnh JPEG của chúng tôi. Chúng tôi đã chuyển sang các hình JPEG nhỏ hơn và thấy hiệu suất tăng lên. Sau đó, chúng tôi đã giảm số lượng ảnh JPEG mà chúng tôi tải xuống tại một thời điểm (từ 90 xuống còn 15). Điều này cũng dẫn đến tăng hiệu suất đáng kể. Tuy nhiên, chúng ta vẫn thấy các khối dài 250-350ms trên sợi chính.

Điều đầu tiên tôi đã thử chỉ là loại bỏ MOC nền để loại bỏ khả năng có thể gây ra sự cố. Trong thực tế, nó làm mọi thứ tồi tệ hơn vì mã cập nhật hoặc tạo của chúng tôi đang chạy trên luồng chính và làm cho hiệu suất hoạt ảnh tổng thể bị suy giảm.

Thay đổi cửa hàng liên tục thành NSInMemoryStoreType không có hiệu lực.

Mọi người có thể chỉ cho tôi "bí mật" sẽ cho tôi hiệu suất giao diện người dùng mà bối cảnh đối tượng được quản lý nền đã hứa không?

+0

Dường như đối tượng bạn đang lưu đang bị lỗi trên chuỗi chính. Bạn có thể chạy trình thu thập dữ liệu lõi để xem nguyên nhân gây ra lỗi không? Và bạn có sử dụng các mối quan hệ nghịch đảo không? – ndfred

+0

Tôi sẽ thanh toán bộ kiểm tra CD, nhưng tôi sử dụng mối quan hệ inver. –

Trả lời

0

Tôi có một vài dự đoán, theo thứ tự mà trong đó bạn nên kiểm tra chúng:

  1. Nó trông giống một awful nhiều như bất cứ điều gì đang được chậm đang xảy ra sau khi dữ liệu được lưu (ví dụ, đó là trong một gọi lại). Điều này dẫn tôi đến việc tải lại PTSCollectionView hoặc tìm nạp lại bộ điều khiển kết quả đã tìm nạp.

  2. Sự cố này có xảy ra với UICollectionView trên iOS 6.x không? Nếu không, điều đó sẽ dẫn tôi dựa vào PTSCollectionView.

  3. Nếu vẫn xảy ra, điều đó có nghĩa là nó có thể không phải là chế độ xem bộ sưu tập mà thay vào đó là bộ điều khiển kết quả được tìm nạp. Nó trông giống như, từ các khung ngăn xếp (mờ đục mặc dù chúng có thể) mà bộ điều khiển kết quả tìm nạp đang cố gắng thực hiện tìm nạp xảy ra thông qua một dispatch_barrier. Chúng được sử dụng để đảm bảo một khối không được thực hiện cho đến sau khi đạt được rào cản. Tôi đang ở trên một chi ở đây, nhưng bạn có thể muốn kiểm tra xem điều này là vì nội bộ, Core Data đang lưu ở nơi khác, và do đó trì hoãn việc thực hiện bất kỳ yêu cầu tìm nạp nào khác. Một lần nữa, đó là một loại phỏng đoán hoang dã và không được giáo dục. Nhưng tôi muốn thử mà không cần bộ điều khiển kết quả được tìm nạp cập nhật ngay lập tức và xem sự nói lắp của bạn vẫn diễn ra.

Một điều khác nổi bật với tôi là bạn đang thực hiện nhiều công việc trên một đứa trẻ MOC nhưng sau đó thực hiện lưu trên phụ huynh. Có vẻ như phần lớn lưu sẽ được thực hiện bởi đứa trẻ.Nhưng nó cũng có thể là tôi chưa từng làm việc với phần này của Core Data trong một thời gian :-)

6

tôi sẽ thực hiện một vài giả định, nhưng từ mô tả của bạn, tôi nghĩ rằng đó là hợp lý.

Đầu tiên, tôi giả sử MOC chính của bạn đã được tạo ra với:

[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 

tôi làm cho giả thiết này bởi vì ...

  1. Bạn sử dụng nó như là một bối cảnh cha mẹ, vì vậy nó phải được hoặc NSMainQueueConcurrencyType hoặc NSPrivateQueueConcurrencyType.

  2. Kể từ khi bạn sử dụng nó ở nơi khác, bạn sẽ chỉ làm như vậy nếu nó được đảm bảo để được truy cập vào hàng đợi chính.

Bây giờ, tôi cũng giả định rằng bạn đã kết nối các MOC chính trực tiếp cho điều phối viên cửa hàng liên tục, và không khác MOC mẹ.

Khi MOC nền không phá, những thay đổi của nó được truyền lên vào MOC chính (mặc dù họ chưa được lưu). Vì FRC của bạn được gắn vào MOC chính, nó sẽ thấy những thay đổi này ngay lập tức.

Khi bạn phát hành lưu trên MOC chính, mã của bạn sẽ chặn vì lưu sẽ không trở lại cho đến khi quá trình lưu hoàn tất.

Vì vậy, những gì bạn đang thấy là hoàn toàn mong đợi.

Có nhiều tùy chọn để giải quyết vấn đề của bạn.

đề nghị đầu tiên của tôi sẽ là để tạo ra một MOC tư nhân xếp hàng và làm cho nó trở thành mẹ của MOC chính-hàng đợi.

Điều này có nghĩa là mọi lưu MOC hàng đợi chính sẽ không phải là khối. Thay vào đó, nó sẽ "lưu" dữ liệu cho phụ huynh, sau đó sẽ thực hiện lưu cơ sở dữ liệu thực tế trên hàng đợi riêng của nó, và kết quả là, trong một luồng riêng biệt, trong nền.

Bây giờ, điều đó sẽ giải quyết vấn đề của bạn về chặn luồng chính. Cơ chế này cũng phù hợp tốt với MOC nền con của bạn tải cơ sở dữ liệu.

Lưu ý, rằng có một vài lỗi liên quan đến bối cảnh lồng nhau trong iOS 5, nhưng nếu bạn đang nhắm mục tiêu iOS 6, hầu hết trong số họ đã được cố định.

Xem Core Data could not fullfil fault for object after obtainPermanantIDs để biết thêm thông tin.

EDIT

giả định của bạn là chính xác, mặc dù tôi sợ rằng tôi đang nhắm mục tiêu iOS 5 và chỉ có thể có một MOC mẹ đến MOC chính trên iOS 6 (nó gây ra một bế tắc với FRC). - Ash Furrow

Nếu bạn đang nhìn thấy bế tắc, đầu tiên thoát khỏi dispatch_syncperformBlockAndWait bạn các cuộc gọi. Việc sử dụng thao tác chặn từ bên trong luồng chính sẽ không bao giờ xảy ra cho bất kỳ điều gì khác ngoài đồng bộ hóa đơn giản nhất (tức là hoạt động đọc đồng bộ từ cấu trúc dữ liệu) ...và sau đó chỉ khi cần thiết. Ngoài ra, các cuộc gọi đồng bộ có thể gây ra bế tắc bất ngờ, vì vậy nó nên tránh bất cứ khi nào có thể, đặc biệt khi gọi bất kỳ mã nào bạn không trực tiếp kiểm soát.

Nếu bạn không thể làm điều đó, cũng có một vài tùy chọn khác. Điều tôi thích nhất là gắn FRC vào một hàng đợi riêng-MOC, và sau đó, "dán" truy cập của bạn vào/từ FRC qua luồng chính. Bạn có thể dispatch_async vào chuỗi chính để xử lý các cập nhật ủy nhiệm cho chế độ xem bảng (hoặc bất kỳ thứ gì). Ví dụ ...

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [tableView beginUpdates]; 
    }); 
} 

Bạn có thể tạo proxy cho FRC, đây chỉ là giao diện người dùng để đảm bảo quyền truy cập được đồng bộ hóa một cách thích hợp. Nó không phải là một proxy đầy đủ ... chỉ đủ cho những gì bạn sử dụng FRC cho ... và để đảm bảo rằng tất cả các hoạt động được đồng bộ một cách thích hợp.

Nó thực sự dễ hơn âm thanh.

Thực tế, bạn sẽ có cùng một thiết lập giống như bạn hiện có, ngoại trừ "main-MOC" sẽ là MOC hàng đợi riêng thay vì MOC hàng đợi chính. Vì vậy, chủ đề chính của bạn sẽ không chặn khi truy cập cơ sở dữ liệu xảy ra.

Tuy nhiên, bạn cần thực hiện một số biện pháp phòng ngừa để đảm bảo bạn sử dụng FRC trong ngữ cảnh phù hợp.

Một tùy chọn khác là sử dụng MOC chính và FRC như bạn làm bây giờ để xử lý các thay đổi đối với cơ sở dữ liệu, nhưng thực hiện tất cả các sửa đổi của cơ sở dữ liệu thông qua một MOC riêng biệt, được đính kèm trực tiếp với điều phối viên lưu trữ liên tục. Điều này làm cho các thay đổi xảy ra trong một chuỗi riêng biệt. Sau đó, bạn sử dụng MOC lưu thông báo để cập nhật ngữ cảnh của bạn và/hoặc tải lại dữ liệu từ cửa hàng.

Các bản cập nhật cho iOS và OSX khắc phục nhiều sự cố với ngữ cảnh lồng nhau cho Dữ liệu lõi, nhưng có một số điều bạn phải lo lắng khi hỗ trợ phiên bản trước đó.

+0

Giả định của bạn là chính xác, mặc dù tôi sợ rằng tôi đang nhắm mục tiêu iOS 5 và chỉ có thể có MOC mẹ cho MOC chính trên iOS 6 (nó gây ra bế tắc với FRC). –

+0

Lỗi bế tắc là do sự cố với [FRC của iOS 5] (http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains), không phải do bất kỳ điều gì trong mã của tôi. Tôi sẽ cung cấp cho các tùy chọn khác một shot. –