مجموعات العرض المعقدة في نظام التشغيل iOS: المشكلات والحلول في مثال موجز VKontakte

تحية! اسمي ساشا ، أنا مطور نظام iOS في الفريق الذي يصنع خلاصة VKontakte. الآن سوف أخبرك كيف يمكننا تحسين عرض الواجهة والتغلب على المشاكل المرتبطة بذلك.
أعتقد أنه يمكنك تخيل ما هو شريط VK. هذه شاشة يمكنك من خلالها مشاهدة مجموعة متنوعة من المحتوى: النصوص والصور الثابتة وصور GIF المتحركة والعناصر المضمنة (الفيديو والموسيقى). يجب عرض كل هذا بسلاسة ، وبالتالي المطالب العالية على أداء الحلول.


الآن ، لنرى ما هي الأساليب القياسية للتعامل مع التعيينات الموجودة وما هي القيود أو المزايا التي يجب مراعاتها.


إذا كنت ترغب في الاستماع أكثر من قراءة ، فإن تسجيل الفيديو الخاص بالتقرير هنا .



محتوى


  1. وصف وحساب التصميم
    1.1. تخطيط السيارات
    1.2. حساب frame اليدوي
  2. حساب حجم النص
    2.1. الطرق القياسية لحساب حجم UILabel / UITextView / UITextField
    2.2. طرق NSAttributedString / NSAttributedString
    2.3. TextKit
    2.4. CoreText
  3. كيف تعمل خلاصة فكونتاكتي؟
  4. كيفية الحصول على أداء أفضل
    4.1 لماذا مشاكل الأداء
    4.2. CATransaction.commit
    4.3. تقديم خط أنابيب
    4.4. الأماكن الأكثر ضعفا للأداء
  5. أدوات القياس
    5.1. تتبع النظام المعدني
    5.2. نقوم بإصلاح عمليات سحب الأداء في الكود أثناء تشغيل التطبيق

  • كيفية البحث عن المشاكل. توصيات
  • استنتاج
  • مصادر المعلومات

1. وصف وحساب التصميم


أولاً ، دعنا نتذكر كيفية إنشاء بنية واجهة مرئية ( تخطيط ) باستخدام أدوات عادية. من أجل توفير مساحة ، سنفعل بدون قوائم - سأقوم ببساطة بسرد الحلول وشرح ميزاتها.


1.1. تخطيط السيارات


ربما تكون الطريقة الأكثر شيوعًا لإنشاء واجهة في iOS هي استخدام نظام تخطيط Auto Layout من Apple. وهي تستند إلى خوارزمية كاساواري ، المرتبطة ارتباطًا وثيقًا بمفهوم القيود.


الآن ، تذكر أن الواجهة المطبقة باستخدام التنسيق التلقائي مبنية على قيود.


ميزات النهج:


  • يتم تحويل نظام القيد إلى مشكلة البرمجة الخطية .
  • Cassowary يحل مشكلة التحسين الناتجة باستخدام طريقة simplex . هذه الطريقة لها تعقيد مقارب الأسي. ماذا يعني هذا؟ مع ازدياد عدد القيود في التخطيط ، في أسوأ الحالات ، يمكن أن تبطئ العمليات الحسابية أضعافا مضاعفة.
  • قيم frame الناتجة لـ UIView هي الحل لمشكلة التحسين المقابلة.

فوائد استخدام التخطيط التلقائي:


  • في التعيينات البسيطة ، التعقيد الحسابي الخطي ممكن .
  • إنها تتوافق بشكل جيد مع جميع العناصر القياسية ، لأنها تقنية "أصلية" من Apple.
  • خارج الصندوق يعمل مع UIView .
  • متوفر في Interface Builder ، والذي يسمح لك بوصف التخطيط في لوحة العمل أو XIB.
  • إنه يضمن حلاً محدثًا حتى أثناء الانتقال. هذا يعني أن قيمة frame لكل UIView دائمًا (!) حل لمهمة التخطيط الفعلي.

قدرات النظام كافية لمعظم العروض. لكنها ليست مناسبة لإنشاء أشرطة مع كمية كبيرة من المحتوى غير المتجانسة. لماذا؟


من المهم أن تتذكر أن التنسيق التلقائي:


  • يعمل فقط في الموضوع الرئيسي . افترض أن مهندسي Apple قد اختاروا Mainstream كنقطة التزامن في حل Auto Layout وقيم الإطار لجميع UIView . بدون هذا ، سيكون عليك حساب "التخطيط التلقائي" في سلسلة رسائل منفصلة ومزامنة القيم مع سلسلة الرسائل الرئيسية باستمرار.
  • يمكن أن تعمل ببطء على تمثيلات معقدة ، لأنها تستند إلى خوارزمية القوة الغاشمة التي التعقيد في أسوأ الحالات هو الأسي.
  • متوفر مع نظام iOS 6.0 . هذه المشكلة ليست بالكاد ، لكن الأمر يستحق الاهتمام.

الخلاصة: باستخدام التنسيق التلقائي ، يكون من المناسب إنشاء شاشات بدون أو مع مجموعات ، ولكن بدون علاقات معقدة بين العناصر.


1.2. حساب frame اليدوي


جوهر النهج: نحسب جميع قيم frame بأنفسنا. على سبيل المثال ، نحن ننفذ أساليب layoutSubviews ، sizeThatFits . وهذا هو ، في layoutSubviews أنفسنا نرتب جميع العناصر الفرعية ، في sizeThatFits ، sizeThatFits ما نحسب حجم المقابلة للموقع المطلوب للعناصر والمحتوى التابع.


ماذا يعطي؟ يمكننا نقل الحسابات المعقدة إلى دفق الخلفية ، ويمكن إجراء حسابات بسيطة نسبيًا في الدفق الرئيسي.


ما هي المشكلة؟ عليك أن تنفذ الحسابات بنفسك ، فمن السهل أن ترتكب خطأ. تحتاج أيضًا إلى التأكد من أن موقف الأطفال والنتائج التي يتم إرجاعها في sizeThatFits .


التقييم الذاتي له ما يبرره إذا:


  • لقد صادفنا أو نتوقع أننا سنواجه قيود أداء Auto Layout.
  • يحتوي التطبيق على مجموعة معقدة ، وهناك فرصة جيدة أن يسقط العنصر المطوّر في إحدى خلاياه ؛
  • نحن نريد حساب حجم العنصر في مؤشر ترابط الخلفية ؛
  • نعرض عناصر غير قياسية على الشاشة ، يجب إعادة حساب حجمها باستمرار حسب المحتوى أو البيئة.

مثال ارسم تلميحات الأدوات التي تتسع تلقائيًا لتناسب المحتوى. الجزء الأكثر إثارة للاهتمام في هذه المهمة هو كيفية حساب الحجم المرئي للنص في كل تلميح الأدوات.




2. حساب حجم النص


يمكن حل هذه المشكلة بأربعة طرق على الأقل ، يعتمد كل منها على مجموعة الطرق الخاصة به. ولكل منها خصائصها والقيود الخاصة بها.


2.1. الطرق القياسية لحساب حجم UILabel / UITextView / UITextField


ترجع sizeThatFits (المستخدمة افتراضيًا في sizeToFit ) و intrinsicContentSize (المستخدمة في التنسيق التلقائي) الحجم المفضل لمحتوى العرض. على سبيل المثال ، بمساعدتهم ، يمكننا معرفة مقدار المساحة التي UILabel النص المكتوب في UILabel .


الجانب السلبي هو أن كلا الطريقتين تعملان فقط في الخيط الرئيسي - لا يمكن استدعاؤهما من الخلفية.


متى تكون الطرق القياسية مفيدة؟


  • إذا استخدمنا بالفعل sizeToFit أو Auto Layout.
  • عندما تكون هناك عناصر قياسية في الشاشة ، ونريد الحصول على حجمها في الكود.
  • لأي يعرض دون مجموعات معقدة.

2.2. طرق NSAttributedString / NSString


لاحظ sizeWithAttributes و sizeWithAttributes . لا أنصح باستخدامها لقراءة حجم محتويات UILabel / UITextView / UITextField . لم أجد في أي مكان في معلومات الوثائق أن أساليب NSString وطرق تخطيط عناصر UIView تستند إلى نفس الكود (نفس الفئات). تنتمي هاتان المجموعتان من الفئات إلى أطر عمل مختلفة: Foundation و UIKit ، على التوالي. ربما كان لديك بالفعل لتناسب النتيجة UILabel حجم UILabel ؟ أو هل NSString حقيقة أن NSString لا تأخذ حجم الرموز التعبيرية في الاعتبار ؟ هذه هي المشاكل التي يمكنك الحصول عليها.


سأخبرك أيضًا بالفصول الدراسية المسؤولة عن رسم النص في UILabel / UITextView / UITextField ، ولكن الآن UITextField نعود إلى الأساليب.


إن استخدام boundingRect و sizeWithAttributes يستحق ذلك إذا كنا:


  • نرسم عناصر واجهة غير قياسية باستخدام drawAtPoint أو drawAtPoint أو طرق أخرى NSAttributedString / NSAttributedString .
  • نريد أن ننظر في حجم العناصر في دفق الخلفية. مرة أخرى ، يكون هذا فقط عند استخدام طرق التقديم المناسبة.
  • ارسم على سياق اعتباطي ، على سبيل المثال ، اعرض خطًا أعلى الصورة.

2.3. TextKit


تتكون هذه الأداة من الفئات القياسية NLayoutManager و NSTextStorage و NSTextContainer . UILabel تخطيط UILabel / UITextView / UITextField عليها.


TextKit مريحة للغاية عندما تحتاج إلى وصف موقع النص بالتفصيل والإشارة إلى الأشكال التي سيتدفق حولها :



باستخدام TextKit ، يمكنك حساب حجم عناصر الواجهة في قائمة انتظار الخلفية ، بالإضافة إلى frame الأسطر / الأحرف . بالإضافة إلى ذلك ، يسمح لك الإطار برسم الحروف الرسومية وتغيير مظهر النص بالكامل في التخطيط الموجود. كل هذا يعمل في نظام iOS 7.0 وما فوق.


TextKit مفيد عندما تحتاج إلى:


  • عرض النص مع تخطيط معقد.
  • رسم النص على الصور.
  • حساب أحجام ركائز الفردية.
  • حساب عدد الخطوط ؛
  • استخدام نتائج العمليات الحسابية في UITextView .

أؤكد مرة أخرى. إذا كنت بحاجة إلى حساب حجم UITextView ، فسنقوم أولاً بتكوين مثيلات NSTextContainer و NSTextContainer و NSTextContainer ، ثم NSTextContainer بتمرير هذه الحالات إلى UITextView المقابلة ، حيث ستكون مسؤولة عن التخطيط. بهذه الطريقة فقط نضمن المصادفة الكاملة لجميع القيم.


لا تستخدم TextKit مع UILabel و UITextField ! بالنسبة لهم (على عكس UITextView ) لا يمكنك تكوين NSLayoutManager و NSTextStorage و NSTextContainer .


2.4. CoreText


هذه هي أداة النص المستوى الأدنى في دائرة الرقابة الداخلية. إنه يوفر أقصى قدر من التحكم في عرض الخطوط والأحرف والخطوط والمسافات البادئة. وهو ، مثل TextKit ، يسمح لك بحساب المعلمات المطبعية للنص ، مثل الخط الأساسي وحجم الإطار للخطوط الفردية.


كما تعلمون ، كلما زادت الحرية ، زادت المسؤولية. ولكي تحصل على نتائج جيدة باستخدام CoreText ، يجب أن تكون قادرًا على استخدام طرقها.


يوفر CoreText أمان مؤشر الترابط للعمليات على معظم الكائنات. هذا يعني أنه يمكننا استدعاء أساليبها من خيوط مختلفة. للمقارنة ، عند استخدام TextKit ، عليك أن تفكر في تسلسل مكالمات الطريقة.


يجب استخدام CoreText إذا:


  • هناك حاجة إلى واجهة برمجة تطبيقات بسيطة منخفضة المستوى للوصول المباشر إلى معلمات النص. يجب أن أقول على الفور أنه بالنسبة للغالبية العظمى من المهام ، فإن قدرات TextKit كافية.
  • هناك الكثير من العمل الذي يجب القيام به مع الخطوط الفردية ( CTLine ) والشخصيات / العناصر.
  • الدعم مهم في نظام التشغيل iOS 6.0.

بالنسبة لتغذية VKontakte ، استخدمنا CoreText. لماذا؟ في الوقت الذي قمنا فيه بتنفيذ الوظائف الأساسية للعمل مع النص ، لم يكن TextKit موجودًا بعد.




3. كيف تعمل تغذية فكونتاكتي؟


باختصار حول كيفية استلامنا للبيانات من الخادم وتخطيط النماذج والعروض.



أولاً ، ضع في اعتبارك المهام التي تم تنفيذها في قائمة انتظار الخلفية. نتلقى بيانات من الخادم ، ونقوم بمعالجتها ونصف العرض اللاحق. في هذه المرحلة ، ليس لدينا مثيلات UIView ، فنحن فقط نضع قواعد وهيكل الواجهة المستقبلية باستخدام أداة التعريف الخاصة بنا ، والتي تشبه إلى حد ما SwiftUI . لحساب التخطيط ، نحسب frame بأكمله مع مراعاة القيود الحالية ، على سبيل المثال ، عرض الشاشة. نجعل تحديث مصدر البيانات الحالي ( dataSourceUpdate ). هنا ، في قائمة انتظار الخلفية ، نقوم بإعداد الصور: إجراء إلغاء الضغط (انظر قسم الأداء لمزيد من التفاصيل) ، ورسم الظلال ، والجولات ، والتأثيرات الأخرى.


انتقل الآن إلى قائمة الانتظار الرئيسية. dataSourceUpdate المستلمة على dataSourceUpdate استخدام أحداث الواجهة ، dataSourceUpdate .


لوصف نظام التصميم الخاص بنا ، ستكون هناك حاجة إلى مقالة منفصلة ، لكن هنا سأدرج ميزاته الرئيسية:


  • API التعريفي هو مجموعة من القواعد التي بنيت عليها الواجهة.
  • المكونات الأساسية تشكل شجرة ( nodes ).
  • حسابات بسيطة في المكونات الأساسية. على سبيل المثال ، في القوائم ، نحسب إزاحة origin فقط ، مع مراعاة عرض / ارتفاع جميع الأطفال.
  • لا تنشئ العناصر الأساسية "حاويات" غير ضرورية لـ UIView في التسلسل الهرمي. على سبيل المثال ، لا يشكل مكون القائمة UIView إضافية ولا يضيف إليه أطفالًا. بدلاً من ذلك ، نحسب إزاحة origin الأطفال بالنسبة إلى العنصر الأصل (لقائمة).
  • إدارة النص منخفضة المستوى مع CoreText.

ولكن حتى مع هذا النهج ، قد لا يكون عرض الشريط سلسًا بسبب مشكلات في الأداء. لماذا؟


كل خلية لها تسلسل هرمي معقد من nodes . وعلى الرغم من أن العناصر الأساسية لا تنشئ حاويات غير ضرورية ، UIView يزال UIView عرض الكثير من UIView في الشريط. وعند ملء التسلسل الهرمي بـ "العقد" (عرض الربط) في قائمة الانتظار الرئيسية ، هناك عمل إضافي يصعب الابتعاد عنه.


لقد حاولنا نقل أكبر عدد ممكن من المهام إلى قائمة انتظار الخلفية ونواصل الآن القيام بذلك. بالإضافة إلى ذلك ، هناك عمليات كثيفة وحدة المعالجة المركزية والمعالجات المكثفة التي يجب أن تؤخذ في الاعتبار والتحايل عليها.




4. كيفية تحقيق أداء أفضل


أبسط إجابة هي إلغاء تحميل السلسلة الرئيسية ووحدة المعالجة المركزية ووحدة معالجة الرسومات. للقيام بذلك ، تحتاج إلى فهم عميق لعمل تطبيقات دائرة الرقابة الداخلية. وقبل كل شيء ، تحديد مصادر المشاكل.


4.1 لماذا مشاكل الأداء


الرسوم المتحركة الأساسية ، RunLoop والتمرير
لنتذكر كيف تم بناء الواجهة في نظام iOS. في المستوى الأعلى ، هناك UIKit ، المسؤولة عن التفاعل مع المستخدم: التعامل مع الإيماءات ، واستيقاظ التطبيق من النوم ، وأشياء مماثلة. لتقديم الواجهة ، تكون الأداة ذات المستوى الأدنى مسؤولة - الرسوم المتحركة الأساسية (كما هو الحال في نظام التشغيل MacOS). هذا إطار مع نظام وصف الواجهة الخاص به. النظر في المفاهيم الأساسية لبناء واجهة.


بالنسبة لـ Core Animation ، الواجهة بالكامل هي طبقات CALayer . أنها تشكل شجرة تجسيد ، تدار من خلال المعاملات CATransaction .


المعاملة هي مجموعة من التغييرات ، وبشكل أكثر دقة ، معلومات حول الحاجة إلى تحديث شيء ما في الواجهة المعروضة. يقع أي تغيير في frame أو معلمات طبقة أخرى في المعاملة الحالية. إن لم يكن بالفعل ، فإن النظام نفسه ينشئ معاملة ضمنية .


العديد من المعاملات تشكل مكدس. تحديثات جديدة تندرج في أعلى معاملة المكدس.


نعلم الآن أنه لتحديث الشاشة ، نحتاج إلى تشكيل معاملات بمعلمات جديدة لشجرة الطبقة.



متى وكيف تنشئ المعاملات؟ في تطبيقنا ، المواضيع لديها كيان يسمى RunLoop . بعبارات بسيطة ، هذه حلقة لا نهائية ، يتم عندها معالجة قائمة الانتظار الحالية للأحداث .


في مؤشر الترابط الرئيسي ، هناك حاجة إلى RunLoop لمعالجة الأحداث من مصادر مختلفة ، مثل الواجهة (الإيماءات) أو NSStream أو على سبيل المثال معالجات لتلقي البيانات من NSStream و NSPort .



كيف ترتبط الرسوم المتحركة الأساسية و RunLoop ؟ لقد وجدنا أعلاه أنه عند تغيير خصائص طبقة في Render Tree ، يقوم النظام بإنشاء معاملات ضمنية إذا لزم الأمر (لذلك ، نحن لسنا بحاجة إلى استدعاء CATransaction.begin لإعادة رسم شيء). علاوة على ذلك ، في كل تكرار من RunLoop النظام تلقائيًا بإغلاق المعاملات المفتوحة ويطبق التغييرات التي تم إجراؤها ( CATransaction.commit ).


انتبه! لا يعتمد عدد التكرارات RunLoop على معدل تحديث الشاشة. لا تتم مزامنة الدورة مع الشاشة على الإطلاق وتعمل مثل "لانهائي while() ".


الآن دعونا نرى ما يحدث في تكرارات RunLoop على مؤشر الترابط الرئيسي أثناء التمرير:


  ... if (dispatchBlocks.count > 0) { //   MainQueue doBlocks() } ... if (hasPanEvent) { handlePan() // UIScrollView change content offset -> change bounds } ... if (hasCATransaction) { CATransaction.commit() } ... 

أولاً ، يتم تنفيذ الكتل المضافة إلى قائمة الانتظار الرئيسية من خلال dispatch_async / dispatch_sync . وحتى يتم إكمالها ، لا ينتقل البرنامج إلى المهام التالية.


بعد ذلك ، تبدأ UIKit في معالجة إيماءة المستخدم . كجزء من معالجة هذه الإيماءة ، يتغير UIScrollView.contentOffset ، ونتيجة لذلك ، UIScrollView.bounds . يؤدي تغيير bounds UIScrollView (على التوالي ، وأحفادها UICollectionView ، UICollectionView ) إلى تحديث الجزء المرئي من المحتوى ( viewport ).


في نهاية التكرار RunLoop ، إذا كان لدينا معاملات مفتوحة ، يحدث commit أو flush تلقائيًا.


للتحقق من كيفية عمل ذلك ، ضع نقاط التوقف في الأماكن المناسبة.
إليك ما ستبدو عليه عملية الإيماءات:



CATransaction.commit بعد handlePan :



أثناء تباطؤ عملية التمرير ، تنشئ CADisplayLink جهاز CADisplayLink وقت CADisplayLink لمزامنة عدد التغييرات التي يتم إجراؤها على contentOffset في الثانية مع معدل تحديث الشاشة.



CATransaction.commit أن CATransaction.commit لا يحدث في نهاية التكرار RunLoop ، ولكن مباشرة في معالجة مؤقت CADisplayLink . لكن هذا لا يهم:



4.2. CATransaction.commit


في الواقع ، يتم تنفيذ جميع العمليات داخل CATransaction.commit على طبقات CALayer . layoutSublayers طرقها الخاصة لتحديث التنسيق ( layoutSublayers ) والصورة ( drawLayer ). ينتج عن التطبيق الافتراضي لهذه الطرق استدعاءات أسلوب المفوض . بإضافة مثيل جديد من UIView إلى التسلسل الهرمي UIView ، فإننا نضيف ضمنيًا الطبقة المقابلة إلى التسلسل الهرمي لطبقة الرسوم المتحركة الأساسية. في هذه الحالة ، يكون UIView افتراضيًا مفوضًا لطبقته. كما ترون من مكدس الاستدعاءات ، تنفذ UIView كجزء من تنفيذ أساليب تفويض CALayer ، أساليبها ، والتي سيتم مناقشتها:



نظرًا لأننا عادةً ما نعمل مع التسلسل الهرمي UIView ، UIView الوصف مع أمثلة UIView .


أثناء CATransaction.commit ، يتم تنفيذ تخطيط جميع UIView المميزة بـ setNeedsLayout . تجدر الإشارة إلى أن استدعاء layoutSubviews أو layoutIfNeeded مرة أخرى ليس منطقيًا بسبب تنفيذها المؤجل المضمون في النظام داخل CATransaction.commit . حتى لو قمت في إحدى المعاملات (بين المكالمات إلى CATransaction.begin و CATransaction.commit ) بتغيير frame عدة مرات واستدعاء setNeedsLayout ، فلن يتم تطبيق كل تغيير على الفور. لن تسري التغييرات النهائية إلا بعد الاتصال بـ CATransaction.commit . أساليب CALayer ذات الصلة: setNeedsLayout و layoutIfNeeded و layoutSublayers .


يتم تكوين مجموعة مشابهة للرسم بواسطة أساليب setNeedsDisplay و setNeedsDisplay . بالنسبة إلى CALayer هذا هو setNeedsDisplay و displayIfNeeded و drawLayer . يستدعي CATransaction.commit طرق التقديم على جميع العناصر المميزة بـ setNeedsDisplay . يشار إلى هذه الخطوة أحيانًا بالرسم خارج الشاشة.


مثال UITableView والراحة ، خذ UITableView :


  ... // Layout UITableView.layoutSubviews() //  ,   .. ... // Offscreen drawing UITableView.drawRect() //    ... 

UIKit تعيد استخدام UICollectionView / UICollectionView في layoutSubviews : تستدعي willDisplayCell مندوب willDisplayCell وما إلى ذلك. يحدث رسم Offscreen أثناء CATransaction.commit : يتم drawInContext أساليب drawInContext لجميع الطبقات أو drawRect لجميع UIView التي setNeedsDisplay . لاحظت عندما نرسم شيئًا ما في drawRect ، يحدث هذا في الخيط الرئيسي ، ونحن بحاجة ماسة إلى تغيير عرض الطبقات لإطار جديد. من الواضح أن مثل هذا الحل يمكن أن يكون غير فعال للغاية.


ماذا يحدث بعد ذلك في CATransaction.commit ؟ يتم إرسال Render Tree إلى Render Server.


4.3. تقديم خط أنابيب


استرجع العملية بأكملها لتشكيل إطار واجهة في iOS (خط أنابيب التقديم [WWDC 2014 Session 419. الرسومات المتقدمة والرسوم المتحركة لتطبيقات iOS]):



ليس فقط عملية تطبيقنا هي المسؤولة عن تشكيل الإطار - Core Animation تعمل أيضًا في عملية نظام منفصلة تسمى Render Server.


كيف يتم تشكيل الإطار. نحن (أو النظام بالنسبة لنا) نقوم بإنشاء معاملة جديدة ( CATransaction ) في التطبيق مع وصف لتغييرات الواجهة ، "الالتزام" بها ونقلها إلى Render Server. كل شيء ، على جانب التطبيق ، يتم العمل. بعد ذلك ، يقوم Render Server بفك تشفير المعاملة (Render Tree) ، ويستدعي الأوامر اللازمة على رقاقة الفيديو ، ويرسم إطارًا جديدًا ويعرضه على الشاشة.


ومن المثير للاهتمام ، عند إنشاء الإطار ، يتم استخدام "تعدد مؤشرات ترابط" معين. إذا كان معدل تحديث الشاشة 60 إطارًا في الثانية ، يتم تشكيل إطار جديد في المجموع ليس في 1/60 ، ولكن في 1/30 من الثانية. هذا لأنه بينما يعد التطبيق إطارًا جديدًا ، لا يزال Render Server يعالج الإطار السابق:



وبشكل تقريبي ، يتكون إجمالي وقت تكوين الإطار قبل عرضها على الشاشة من 1/60 ثانية في عمليتنا لتشكيل المعاملة و 1/60 ثانية في عملية Render Server عند معالجة المعاملة.


أود أن أدلي بالملاحظة التالية. يمكننا موازاة رسم الطبقات بأنفسنا وتقديم محتويات CGImage UIImage / CGImage في دفق الخلفية. بعد ذلك ، في CALayer.contents الرئيسية ، تحتاج إلى تعيين الصورة التي تم إنشاؤها لخاصية CALayer.contents . من حيث الأداء ، هذا هو نهج جيد للغاية. هذا هو المطورين الذين يستخدمون ذلك الملمس . ولكن نظرًا لأننا لا نستطيع تغيير CALayer.contents إلا في عملية إنشاء معاملة في عملية تطبيقنا ، لدينا فقط 1/60 ثانية عند 60 إطارًا لإنشاء صورة جديدة واستبدالها ، بدلاً من 1/30 ثانية (مع الأخذ في الاعتبار تحسينات وموازنة خط أنابيب التقديم مع Render Server ).


بالإضافة إلى ذلك ، لا يزال Render Server قادرًا على التعامل مع المزج (انظر أدناه) والتخزين المؤقت للطبقة القصيرة الأجل [iOS Core Animation: Advanced Techniques. نيك لوكوود]. 1/60 CALayer.contents , . .


: , .


4.4.


Main-thread



1. ( CATransaction.commit ) - UIView.layoutSubviews UIView (, CALayer ). , layoutSubviews / cellForRow / willDisplayCell .


2. drawInContext / drawRect . - Main- ( CATransaction.commit ) — . , .


3. . . CATransaction.commit , , .


4. . UIImage / CGImage .


5. . Main-thread , scroll. - , UI.


6. Main-. , RunLoop Main- , , Main-. .


GPU



Blending . GPU ( Render Server GPU, ). , , Background-.


. , UIBlurEffect , UIVibrancyEffect , , (Render Pass). , , .


Offscreen rendering (Render Server)



Render Server . , , :



CALayer , , Offscreen rendering. , UIVisualEffect ( , Render Server CPU, GPU).


, .




5.


, , Time Profiler. Metal System Trace — Time Profiler .


5.1. Metal System Trace


, ( ). , : , .


, Metal System Trace , . , Render Server. , Main-, — , .



- , :



Metal System Trace . 64- , iPhone 5s. , . , - , , UI.


5.2.


. , - - . , CADisplayLink .


CADisplayLink timestamp — ( Render Server). CADisplayLink.timestamp timestamp . , (, 1/60 ) :


  //  CADisplayLink. link = [CADisplayLink displayLinkWithTarget:target selector:selector] [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode] //    CADisplayLink : diff = prevTimestamp - link.timestamp if (diff > 1/fps) { //  freeze } prevTimestamp = link.timestamp 

CADisplayLink UITrackingRunLoopMode , .


Rendering Pipeline:


UI-, . «» freezeFrameTimeRate :


 scrollTime //    Scroll freezeFrameTime //    ,  "",       freezeFrameTimeRate = freezeFrameTime / scrollTime 

, - UIView . , «»:



, , « UIView » . لماذا؟ , . , , , : CADisplayLink , Render Server link.timetamp , Render Server , . 60 UI-, Render Server. Render Server , .


, , , Render Server . Metal , Render Server. , , iOS, Render Server .


.


, , . , .


: — ! — .




استنتاج


— . , , .





, — . , .


, :


  1. Apple .
  2. Auto Layout .
  3. The Cassowary Linear Arithmetic Constraint Solving Algorithm .
  4. iOS Core Animation: Advanced Techniques. Nick Lockwood.
  5. WWDC 2014 Session 419. Advanced Graphics and Animations for iOS Apps.

Source: https://habr.com/ru/post/ar481626/


All Articles