I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: ไม่มี "ดีที่สุด" หรือ "วิธีที่ถูกที่สุด" สำหรับการสร้างสถาปัตยกรรมแอปพลิเคชัน มันเป็นงานที่สร้างสรรค์มาก คุณควรเลือกสถาปัตยกรรมที่ตรงไปตรงมาที่สุดและขยายได้ซึ่งจะชัดเจนสำหรับนักพัฒนาใด ๆ ที่เริ่มทำงานในโครงการของคุณหรือสำหรับนักพัฒนาอื่น ๆ ในทีมของคุณ แต่ฉันยอมรับว่าอาจมี "ดี" และ "ไม่ดี" "สถาปัตยกรรม
คุณพูดว่า: collect the most interesting approaches from experienced iOS developers
ฉันไม่คิดว่าวิธีการของฉันน่าสนใจที่สุดหรือถูกต้อง แต่ฉันใช้มันในหลาย ๆ โครงการและพอใจกับมัน มันเป็นวิธีการผสมผสานของสิ่งที่คุณได้กล่าวถึงข้างต้นและยังมีการปรับปรุงจากความพยายามในการวิจัยของฉันเอง ฉันน่าสนใจในปัญหาของวิธีการสร้างซึ่งรวมหลายรูปแบบที่รู้จักกันดีและสำนวน ฉันคิดว่ารูปแบบองค์กรจำนวนมากของFowlerสามารถนำไปใช้กับแอปพลิเคชันมือถือได้สำเร็จ นี่คือรายการของสิ่งที่น่าสนใจที่สุดซึ่งเราสามารถนำไปใช้สำหรับการสร้างสถาปัตยกรรมแอปพลิเคชั่น iOS ( ในความคิดของฉัน ): Service Layer , Unit of Work , Remote Facade , Data Transfer Object ,เกตเวย์ , ชั้น supertype , คดีพิเศษ , Domain รุ่น คุณควรออกแบบเลเยอร์โมเดลอย่างถูกต้องและอย่าลืมเกี่ยวกับการคงอยู่ (ซึ่งสามารถเพิ่มประสิทธิภาพของแอพได้อย่างมาก) คุณสามารถใช้Core Data
สำหรับสิ่งนี้ แต่คุณไม่ควรลืมนั่นCore Data
ไม่ใช่ ORM หรือฐานข้อมูล แต่เป็นตัวจัดการกราฟวัตถุที่มีอยู่เป็นตัวเลือกที่ดี ดังนั้นบ่อยครั้งมากที่จะCore Data
หนักเกินไปสำหรับความต้องการของคุณและคุณสามารถดูวิธีแก้ปัญหาใหม่ ๆ เช่นRealmและCouchbase Liteหรือสร้างการแมปวัตถุที่มีน้ำหนักเบา / เลเยอร์การติดตาของคุณขึ้นอยู่กับ SQLite หรือLevelDB ดิบ. นอกจากนี้ผมแนะนำให้คุณทำความคุ้นเคยกับการออกแบบขับเคลื่อนโดเมนและCQRS
ตอนแรกฉันคิดว่าเราควรสร้างเลเยอร์ใหม่สำหรับการสร้างเครือข่ายเพราะเราไม่ต้องการตัวควบคุมไขมันหรือโมเดลที่หนักหนาสาหัส ฉันไม่เชื่อในfat model, skinny controller
สิ่งเหล่านั้น แต่ฉันเชื่อในskinny everything
วิธีการเพราะชั้นเรียนไม่ควรอ้วนเลยทีเดียว เครือข่ายทั้งหมดสามารถแยกออกเป็นตรรกะทางธุรกิจโดยทั่วไปดังนั้นเราควรมีอีกชั้นหนึ่งที่เราสามารถวางไว้ได้ Service Layerคือสิ่งที่เราต้องการ:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
ในMVC
ดินแดนของเราService Layer
นั้นเป็นเหมือนคนกลางระหว่างโมเดลโดเมนและคอนโทรลเลอร์ มีความแตกต่างที่คล้ายกันของวิธีการนี้เรียกว่าMVCSโดยที่ a Store
เป็นService
ชั้นของเรา Store
อินสแตนซ์ของแบบจำลองและจัดการกับเครือข่ายแคช ฯลฯ ฉันต้องการพูดถึงว่าคุณไม่ควรเขียนเครือข่ายและตรรกะทางธุรกิจทั้งหมดของคุณในเลเยอร์บริการของคุณ นี่ก็ถือได้ว่าเป็นการออกแบบที่ไม่ดี สำหรับข้อมูลเพิ่มเติมดูที่โมเดลโดเมนAnemicและRich วิธีการบริการบางอย่างและตรรกะทางธุรกิจสามารถจัดการได้ในรูปแบบดังนั้นมันจะเป็นรูปแบบ "รวย" (พร้อมพฤติกรรม)
ฉันมักจะใช้อย่างกว้างขวางสองห้องสมุด: AFNetworking 2.0และReactiveCocoa ฉันคิดว่ามันเป็นสิ่งที่จำเป็นสำหรับแอพพลิเคชั่นที่ทันสมัยใด ๆ ที่โต้ตอบกับเครือข่ายและบริการบนเว็บหรือมีตรรกะ UI ที่ซับซ้อน
สถาปัตยกรรม
ตอนแรกผมสร้างทั่วไปAPIClient
ชั้นซึ่งเป็น subclass ของAFHTTPSessionManager นี่คือข้อผิดพลาดของการเชื่อมต่อเครือข่ายทั้งหมดในแอปพลิเคชัน: คลาสบริการทั้งหมดมอบหมายการร้องขอ REST จริงให้กับมัน มันมีการปรับแต่งทั้งหมดของไคลเอนต์ HTTP ซึ่งฉันต้องการในแอปพลิเคชันเฉพาะ: การปักหมุด SSL, การประมวลผลข้อผิดพลาดและการสร้างNSError
วัตถุที่ตรงไปตรงมาด้วยเหตุผลความล้มเหลวโดยละเอียดและคำอธิบายของAPI
ข้อผิดพลาดทั้งหมดและการเชื่อมต่อ ผู้ใช้), การตั้งค่าคำขอและการตอบสนอง serializers, ส่วนหัว http และสิ่งอื่น ๆ ที่เกี่ยวข้องกับเครือข่าย แล้วฉันมีเหตุผลแบ่งทั้งหมดคำขอ API เข้า subservices หรือมากกว่าอย่างถูกต้องmicroservices : UserSerivces
, CommonServices
, SecurityServices
,FriendsServices
และตามตรรกะทางธุรกิจที่ใช้ microservices เหล่านี้แต่ละคลาสแยกกัน Service Layer
พวกเขาร่วมกันในรูปแบบ คลาสเหล่านี้มีเมธอดสำหรับแต่ละคำขอ API ประมวลผลโมเดลโดเมนและส่งคืน a RACSignal
ด้วยโมเดลการตอบกลับที่แจงหรือNSError
ไปยังผู้เรียกเสมอ
ฉันต้องการพูดถึงว่าถ้าคุณมีตรรกะการทำให้เป็นอันดับแบบซับซ้อน - แล้วสร้างอีกชั้นสำหรับมัน: สิ่งที่ชอบData Mapperแต่ทั่วไปมากขึ้นเช่น JSON / XML -> mapper แบบจำลอง หากคุณมีแคช: สร้างเป็นเลเยอร์ / บริการแยกต่างหากด้วย (คุณไม่ควรใช้ตรรกะทางธุรกิจกับการแคช) ทำไม? เพราะชั้นแคชที่ถูกต้องนั้นค่อนข้างซับซ้อนด้วย gotchas ของตัวเอง ผู้คนใช้ตรรกะที่ซับซ้อนเพื่อให้ได้การแคชที่ถูกต้องและคาดการณ์ได้เช่นการแคชแบบ monoidal ที่มีการคาดการณ์โดยอิงจากผู้ชำนาญการ คุณสามารถอ่านเกี่ยวกับห้องสมุดที่สวยงามแห่งนี้ชื่อCarlosเพื่อทำความเข้าใจเพิ่มเติม และอย่าลืมว่า Core Data สามารถช่วยคุณในการแคชปัญหาทั้งหมดและจะช่วยให้คุณเขียนตรรกะน้อยลง นอกจากนี้หากคุณมีตรรกะบางอย่างระหว่างNSManagedObjectContext
และเซิร์ฟเวอร์ขอรุ่นคุณสามารถใช้รูปแบบพื้นที่เก็บข้อมูลซึ่งแยกตรรกะที่ดึงข้อมูลและแมปไปยังรูปแบบเอนทิตีจากตรรกะทางธุรกิจที่ทำหน้าที่ในรูปแบบ ดังนั้นฉันแนะนำให้ใช้รูปแบบ Repository แม้ว่าคุณจะมีสถาปัตยกรรมแบบ Core Data ก็ตาม พื้นที่เก็บข้อมูลสามารถสิ่งที่เป็นนามธรรมเช่นNSFetchRequest
, NSEntityDescription
, NSPredicate
และอื่น ๆ เพื่อวิธีการธรรมดาเหมือนหรือ
get
put
หลังจากการกระทำเหล่านี้ทั้งหมดในเลเยอร์บริการผู้เรียก (มุมมองตัวควบคุม) สามารถทำสิ่งอะซิงโครนัสที่ซับซ้อนบางอย่างด้วยการตอบสนอง: การจัดการสัญญาณการผูกมัดการแมป ฯลฯ ด้วยความช่วยเหลือของReactiveCocoa
ดั้งเดิมหรือเพียงแค่สมัครสมาชิก . ผมฉีดกับฉีดอยู่ในทุกชั้นเรียนบริการของฉันAPIClient
ซึ่งจะแปลบริการโทรเข้าโดยเฉพาะอย่างยิ่งที่เกี่ยวข้องGET
, POST
, PUT
, DELETE
ฯลฯ การร้องขอไปยังปลายทางที่เหลือ ในกรณีนี้APIClient
จะถูกส่งผ่านไปยังตัวควบคุมทั้งหมดโดยปริยายคุณสามารถทำให้สิ่งนี้ชัดเจนด้วยการกำหนดขอบเขตของAPIClient
คลาสบริการ วิธีนี้เหมาะสมถ้าคุณต้องการใช้การปรับแต่งที่แตกต่างกันของAPIClient
สำหรับคลาสบริการเฉพาะ แต่ถ้าคุณด้วยเหตุผลบางอย่างไม่ต้องการสำเนาเพิ่มเติมหรือคุณแน่ใจว่าคุณจะใช้หนึ่งอินสแตนซ์หนึ่งโดยเฉพาะ (โดยไม่มีการกำหนดเอง) ของAPIClient
- ทำให้เป็นซิงเกิล แต่ไม่ต้องการโปรดดอน ไม่ทำให้คลาสบริการเป็นแบบซิงเกิล
จากนั้นแต่ละตัวควบคุมมุมมองอีกครั้งด้วย DI ฉีดคลาสบริการที่ต้องการเรียกวิธีการบริการที่เหมาะสมและรวบรวมผลลัพธ์ของพวกเขาด้วยตรรกะ UI สำหรับฉีดพึ่งพาผมชอบที่จะใช้BloodMagicหรือกรอบการทำงานที่มีประสิทธิภาพมากขึ้นไต้ฝุ่น ฉันไม่เคยใช้ซิงเกิลตันAPIManagerWhatever
คลาสพระเจ้าหรือสิ่งผิดปกติอื่น ๆ เพราะถ้าคุณเรียกชั้นเรียนของคุณWhateverManager
นี้บ่งชี้กว่าที่คุณไม่ทราบว่าจุดประสงค์ของมันและมันก็เป็นทางเลือกการออกแบบที่ไม่ดี Singletons ยังเป็นแบบป้องกันและในกรณีส่วนใหญ่ (ยกเว้นของหายาก) เป็นวิธีการแก้ปัญหาที่ผิด ควรพิจารณาซิงเกิลตันเฉพาะเมื่อตรงตามเกณฑ์ทั้งสามข้อต่อไปนี้:
- ไม่สามารถมอบหมายความเป็นเจ้าของอินสแตนซ์เดี่ยวได้อย่างสมเหตุสมผล
- การเริ่มต้น Lazy เป็นที่พึงปรารถนา
- การเข้าถึงทั่วโลกไม่ได้มีไว้สำหรับ
ในกรณีที่เราเป็นเจ้าของอินสแตนซ์เดียวไม่ใช่ปัญหาและเราไม่ต้องการการเข้าถึงทั่วโลกหลังจากที่เราแบ่งผู้จัดการเทพเจ้าของเราเป็นบริการเพราะตอนนี้มีเพียงหนึ่งหรือหลายตัวควบคุมเฉพาะต้องการบริการเฉพาะ (เช่นUserProfile
ความต้องการควบคุมUserServices
และอื่น ๆ ) .
เราควรเคารพS
หลักการในSOLIDเสมอและใช้การแยกข้อกังวลดังนั้นอย่าใช้วิธีการบริการและเครือข่ายทั้งหมดของคุณในชั้นเดียวเพราะมันบ้าโดยเฉพาะอย่างยิ่งถ้าคุณพัฒนาแอปพลิเคชันองค์กรขนาดใหญ่ นั่นเป็นเหตุผลที่เราควรพิจารณาการฉีดพึ่งพาและวิธีการบริการ ผมคิดว่าวิธีการนี้เป็นที่ทันสมัยและมีการโพสต์ OO ในกรณีนี้เราแบ่งแอปพลิเคชันของเราออกเป็นสองส่วน: ตรรกะการควบคุม (ตัวควบคุมและเหตุการณ์) และพารามิเตอร์
พารามิเตอร์ชนิดหนึ่งจะเป็นพารามิเตอร์ "ข้อมูล" ทั่วไป นั่นคือสิ่งที่เราผ่านฟังก์ชั่น, จัดการ, แก้ไข, คงอยู่, ฯลฯ สิ่งเหล่านี้คือเอนทิตี, มวลรวม, คอลเลกชัน, คลาสเคส อีกประเภทหนึ่งคือพารามิเตอร์ "บริการ" เหล่านี้เป็นคลาสที่แค็ปซูลตรรกะทางธุรกิจอนุญาตให้สื่อสารกับระบบภายนอกให้การเข้าถึงข้อมูล
นี่คือขั้นตอนการทำงานทั่วไปของสถาปัตยกรรมของฉันตามตัวอย่าง สมมุติว่าเรามีFriendsViewController
, ซึ่งจะแสดงรายการเพื่อนของผู้ใช้และเรามีตัวเลือกให้ลบออกจากเพื่อน ฉันสร้างวิธีการในFriendsServices
ชั้นเรียนของฉันที่เรียกว่า:
- (RACSignal *)removeFriend:(Friend * const)friend
โดยที่Friend
เป็นโมเดล / โดเมนวัตถุ (หรือเป็นเพียงUser
วัตถุหากมีคุณลักษณะที่คล้ายกัน) underhood วิธีนี้จะแยกวิเคราะห์Friend
การNSDictionary
ของพารามิเตอร์ JSON friend_id
, name
, surname
, friend_request_id
และอื่น ๆ ฉันมักจะใช้ห้องสมุดMantleสำหรับหม้อไอน้ำประเภทนี้และสำหรับเลเยอร์โมเดลของฉัน (แยกวิเคราะห์ไปข้างหน้าและไปข้างหน้าการจัดการลำดับชั้นวัตถุที่ซ้อนกันใน JSON และอื่น ๆ ) หลังจากแยกที่เรียกว่าAPIClient
DELETE
วิธีการที่จะทำการร้องขอ REST ที่เกิดขึ้นจริงและผลตอบแทนResponse
ในRACSignal
การโทร ( FriendsViewController
ในกรณีของเรา) เพื่อแสดงข้อความที่เหมาะสมสำหรับผู้ใช้หรืออะไรก็ตาม
หากใบสมัครของเราใหญ่มากเราต้องแยกตรรกะของเราออกให้ชัดเจนยิ่งขึ้น เช่นมันไม่ดีเสมอไปในการผสมRepository
หรือจำลองตรรกะกับสิ่งService
ใดสิ่งหนึ่ง เมื่อผมอธิบายวิธีการของฉันฉันได้กล่าวว่าremoveFriend
วิธีการที่ควรจะอยู่ในService
ชั้น Repository
แต่ถ้าเราจะมีความรู้มากขึ้นเราสามารถสังเกตเห็นว่ามันจะดีกว่าที่เป็น จำไว้ว่า Repository คืออะไร Eric Evans ให้คำอธิบายที่ชัดเจนในหนังสือของเขา [DDD]:
พื้นที่เก็บข้อมูลแสดงวัตถุทั้งหมดของบางประเภทเป็นชุดแนวคิด มันทำหน้าที่เหมือนคอลเลกชันยกเว้นด้วยความสามารถในการสอบถามที่ซับซ้อนมากขึ้น
ดังนั้น a Repository
จึงเป็นส่วนหน้าซึ่งใช้ซีแมนทิกส์สไตล์การรวบรวม (เพิ่มอัปเดตลบ) เพื่อให้การเข้าถึงข้อมูล / วัตถุ นั่นเป็นเหตุผลที่เมื่อคุณมีสิ่งที่ชอบ: getFriendsList
, getUserGroups
, removeFriend
คุณสามารถวางไว้ในRepository
เพราะคอลเลกชันเหมือนความหมายสวยใสที่นี่ และรหัสเช่น:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
เป็นตรรกะทางธุรกิจอย่างแน่นอนเนื่องจากอยู่นอกเหนือCRUD
การดำเนินงานขั้นพื้นฐานและเชื่อมต่อสองวัตถุโดเมน ( Friend
และRequest
) นั่นคือเหตุผลที่ควรวางไว้ในService
เลเยอร์ นอกจากนี้ผมต้องการที่จะแจ้งให้ทราบล่วงหน้า: ไม่ได้สร้างแนวคิดที่ไม่จำเป็น ใช้วิธีการเหล่านี้อย่างชาญฉลาด เพราะหากคุณต้องการเอาชนะแอ็พพลิเคชันของคุณด้วย abstractions สิ่งนี้จะเพิ่มความซับซ้อนโดยไม่ตั้งใจและความซับซ้อนทำให้เกิดปัญหาในระบบซอฟต์แวร์มากกว่าสิ่งอื่นใด
ฉันอธิบายคุณเป็นตัวอย่าง "Objective-C" แบบเก่า แต่วิธีนี้สามารถดัดแปลงได้ง่ายสำหรับภาษา Swift พร้อมการปรับปรุงที่มากขึ้นเพราะมันมีคุณสมบัติที่มีประโยชน์และน้ำตาลที่ใช้งานได้ ผมขอแนะนำให้ใช้ห้องสมุดนี้: Moya ช่วยให้คุณสร้างAPIClient
เลเยอร์ที่หรูหรามากขึ้น(workhorse ของเราตามที่คุณจำได้) ตอนนี้APIClient
ผู้ให้บริการของเราจะเป็นประเภทค่า (enum) ที่มีส่วนขยายที่สอดคล้องกับโปรโตคอลและใช้ประโยชน์จากการจับคู่รูปแบบการทำลายล้าง Swift enums + การจับคู่รูปแบบช่วยให้เราสามารถสร้างประเภทข้อมูลพีชคณิตเช่นเดียวกับในการเขียนโปรแกรมการทำงานแบบคลาสสิก microservices ของเราจะใช้APIClient
ผู้ให้บริการที่ได้รับการปรับปรุงนี้เช่นเดียวกับใน Objective-C สำหรับเลเยอร์โมเดลแทนคุณMantle
สามารถใช้ไลบรารี ObjectMapperหรือฉันชอบที่จะใช้ห้องสมุด
Argo ที่หรูหราและใช้งานได้ดีขึ้น
ดังนั้นฉันจึงอธิบายวิธีสถาปัตยกรรมทั่วไปซึ่งสามารถปรับให้เหมาะกับการใช้งานใด ๆ ฉันคิดว่า แน่นอนว่าอาจมีการปรับปรุงเพิ่มเติมมากมาย ฉันแนะนำให้คุณเรียนรู้การเขียนโปรแกรมที่ใช้งานได้เพราะคุณจะได้ประโยชน์จากมันมาก แต่อย่าไปไกลเกินไป การกำจัดสถานะที่ไม่แน่นอนทั่วโลกที่ใช้ร่วมกันมากเกินไปการสร้างรูปแบบโดเมนที่ไม่เปลี่ยนรูปแบบหรือการสร้างฟังก์ชั่นที่บริสุทธิ์โดยไม่มีผลข้างเคียงจากภายนอกคือโดยทั่วไปแล้วแนวปฏิบัติที่ดีและSwift
ภาษาใหม่ๆ แต่โปรดจำไว้เสมอว่าการโอเวอร์โหลดโค้ดของคุณด้วยรูปแบบการใช้งานที่บริสุทธิ์อย่างหนักวิธีการตามหมวดหมู่เชิงทฤษฎีเป็นแนวคิดที่ไม่ดีเพราะนักพัฒนารายอื่นจะอ่านและสนับสนุนโค้ดของคุณและพวกเขาอาจหงุดหงิดหรือน่ากลัวprismatic profunctors
และสิ่งต่าง ๆ ในรูปแบบที่ไม่เปลี่ยนรูปของคุณ สิ่งเดียวกันกับReactiveCocoa
: อย่าใช้RACify
รหัสของคุณมากเกินไปเพราะมันจะไม่สามารถอ่านได้อย่างรวดเร็วโดยเฉพาะอย่างยิ่งสำหรับมือใหม่ ใช้เมื่อมันสามารถทำให้เป้าหมายและตรรกะของคุณง่ายขึ้น
read a lot, mix, experiment, and try to pick up the best from different architectural approaches
ดังนั้น มันเป็นคำแนะนำที่ดีที่สุดที่ฉันสามารถให้คุณได้