Xamarin.iOS'ta Özel Durum Sıralama
Xamarin.iOS, özellikle yerel kodda özel durumlara yanıt vermeye yardımcı olacak yeni olaylar içerir.
Hem yönetilen kod hem Objective-C de çalışma zamanı özel durumları (try/catch/finally yan tümceleri) için destek içerir.
Ancak, uygulamaları farklıdır; bu da çalışma zamanı kitaplıklarının (Mono çalışma zamanı ve çalışma zamanı kitaplıkları) özel durumları işlemeleri ve ardından başka dillerde yazılmış kodu çalıştırmaları gereken sorunlar olduğu anlamına Objective-C gelir.
Bu belge, ortaya çıkabilir sorunları ve olası çözümleri açıklar.
Ayrıca, farklı senaryoları ve bunların çözümlerinitest etmek için kullanılan örnek bir proje olan Özel Durum Sıralamayı içerir.
Sorun
Sorun, bir özel durum oluştuğunda ve yığın geriye doğru izleme sırasında, oluşan özel durum türüyle eşleşmez bir çerçeveyle karşılaşıldı.
Xamarin.iOS veya Xamarin.Mac için bunun tipik bir örneği, yerel bir API'nin bir özel durum oluşturması ve yığın geriye doğru izleme işlemi yönetilen bir çerçeveye ulaştığında bu özel durumun bir şekilde ele Objective-CObjective-C olmasıdır.
Varsayılan eylem, hiçbir şey yapmamadır. Yukarıdaki örnek için bu, çalışma zamanının yönetilen kareleri geriye Objective-C doğru izlemesine izin verme anlamına gelir. Çalışma zamanı yönetilen karelerin nasıl geriye doğru geriye doğru itiltilcesini bilmiyorsa, örneğin bu çerçevede herhangi bir veya yan Objective-Ccatchfinally tümcesi yürütmeyer, bu soruna neden olur.
Bozuk kod
Aşağıdaki kod örneğini inceleyin:
var dict = new NSMutableDictionary ();
dict.LowlevelSetObject (IntPtr.Zero, IntPtr.Zero);
Bu, yerel Objective-C kodda bir NSInvalidArgumentException atar:
NSInvalidArgumentException *** setObjectForKey: key cannot be nil
Yığın izlemesi de aşağıdakine benzer:
0 CoreFoundation __exceptionPreprocess + 194
1 libobjc.A.dylib objc_exception_throw + 52
2 CoreFoundation -[__NSDictionaryM setObject:forKey:] + 1015
3 libobjc.A.dylib objc_msgSend + 102
4 TestApp ObjCRuntime.Messaging.void_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
5 TestApp Foundation.NSMutableDictionary.LowlevelSetObject (intptr,intptr)
6 TestApp ExceptionMarshaling.Exceptions.ThrowObjectiveCException ()
0-3 arasında kareler yerel karelerdir ve çalışma zamanında yığın geriye Objective-C doğru geriye doğru izleme bu kareleri geriye doğru geriye doğru takip edebilirsiniz. Objective-C Özellikle, herhangi bir veya yan Objective-C@catch@finally tümcesini yürütür.
Ancak, yığın geriye doğru izleme, yönetilen kareleri Objective-C (4-6 kareler) düzgün bir şekilde geriye doğru izleme özelliğine sahip değildir, bu durumda kareler geriye doğru olmaz, ancak yönetilen özel durum mantığı yürütülmez. Objective-C
Bu da genellikle bu özel durumları şu şekilde yakalamanın mümkün olmadığını ifade eder:
try {
var dict = new NSMutableDictionary ();
dict.LowLevelSetObject (IntPtr.Zero, IntPtr.Zero);
} catch (Exception ex) {
Console.WriteLine (ex);
} finally {
Console.WriteLine ("finally");
}
Bunun nedeni yığın geriye doğru izlemenin yönetilen yan tümcesi hakkında bilgisi olmasıdır Objective-Ccatch ve yan finally tümcesi de yürütülmez.
Yukarıdaki kod örneği etkili olduğunda bunun nedeni, Objective-CNSSetUncaughtExceptionHandler Xamarin.iOS ve Xamarin.Mac'in kullanmakta olduğu işlanmamış özel durumlar hakkında bilgi almak için bir yöntemi olması ve bu noktada herhangi bir özel durumu yönetilen özel durumlara dönüştürmeye Objective-C çalışmasıdır.
Senaryolar
Senaryo 1 - Yönetilen yakalama Objective-C işleyicisi ile özel durumları yakalama
Aşağıdaki senaryoda, yönetilen işleyicileri kullanarak Objective-C özel durumları yakalamak catch mümkündür:
- Bir Objective-C özel durum oluşturur.
- Çalışma zamanı yığını gösterir (ancak geriye doğru izlemez), özel durumu Objective-C
@catchişleyiciyi arıyor. - Çalışma zamanı hiçbir işleyici bulamaz, çağrısı yapar ve Objective-C
@catchNSGetUncaughtExceptionHandlerXamarin.iOS/Xamarin.Mac tarafından yüklenmiş işleyiciyi çağırır. - Xamarin.iOS/Xamarin.Mac'in işleyicisi, özel durumu yönetilen bir özel duruma dönüştürür Objective-C ve oluşturur. Çalışma zamanı yığını geriye doğru izlemez (yalnızca onu takip etti), geçerli çerçeve özel Objective-C durumun Objective-C atılmış olduğu karedir.
Burada başka bir sorun oluşur çünkü Mono çalışma zamanı karelerin nasıl geriye doğru geriye doğru doğru şekilde geriye doğru doğru şekilde geriye Objective-C doğru şekilde gelemeyebilir.
Xamarin.iOS'un yakalanmayan Objective-C özel durum geri çağırması çağrıldığı zaman yığın şöyle olur:
0 libxamarin-debug.dylib exception_handler(exc=name: "NSInvalidArgumentException" - reason: "*** setObjectForKey: key cannot be nil")
1 CoreFoundation __handleUncaughtException + 809
2 libobjc.A.dylib _objc_terminate() + 100
3 libc++abi.dylib std::__terminate(void (*)()) + 14
4 libc++abi.dylib __cxa_throw + 122
5 libobjc.A.dylib objc_exception_throw + 337
6 CoreFoundation -[__NSDictionaryM setObject:forKey:] + 1015
7 libxamarin-debug.dylib xamarin_dyn_objc_msgSend + 102
8 TestApp ObjCRuntime.Messaging.void_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
9 TestApp Foundation.NSMutableDictionary.LowlevelSetObject (intptr,intptr) [0x00000]
10 TestApp ExceptionMarshaling.Exceptions.ThrowObjectiveCException () [0x00013]
Burada, tek yönetilen kareler 8-10 kareleridir, ancak yönetilen özel durum 0. karede atılan özel durumdur. Bu, Mono çalışma zamanı 0-7 yerel çerçevelerini geriye doğru izlemesi gerektiğini ve bu da yukarıda tartışılan soruna eşdeğer bir soruna neden olduğu anlamına gelir: Mono çalışma zamanı yerel kareleri geriye doğru izlemesine rağmen, herhangi bir veya yan tümcesi Objective-C@catch@finally yürütmez.
Kod örneği:
-(id) setObject: (id) object forKey: (id) key
{
@try {
if (key == nil)
[NSException raise: @"NSInvalidArgumentException"];
} @finally {
NSLog (@"This will not be executed");
}
}
Bu @finally çerçeveyi geriye doğru takip Mono çalışma zamanı yan tümcesi bunun hakkında bilgi alamayacaktır.
Bunun bir varyasyonu, yönetilen kodda yönetilen bir özel durum atmak ve ardından ilk yönetilen yan tümceye almak için yerel kareler arasında geriye doğru geriye doğru izleme catch yapmaktır:
class AppDelegate : UIApplicationDelegate {
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
throw new Exception ("An exception");
}
static void Main (string [] args)
{
try {
UIApplication.Main (args, null, typeof (AppDelegate));
} catch (Exception ex) {
Console.WriteLine ("Managed exception caught.");
}
}
}
Yönetilen yöntem yerel yöntemi çağıracak ve ardından iOS yönetilen yöntemi çağırmadan önce çok fazla yerel kod yürütmesi yapar ve yönetilen özel durum thrown olduğunda yığında hala çok sayıda yerel çerçeve UIApplication:MainUIApplicationMainAppDelegate:FinishedLaunching vardır:
0: TestApp ExceptionMarshaling.IOS.AppDelegate:FinishedLaunching (UIKit.UIApplication,Foundation.NSDictionary)
1: TestApp (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___object_object (object,intptr,intptr,intptr)
2: libmonosgen-2.0.dylib mono_jit_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>, error=<unavailable>)
3: libmonosgen-2.0.dylib do_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>, error=<unavailable>)
4: libmonosgen-2.0.dylib mono_runtime_invoke [inlined] mono_runtime_invoke_checked(method=<unavailable>, obj=<unavailable>, params=<unavailable>, error=0xbff45758)
5: libmonosgen-2.0.dylib mono_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>)
6: libxamarin-debug.dylib xamarin_invoke_trampoline(type=<unavailable>, self=<unavailable>, sel="application:didFinishLaunchingWithOptions:", iterator=<unavailable>), context=<unavailable>)
7: libxamarin-debug.dylib xamarin_arch_trampoline(state=0xbff45ad4)
8: libxamarin-debug.dylib xamarin_i386_common_trampoline
9: UIKit -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]
10: UIKit -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:]
11: UIKit -[UIApplication _runWithMainScene:transitionContext:completion:]
12: UIKit __84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke.3124
13: UIKit -[UIApplication workspaceDidEndTransaction:]
14: FrontBoardServices __37-[FBSWorkspace clientEndTransaction:]_block_invoke_2
15: FrontBoardServices __40-[FBSWorkspace _performDelegateCallOut:]_block_invoke
16: FrontBoardServices __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
17: FrontBoardServices -[FBSSerialQueue _performNext]
18: FrontBoardServices -[FBSSerialQueue _performNextFromRunLoopSource]
19: FrontBoardServices FBSSerialQueueRunLoopSourceHandler
20: CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
21: CoreFoundation __CFRunLoopDoSources0
22: CoreFoundation __CFRunLoopRun
23: CoreFoundation CFRunLoopRunSpecific
24: CoreFoundation CFRunLoopRunInMode
25: UIKit -[UIApplication _run]
26: UIKit UIApplicationMain
27: TestApp (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
28: TestApp UIKit.UIApplication:Main (string[],intptr,intptr)
29: TestApp UIKit.UIApplication:Main (string[],string,string)
30: TestApp ExceptionMarshaling.IOS.Application:Main (string[])
0-1 ve 27-30 kareler yönetilirken, arasındaki tüm kareler yereldir. Mono bu karelerde geriye doğru geriye doğru ilerlerse, Objective-C@catch hiçbir veya yan @finally tümcesi yürütülmez.
Senaryo 2 - özel durumları Objective-C yakalayamadı
Aşağıdaki senaryoda, özel durum başka bir şekilde iş olduğundan, yönetilen catch işleyicileri kullanarak Objective-C özel durumları yakalamak mümkün değildir:
- Bir Objective-C özel durum oluşturur.
- Çalışma zamanı yığını gösterir (ancak geriye doğru izlemez), özel durumu Objective-C
@catchişleyiciyi arıyor. - Çalışma Objective-C zamanı bir
@catchişleyici bulur, yığını geriye doğru yürütür ve işleyiciyi yürütmeye@catchbaşlar.
Bu senaryo genellikle Xamarin.iOS uygulamaları içinde bulunur çünkü ana iş parçacığında genellikle aşağıdaki gibi bir kod vardır:
void UIApplicationMain ()
{
@try {
while (true) {
ExecuteRunLoop ();
}
} @catch (NSException *ex) {
NSLog (@"An unhandled exception occured: %@", exc);
abort ();
}
}
Bu, ana iş parçacığında hiçbir zaman gerçekten işlanmamış bir özel durum olduğu anlamına gelir ve bu nedenle özel durumları yönetilen özel durumlara dönüştüren geri Objective-CObjective-C çağırmamız hiçbir zaman çağrılmaz.
Xamarin.Mac'in desteklediğinden önceki bir macOS sürümünde Xamarin.Mac uygulamalarının hata ayıklaması yaparken de oldukça yaygındır çünkü hata ayıklayıcıdaki kullanıcı arabirimi nesnelerinin çoğu incelerken, yürütme platformunda mevcut olmayan seçicilere karşılık gelen özellikleri getirmeye çalışabilirsiniz (çünkü Xamarin.Mac daha yüksek bir macOS sürümü için destek içerir). Bu seçicilerin çağrılarak bir ("tanınmayan seçici ..." içine gönderilir) ve bu da sonunda sürecin NSInvalidArgumentException kilitlenmesine neden olur.
Özetlemek gerekirse, işleyecek şekilde programlanmamış çalışma zamanı veya Mono çalışma zamanı geriye doğru izleme çerçevelerinin olması kilitlenmeler, bellek sızıntıları ve diğer öngörülemeyen (yanlış) davranış türleri gibi tanımsız davranışlara yol Objective-C açabilirsiniz.
## Çözüm
Xamarin.iOS 10 ve Xamarin.Mac 2.10'da, yönetilen yerel sınırlarda hem yönetilen hem de özel durumları yakalama ve bu özel durumu diğer türe dönüştürme desteği Objective-C ekledik.
Sahte kodda şu şekildedir:
[DllImport (Constants.ObjectiveCLibrary)]
static extern void objc_msgSend (IntPtr handle, IntPtr selector);
static void DoSomething (NSObject obj)
{
objc_msgSend (obj.Handle, Selector.GetHandle ("doSomething"));
}
P/Invoke objc_msgSend araya çağrılır ve bunun yerine bu çağrılır:
void
xamarin_dyn_objc_msgSend (id obj, SEL sel)
{
@try {
objc_msgSend (obj, sel);
} @catch (NSException *ex) {
convert_to_and_throw_managed_exception (ex);
}
}
Ve ters durum için de benzer bir şey yapılır (yönetilen özel durumları özel Objective-C durumlara hazırlar).
Yönetilen yerel sınırda özel durumları yakalamak ücretsiz değildir, bu nedenle her zaman varsayılan olarak etkin değildir:
- Xamarin.iOS/tvOS: Simülatörde Objective-C özel durumların kesme noktası etkinleştirilir.
- Xamarin.watchOS: Çalışma zamanının yönetilen kareleri geriye doğru izlemesine izin vermenin atık toplayıcıyı karıştırmasına ve kilitlenmesine neden olduğundan, her durumda kesme Objective-C uygulanır.
- Xamarin.Mac: Hata ayıklama Objective-C derlemeleri için özel durumların kesme noktası etkinleştirilir.
Derleme zamanı bayrakları bölümünde, varsayılan olarak etkin olmayan kesmenin nasıl etkinleştirilmeleri açıklanmaz.
Ekinlikler
Bir özel durum araya alındıktan sonra iki yeni olay ortaya çıkar: Runtime.MarshalManagedException ve Runtime.MarshalObjectiveCException .
Her iki olay da, atılan özgün özel durumu içeren bir nesnesine (özelliği) ve özel durumun nasıl sıra olacağını tanımlamak EventArgs için bir özel durum ExceptionExceptionMode geçirildi.
özelliği, ExceptionMode işleyicide yapılan herhangi bir özel işleme göre davranışı değiştirmek için olay işleyicisinde değiştirilebilir. Bunun bir örneği, belirli bir özel durum oluşursa işlemi durdurmak olabilir.
özelliğinin ExceptionMode değiştirilmesi tek bir olay için geçerlidir; gelecekte araya alınan özel durumları etkilemez.
Aşağıdaki modlar kullanılabilir:
Default: Varsayılan değer platforma göre değişir. GC'nin işbirliği modunda (watchOS) ve aksi durumdaThrowObjectiveCExceptionUnwindNativeCode(iOS / watchOS / macOS) ise bu olur. Varsayılan değer gelecekte değişebilir.UnwindNativeCode: Bu, önceki (tanımsız) davranıştır. BU, GC'yi işbirliği modunda kullanırken kullanılamaz (watchOS'ta tek seçenektir; bu nedenle watchOS'ta geçerli bir seçenek değildir) ancak diğer tüm platformlar için varsayılan seçenektir.ThrowObjectiveCException: Yönetilen özel durumu bir özel Objective-C duruma dönüştür ve özel durumu Objective-C oluşturur. Bu, watchOS'ta varsayılan değerdir.Abort: İşlemi iptal edin.Disable: Özel durum kesmeyi devre dışı bırakarak olay işleyicisinde bu değeri ayarlamak mantıklı değildir, ancak olay başlatıldıktan sonra devre dışı bırakmak için çok geç olur. Her durumda, ayarlanırsa olarakUnwindNativeCodedavranır.
Özel durumları Objective-C yönetilen koda sıralamak için aşağıdaki modlar kullanılabilir:
Default: Varsayılan değer platforma göre değişir. GC'nin işbirliği modunda (watchOS) ve aksi takdirdeThrowManagedExceptionUnwindManagedCode(iOS / tvOS / macOS) ise bu olur. Varsayılan değer gelecekte değişebilir.UnwindManagedCode: Bu, önceki (tanımsız) davranıştır. Bu, GC'yi işbirliği modunda kullanırken kullanılamaz (watchOS'ta tek geçerli GC modudur; bu nedenle watchOS'ta geçerli bir seçenek değildir), ancak diğer tüm platformlar için varsayılan seçenektir.ThrowManagedException: Özel durumu Objective-C yönetilen bir özel duruma dönüştür ve yönetilen özel durum oluşturur. Bu, watchOS'ta varsayılan değerdir.Abort: İşlemi iptal edin.Disable:D özel durum kesme noktası sağlar, bu nedenle olay işleyicisinde bu değeri ayarlamak mantıklı değildir, ancak olay başlatıldıktan sonra devre dışı bırakmak için çok geç olur. Herhangi bir durumda ayarlanırsa, işlemi iptal eder.
Bu nedenle, bir özel durum her sıralandı her durumda görmek için bunu yapabiliriz:
Runtime.MarshalManagedException += (object sender, MarshalManagedExceptionEventArgs args) =>
{
Console.WriteLine ("Marshaling managed exception");
Console.WriteLine (" Exception: {0}", args.Exception);
Console.WriteLine (" Mode: {0}", args.ExceptionMode);
};
Runtime.MarshalObjectiveCException += (object sender, MarshalObjectiveCExceptionEventArgs args) =>
{
Console.WriteLine ("Marshaling Objective-C exception");
Console.WriteLine (" Exception: {0}", args.Exception);
Console.WriteLine (" Mode: {0}", args.ExceptionMode);
};
Build-Time Bayrakları
Aşağıdaki seçenekleri mtouch (Xamarin.iOS uygulamaları için) ve mmp 'ye (Xamarin.Mac uygulamaları için) geçirilebilir. Bu, özel durum durdurmanın etkin olup olmadığını belirler ve gerçekleşmesi gereken varsayılan eylemi ayarlar:
--marshal-managed-exceptions=defaultunwindnativecodethrowobjectivecexceptionabortdisable
--marshal-objectivec-exceptions=defaultunwindmanagedcodethrowmanagedexceptionabortdisable
dışında, disable bu değerler ve ExceptionMode olaylara geçirilen değerlerle MarshalManagedExceptionMarshalObjectiveCException aynıdır.
Bu disable seçenek çoğunlukla disable devre dışı bıraksa da, yürütme ek yükü eklemeden özel durumları yine de kesmiş oluruz. Sıralama olayları bu özel durumlar için yine de yükseltildi ve varsayılan mod, yürütülen platform için varsayılan moddur.
Sınırlamalar
Özel durumları yakalamaya çalışırken yalnızca işlev ailesine P/Invoke'ları objc_msgSendObjective-C kesmiş oluruz. Bu, başka bir C işlevine P/Invoke işlevinin daha sonra herhangi bir özel durum atarak eski ve tanımsız davranışla karşılaşıp karşılaşmayacakları anlamına gelir (bu durum gelecekte geliştir Objective-C olabilir).