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

محتوى
- وصف وحساب التصميم
1.1. تخطيط السيارات
1.2. حساب frame
اليدوي - حساب حجم النص
2.1. الطرق القياسية لحساب حجم UILabel
/ UITextView
/ UITextField
2.2. طرق NSAttributedString
/ NSAttributedString
2.3. TextKit
2.4. CoreText - كيف تعمل خلاصة فكونتاكتي؟
- كيفية الحصول على أداء أفضل
4.1 لماذا مشاكل الأداء
4.2. CATransaction.commit
4.3. تقديم خط أنابيب
4.4. الأماكن الأكثر ضعفا للأداء - أدوات القياس
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 .
, ( ). , : , .
, 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 .
.
, , . , .
: — ! — .
استنتاج
— . , , .
, — . , .
, :
- Apple .
- Auto Layout .
- The Cassowary Linear Arithmetic Constraint Solving Algorithm .
- iOS Core Animation: Advanced Techniques. Nick Lockwood.
- WWDC 2014 Session 419. Advanced Graphics and Animations for iOS Apps.
