brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
คำสั่งสแกนเป็นฟังก์ชันหลักของ Periphery หากต้องการเริ่มต้นการตั้งค่าที่แนะนำ เพียงเปลี่ยนไดเร็กทอรีโปรเจ็กต์ของคุณแล้วรัน:
periphery scan --setup
การตั้งค่าที่แนะนำใช้ได้กับโปรเจ็กต์ Xcode และ SwiftPM เท่านั้น หากต้องการใช้ Periphery กับระบบบิลด์ที่ไม่ใช่ของ Apple เช่น Bazel โปรดดูระบบบิลด์
หลังจากตอบคำถามสองสามข้อแล้ว Periphery จะพิมพ์คำสั่งสแกนแบบเต็มและดำเนินการ
การตั้งค่าที่แนะนำมีไว้เพื่อวัตถุประสงค์เบื้องต้นเท่านั้น เมื่อคุณคุ้นเคยกับ Periphery แล้ว คุณสามารถลองใช้ตัวเลือกขั้นสูงเพิ่มเติมได้ ซึ่งทั้งหมดนี้สามารถดูได้ด้วย periphery help scan
เพื่อให้ได้ผลลัพธ์ที่สอดคล้องกันจาก Periphery จำเป็นอย่างยิ่งที่จะต้องเข้าใจความหมายของเป้าหมายการสร้างที่คุณเลือกวิเคราะห์ ตัวอย่างเช่น ลองจินตนาการถึงโปรเจ็กต์ที่ประกอบด้วยสามเป้าหมาย: แอป Lib และการทดสอบ เป้าหมายแอปนำเข้า Lib และเป้าหมายการทดสอบนำเข้าทั้งแอปและ Lib หากคุณต้องระบุทั้งสามตัวเลือกให้กับ --targets
ตัวเลือก Periphery จะสามารถวิเคราะห์โปรเจ็กต์ของคุณโดยรวมได้ อย่างไรก็ตาม หากคุณเลือกที่จะวิเคราะห์ App และ Lib เท่านั้น แต่ไม่ใช่การทดสอบ Periphery อาจรายงานบางอินสแตนซ์ของโค้ดที่ไม่ได้ใช้ซึ่งมีการอ้างอิงโดยการทดสอบ เท่านั้น ดังนั้นเมื่อคุณสงสัยว่า Periphery ให้ผลลัพธ์ที่ไม่ถูกต้อง สิ่งสำคัญคือต้องพิจารณาเป้าหมายที่คุณเลือกวิเคราะห์
หากโปรเจ็กต์ของคุณประกอบด้วยเฟรมเวิร์กแบบสแตนด์อโลนตั้งแต่หนึ่งเฟรมขึ้นไปที่ไม่มีแอปพลิเคชันบางประเภทที่ใช้อินเทอร์เฟซ คุณจะต้องบอก Periphery ให้ถือว่าการประกาศสาธารณะทั้งหมดนั้นจริง ๆ แล้วถูกใช้โดยการรวม --retain-public
ตัวเลือก.
สำหรับโปรเจ็กต์ที่ผสม Objective-C และ Swift ขอแนะนำอย่างยิ่งให้คุณอ่านเกี่ยวกับผลกระทบที่อาจเกิดขึ้นกับผลลัพธ์ของคุณ
เมื่อคุณได้เลือกตัวเลือกที่เหมาะสมสำหรับโครงการของคุณแล้ว คุณอาจต้องการคงตัวเลือกเหล่านั้นไว้ในไฟล์การกำหนดค่า YAML วิธีที่ง่ายที่สุดในการบรรลุเป้าหมายนี้คือการรัน Periphery ด้วยตัวเลือก --verbose
ใกล้กับจุดเริ่มต้นของเอาต์พุต คุณจะเห็นส่วน [configuration:begin]
โดยมีการกำหนดค่าของคุณอยู่ในรูปแบบ YAML ด้านล่าง คัดลอกและวางการกำหนดค่าลงใน .periphery.yml
ในรูทของโฟลเดอร์โปรเจ็กต์ของคุณ ตอนนี้คุณสามารถเรียกใช้ periphery scan
แล้วระบบจะใช้การกำหนดค่า YAML
Periphery จะสร้างโปรเจ็กต์ของคุณก่อน สำหรับโปรเจ็กต์ Xcode โครงร่างที่ให้ไว้ผ่านตัวเลือก --schemes
จะถูกสร้างขึ้นโดยใช้ xcodebuild
สำหรับโปรเจ็กต์ Swift Package Manager แต่ละเป้าหมายที่ให้ไว้ผ่านตัวเลือก --targets
จะถูกสร้างขึ้นโดยใช้ swift build
คอมไพเลอร์ Swift ใช้เทคนิคที่เรียกว่าการสร้างดัชนีขณะสร้างเพื่อเติมที่เก็บดัชนีที่มีข้อมูลเกี่ยวกับโครงสร้างของซอร์สโค้ดของโปรเจ็กต์ของคุณ
หลังจากสร้างโปรเจ็กต์แล้ว Periphery จะดำเนินการขั้นตอนการจัดทำดัชนี สำหรับไฟล์ต้นฉบับทุกไฟล์ที่เป็นสมาชิกของเป้าหมายที่ให้ไว้ผ่านตัวเลือก --targets
Periphery จะได้รับข้อมูลโครงสร้างจากที่จัดเก็บดัชนี และสร้างการแสดงกราฟภายในของโปรเจ็กต์ของคุณ นอกจากนี้ Periphery ยังวิเคราะห์แผนผังไวยากรณ์เชิงนามธรรม (AST) ของแต่ละไฟล์เพื่อกรอกรายละเอียดบางอย่างที่ไม่ได้มาจากที่เก็บดัชนี
เมื่อการจัดทำดัชนีเสร็จสมบูรณ์ Periphery จะวิเคราะห์กราฟเพื่อระบุโค้ดที่ไม่ได้ใช้ ระยะนี้ประกอบด้วยขั้นตอนต่างๆ ที่จะเปลี่ยนแปลงกราฟเพื่อให้ระบุสถานการณ์เฉพาะของโค้ดที่ไม่ได้ใช้ได้ง่ายขึ้น ขั้นตอนสุดท้ายจะเดินตามกราฟจากรากเพื่อระบุการประกาศที่ไม่มีการอ้างอิงอีกต่อไป
เป้าหมายของ Periphery คือการรายงานอินสแตนซ์ของ การประกาศ ที่ไม่ได้ใช้ การประกาศคือ class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
ฯลฯ ตามที่คุณคาดหวัง Periphery สามารถระบุการประกาศที่ไม่มีการอ้างอิงอย่างง่าย เช่น class
ที่ไม่ได้ใช้อีกต่อไปในที่ใดของคุณ รหัสฐาน
อุปกรณ์ต่อพ่วงยังสามารถระบุอินสแตนซ์ขั้นสูงของโค้ดที่ไม่ได้ใช้ได้ ส่วนต่อไปนี้จะอธิบายรายละเอียดเหล่านี้
อุปกรณ์ต่อพ่วงสามารถระบุพารามิเตอร์ฟังก์ชันที่ไม่ได้ใช้ได้ อินสแตนซ์ของพารามิเตอร์ที่ไม่ได้ใช้ยังสามารถระบุได้ในโปรโตคอลและการประกาศที่สอดคล้อง เช่นเดียวกับพารามิเตอร์ในวิธีการแทนที่ สถานการณ์ทั้งสองนี้มีอธิบายเพิ่มเติมด้านล่าง
พารามิเตอร์ที่ไม่ได้ใช้ของฟังก์ชันโปรโตคอลจะถูกรายงานว่าไม่ได้ใช้ก็ต่อเมื่อไม่ได้ใช้พารามิเตอร์ในการใช้งานทั้งหมดด้วย
protocol Greeter {
func greet ( name : String )
func farewell ( name : String ) // 'name' is unused
}
class InformalGreeter : Greeter {
func greet ( name : String ) {
print ( " Sup " + name + " . " )
}
func farewell ( name : String ) { // 'name' is unused
print ( " Cya. " )
}
}
เคล็ดลับ
คุณสามารถละเว้นพารามิเตอร์ที่ไม่ได้ใช้ทั้งหมดจากโปรโตคอลและปรับฟังก์ชันให้สอดคล้องกับตัวเลือก
--retain-unused-protocol-func-params
เช่นเดียวกับโปรโตคอล พารามิเตอร์ของฟังก์ชันที่ถูกแทนที่จะถูกรายงานว่าไม่ได้ใช้หากไม่ได้ใช้ในฟังก์ชันพื้นฐานและฟังก์ชันที่แทนที่ทั้งหมดด้วย
class BaseGreeter {
func greet ( name : String ) {
print ( " Hello. " )
}
func farewell ( name : String ) { // 'name' is unused
print ( " Goodbye. " )
}
}
class InformalGreeter : BaseGreeter {
override func greet ( name : String ) {
print ( " Sup " + name + " . " )
}
override func farewell ( name : String ) { // 'name' is unused
print ( " Cya. " )
}
}
พารามิเตอร์ที่ไม่ได้ใช้ของโปรโตคอลหรือคลาสที่กำหนดไว้ในโมดูลภายนอก (เช่น Foundation) จะถูกละเว้นเสมอ เนื่องจากคุณไม่มีสิทธิ์เข้าถึงเพื่อแก้ไขการประกาศฟังก์ชันพื้นฐาน
พารามิเตอร์ที่ไม่ได้ใช้ของฟังก์ชันที่เรียกง่ายๆ ว่า fatalError
ก็จะถูกละเว้นเช่นกัน ฟังก์ชันดังกล่าวมักจะไม่มีการใช้งานตัวเริ่มต้นที่จำเป็นในคลาสย่อย
class Base {
let param : String
required init ( param : String ) {
self . param = param
}
}
class Subclass : Base {
init ( custom : String ) {
super . init ( param : custom )
}
required init ( param : String ) {
fatalError ( " init(param:) has not been implemented " )
}
}
โปรโตคอลที่สอดคล้องกับออบเจ็กต์จะไม่ถูกใช้จริง ๆ เว้นแต่จะถูกใช้เป็นประเภทที่มีอยู่ด้วยหรือเพื่อเชี่ยวชาญวิธีการ/คลาสทั่วไป อุปกรณ์ต่อพ่วงสามารถระบุโปรโตคอลที่ซ้ำซ้อนได้ไม่ว่าจะถูกปรับให้เข้ากับวัตถุเดียวหรือหลายวัตถุก็ตาม
protocol MyProtocol { // 'MyProtocol' is redundant
func someMethod ( )
}
class MyClass1 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass1! " )
}
}
class MyClass2 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass2! " )
}
}
let myClass1 = MyClass1 ( )
myClass1 . someMethod ( )
let myClass2 = MyClass2 ( )
myClass2 . someMethod ( )
ที่นี่เราจะเห็นได้ว่าแม้จะมีการเรียกใช้ someMethod
ทั้งสองแบบ แต่ก็ไม่มีจุดใดที่วัตถุจะเข้ารับประเภทของ MyProtocol
ดังนั้นตัวโปรโตคอลเองจึงซ้ำซ้อน และไม่มีประโยชน์ใดๆ จาก MyClass1
หรือ MyClass2
ที่ปฏิบัติตาม เราสามารถลบ MyProtocol
ออกพร้อมกับความสอดคล้องที่ซ้ำซ้อนแต่ละรายการได้ และเพียงแค่เก็บ someMethod
ไว้ในแต่ละคลาส
เช่นเดียวกับวิธีการปกติหรือคุณสมบัติของออบเจ็กต์ คุณสมบัติแต่ละรายการและวิธีการที่ประกาศโดยโปรโตคอลของคุณสามารถระบุได้ว่าไม่ได้ใช้
protocol MyProtocol {
var usedProperty : String { get }
var unusedProperty : String { get } // 'unusedProperty' is unused
}
class MyConformingClass : MyProtocol {
var usedProperty : String = " used "
var unusedProperty : String = " unused " // 'unusedProperty' is unused
}
class MyClass {
let conformingClass : MyProtocol
init ( ) {
conformingClass = MyConformingClass ( )
}
func perform ( ) {
print ( conformingClass . usedProperty )
}
}
let myClass = MyClass ( )
myClass . perform ( )
ตรงนี้เราจะเห็นได้ว่า MyProtocol
ถูกใช้อยู่ และไม่สามารถลบออกได้ อย่างไรก็ตาม เนื่องจาก unusedProperty
ไม่เคยถูกเรียกใช้บน MyConformingClass
ดังนั้น Periphery จึงสามารถระบุได้ว่าการประกาศ unusedProperty
ใน MyProtocol
นั้นไม่ได้ใช้เช่นกัน และสามารถลบออกได้พร้อมกับการใช้งานที่ไม่ได้ใช้ของ unusedProperty
นอกเหนือจากความสามารถในการระบุการแจกแจงที่ไม่ได้ใช้แล้ว Periphery ยังสามารถระบุกรณีการแจงนับที่ไม่ได้ใช้แต่ละรายการได้อีกด้วย การแจงนับธรรมดาที่ไม่สามารถเป็นตัวแทนได้ เช่น ที่ ไม่มี ประเภท String
, Character
, Int
หรือทศนิยม สามารถระบุได้อย่างน่าเชื่อถือ อย่างไรก็ตาม การแจงนับที่ มี ประเภทค่าดิบอาจเป็นแบบไดนามิกได้ ดังนั้นจึงต้องถือว่ามีการใช้
มาทำความเข้าใจเรื่องนี้ด้วยตัวอย่างสั้นๆ:
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
ไม่มีการอ้างอิงโดยตรงถึงกรณี myCase
ดังนั้นจึงสมเหตุสมผลที่จะคาดหวังว่ามัน อาจจะ ไม่จำเป็นอีกต่อไป อย่างไรก็ตาม หากมันถูกลบออก เราจะเห็นว่า somethingImportant
จะไม่ถูกเรียกหาก someFunction
ถูกส่งผ่านค่าของ "myCase"
คุณสมบัติที่ได้รับมอบหมายแต่ไม่เคยใช้จะถูกระบุเช่นนี้ เช่น:
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
ในบางกรณี นี่อาจเป็นพฤติกรรมที่ตั้งใจไว้ ดังนั้นคุณจึงมีตัวเลือกสองสามตัวในการปิดเสียงผลลัพธ์ดังกล่าว:
--retain-assign-only-property-types
ประเภทที่กำหนดจะต้องตรงกับการใช้งานที่แน่นอนในการประกาศคุณสมบัติ (ไม่มีเครื่องหมายคำถามเสริม) เช่น String
, [String]
, Set
Periphery ไม่สามารถแก้ไขประเภทคุณสมบัติที่อนุมานได้ ดังนั้นในบางกรณี คุณอาจต้องเพิ่มคำอธิบายประกอบประเภทที่ชัดเจนให้กับคุณสมบัติของคุณ--retain-assign-only-properties
การประกาศที่ทำเครื่องหมายเป็น public
แต่ไม่ได้อ้างอิงจากภายนอกโมดูลหลัก จะถูกระบุว่ามีการเข้าถึงสาธารณะที่ซ้ำซ้อน ในสถานการณ์สมมตินี้ คำอธิบายประกอบ public
สามารถลบออกจากการประกาศได้ การลบการเข้าถึงสาธารณะที่ซ้ำซ้อนออกมีข้อดีสองประการ:
final
โดยค้นหาการประกาศที่อาจแทนที่ทั้งหมดโดยอัตโนมัติ คลาส final
ได้รับการปรับให้เหมาะสมที่สุดโดยคอมไพเลอร์ การวิเคราะห์นี้สามารถปิดการใช้งานได้ด้วย --disable-redundant-public-analysis
อุปกรณ์ต่อพ่วงสามารถตรวจจับการนำเข้าเป้าหมายที่สแกนโดยไม่ได้ใช้ เช่น เป้าหมายที่ระบุด้วยอาร์กิวเมนต์ --targets
ไม่สามารถตรวจพบการนำเข้าเป้าหมายอื่นที่ไม่ได้ใช้เนื่องจากไฟล์ต้นฉบับ Swift ไม่พร้อมใช้งานและไม่สามารถสังเกตการใช้ @_exported
ได้ @_exported
เป็นปัญหาเนื่องจากจะเปลี่ยนอินเทอร์เฟซสาธารณะของเป้าหมาย โดยที่การประกาศที่ส่งออกโดยเป้าหมายไม่จำเป็นต้องประกาศโดยเป้าหมายที่นำเข้าอีกต่อไป ตัวอย่างเช่น เป้าหมาย Foundation
จะส่งออก Dispatch
ท่ามกลางเป้าหมายอื่นๆ หากไฟล์ต้นฉบับใดๆ นำเข้า Foundation
และอ้างอิง DispatchQueue
แต่ไม่มีการประกาศอื่นจาก Foundation
การนำเข้า Foundation
จะไม่สามารถลบออกได้ เนื่องจากจะทำให้ประเภท DispatchQueue
ไม่พร้อมใช้งานด้วย เพื่อหลีกเลี่ยงผลบวกลวง ดังนั้น Periphery จะตรวจจับเฉพาะการนำเข้าเป้าหมายที่สแกนโดยไม่ได้ใช้เท่านั้น
อุปกรณ์ต่อพ่วงมีแนวโน้มที่จะสร้างผลบวกลวงสำหรับเป้าหมายที่มี Swift และ Objective-C แบบผสม เนื่องจากอุปกรณ์ต่อพ่วงไม่สามารถสแกนไฟล์ Objective-C ได้ ดังนั้นจึงแนะนำให้ปิดการใช้งานการตรวจจับการนำเข้าที่ไม่ได้ใช้สำหรับโปรเจ็กต์ที่มี Objective-C จำนวนมาก หรือแยกเป้าหมายภาษาผสมออกจากผลลัพธ์ด้วยตนเอง
อุปกรณ์ต่อพ่วงไม่สามารถวิเคราะห์โค้ด Objective-C ได้เนื่องจากประเภทอาจถูกพิมพ์แบบไดนามิก
ตามค่าเริ่มต้น Periphery จะไม่ถือว่ามีการใช้การประกาศที่เข้าถึงได้โดยรันไทม์ Objective-C หากโปรเจ็กต์ของคุณเป็นการผสมผสานระหว่าง Swift และ Objective-C คุณสามารถเปิดใช้งานพฤติกรรมนี้ได้ด้วยตัวเลือก --retain-objc-accessible
การประกาศ Swift ที่สามารถเข้าถึงได้โดยรันไทม์ Objective-C คือการประกาศที่มีคำอธิบายประกอบอย่างชัดเจนด้วย @objc
หรือ @objcMembers
และคลาสที่สืบทอด NSObject
ทั้งทางตรงและทางอ้อมผ่านคลาสอื่น
อีกทางหนึ่ง --retain-objc-annotated
สามารถใช้เพื่อรักษาเฉพาะการประกาศที่มีคำอธิบายประกอบอย่างชัดเจนด้วย @objc
หรือ @objcMembers
ประเภทที่สืบทอด NSObject
จะไม่ถูกรักษาไว้เว้นแต่จะมีคำอธิบายประกอบที่ชัดเจน ตัวเลือกนี้อาจเปิดเผยโค้ดที่ไม่ได้ใช้มากขึ้น แต่มีข้อแม้ว่าผลลัพธ์บางส่วนอาจไม่ถูกต้องหากมีการใช้การประกาศในโค้ด Objective-C จริงๆ เพื่อแก้ไขผลลัพธ์ที่ไม่ถูกต้องเหล่านี้ คุณต้องเพิ่มคำอธิบายประกอบ @objc
ในการประกาศ
Swift สังเคราะห์โค้ดเพิ่มเติมสำหรับประเภท Codable
ที่ไม่สามารถมองเห็นได้จาก Periphery และอาจส่งผลให้เกิดผลบวกลวงสำหรับคุณสมบัติที่ไม่ได้อ้างอิงโดยตรงจากโค้ดที่ไม่ได้สังเคราะห์ หากโปรเจ็กต์ของคุณมีหลายประเภท คุณสามารถรักษาคุณสมบัติทั้งหมดในประเภท Codable
ได้ด้วย --retain-codable-properties
หรือคุณสามารถรักษาคุณสมบัติเฉพาะในประเภท Encodable
ด้วย --retain-encodable-properties
หากมีการประกาศความสอดคล้อง Codable
โดยโปรโตคอลในโมดูลภายนอกที่ไม่ได้สแกนโดย Periphery คุณสามารถสั่งให้ Periphery ระบุโปรโตคอลเป็น Codable
ด้วย --external-codable-protocols "ExternalProtocol"
คลาสใดๆ ที่สืบทอด XCTestCase
จะถูกเก็บรักษาไว้โดยอัตโนมัติพร้อมกับวิธีทดสอบ อย่างไรก็ตาม เมื่อคลาสสืบทอด XCTestCase
โดยอ้อมผ่านคลาสอื่น เช่น UnitTestCase
และคลาสนั้นอยู่ในเป้าหมายที่ไม่ได้สแกนโดย Periphery คุณจะต้องใช้ตัวเลือก --external-test-case-classes UnitTestCase
เพื่อสั่งให้ Periphery ถือว่า UnitTestCase
เป็นคลาสย่อย XCTestCase
หากโปรเจ็กต์ของคุณมีไฟล์ Interface Builder (เช่น สตอรี่บอร์ดและ XIB) Periphery จะพิจารณาสิ่งเหล่านี้เมื่อระบุการประกาศที่ไม่ได้ใช้ อย่างไรก็ตาม ปัจจุบัน Periphery ระบุเฉพาะคลาสที่ไม่ได้ใช้เท่านั้น ข้อจำกัดนี้มีอยู่เนื่องจาก Periphery ยังไม่ได้แยกวิเคราะห์ไฟล์ Interface Builder อย่างสมบูรณ์ (ดูปัญหา #212) เนื่องจากหลักการออกแบบของ Periphery ในการหลีกเลี่ยงผลบวกลวง จึงสันนิษฐานว่าหากมีการอ้างอิงคลาสในไฟล์ Interface Builder IBOutlets
และ IBActions
ทั้งหมดจะถูกนำมาใช้ แม้ว่าคลาสเหล่านั้นอาจไม่ใช่ในความเป็นจริงก็ตาม วิธีการนี้จะได้รับการแก้ไขเพื่อระบุ IBActions
และ IBOutlets
ที่ไม่ได้ใช้อย่างถูกต้อง เมื่อ Periphery ได้รับความสามารถในการแยกวิเคราะห์ไฟล์ Interface Builder
ไม่ว่าด้วยเหตุผลใดก็ตาม คุณอาจต้องการเก็บโค้ดที่ไม่ได้ใช้บางส่วนไว้ คำสั่งความคิดเห็นของซอร์สโค้ดสามารถใช้เพื่อละเว้นการประกาศเฉพาะ และแยกออกจากผลลัพธ์
คำสั่งละเว้นความคิดเห็นสามารถวางได้โดยตรงบนบรรทัดเหนือการประกาศใด ๆ ที่จะเพิกเฉย และการประกาศสืบทอดทั้งหมด:
// periphery:ignore
class MyClass { }
คุณยังสามารถละเว้นพารามิเตอร์ฟังก์ชันที่ไม่ได้ใช้เฉพาะได้:
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
คำสั่ง // periphery:ignore:all
สามารถวางไว้ที่ด้านบนของไฟล์ต้นฉบับเพื่อละเว้นเนื้อหาทั้งหมดของไฟล์ โปรดทราบว่าความคิดเห็นจะต้องอยู่เหนือโค้ดใดๆ รวมถึงคำสั่งนำเข้าด้วย
คำสั่งความคิดเห็นยังรองรับความคิดเห็นต่อท้ายที่ตามหลังยัติภังค์ เพื่อให้คุณสามารถใส่คำอธิบายในบรรทัดเดียวกันได้:
// periphery:ignore - explanation of why this is necessary
class MyClass { }
ก่อนที่จะตั้งค่าการรวม Xcode เราขอแนะนำอย่างยิ่งให้คุณให้ Periphery ทำงานในเทอร์มินัลก่อน เนื่องจากคุณจะใช้คำสั่งเดียวกันนี้ผ่าน Xcode
เลือกโปรเจ็กต์ของคุณใน Project Navigator แล้วคลิกปุ่ม + ที่ด้านซ้ายล่างของส่วนเป้าหมาย เลือก ข้ามแพลตฟอร์ม และเลือก รวม กดถัดไป
Periphery เป็นโครงการที่เกิดจากความหลงใหลซึ่งต้องใช้ความพยายามอย่างมากในการรักษาและพัฒนา หากคุณพบว่า Periphery มีประโยชน์ โปรดพิจารณาสนับสนุนผ่านผู้สนับสนุน GitHub
ขอขอบคุณเป็นพิเศษสำหรับผู้สนับสนุนใจดีดังต่อไปนี้:
SaGa Corp พัฒนาเทคโนโลยีที่เป็นเอกลักษณ์สำหรับผู้เล่นทางการเงินและลูกค้าของพวกเขา
Emerge Tools คือชุดผลิตภัณฑ์ที่ปฏิวัติวงการซึ่งออกแบบมาเพื่อเพิ่มพลังให้กับแอปบนอุปกรณ์เคลื่อนที่และทีมที่สร้างแอปเหล่านั้น