อันตรายจากวิธีการที่สั่นคลอนใน Objective-C คืออะไร


235

ฉันเคยได้ยินผู้คนกล่าวว่าวิธีการวกวนเป็นวิธีที่อันตราย แม้แต่ชื่อที่สั่นคลอนก็แสดงให้เห็นว่ามันเป็นการโกง

เมธอด Swizzlingกำลังแก้ไขการแม็พดังนั้นการเรียกตัวเลือก A จริง ๆ แล้วจะเรียกใช้งานบีหนึ่งในการใช้สิ่งนี้คือการขยายพฤติกรรมของคลาสที่มาปิด

เราสามารถทำตามความเสี่ยงให้เป็นรูปธรรมได้เพื่อให้ทุกคนที่กำลังตัดสินใจว่าจะใช้การ swizzling สามารถทำการตัดสินใจอย่างมีข้อมูลว่าคุ้มค่ากับสิ่งที่พวกเขาพยายามทำหรือไม่

เช่น

  • การตั้งชื่อการชน : ถ้าชั้นเรียนขยายการทำงานในภายหลังเพื่อรวมชื่อวิธีการที่คุณเพิ่มไว้มันจะทำให้เกิดปัญหาอย่างใหญ่หลวง ลดความเสี่ยงด้วยวิธีการตั้งชื่อแบบ swizzled อย่างสมเหตุสมผล

มันมีอยู่แล้วไม่ดีมากที่จะได้รับสิ่งอื่นที่คุณคาดหวังจากรหัสที่คุณได้อ่าน
มาร์ติน Babacaev

นี้ฟังดูเหมือนวิธีที่ไม่สะอาดในการทำสิ่งที่งูเหลือมตกแต่งทำอย่างหมดจด
Claudiu

คำตอบ:


439

ฉันคิดว่านี่เป็นคำถามที่ยอดเยี่ยมจริงๆและมันเป็นความอัปยศที่แทนที่จะจัดการกับคำถามจริง ๆ คำตอบส่วนใหญ่ทำให้ปัญหาเกิดขึ้น

การใช้วิธีการที่ร้อนจัดก็เหมือนกับการใช้มีดคม ๆ ในห้องครัว บางคนกลัวของมีดคมเพราะพวกเขาคิดว่าพวกเขาจะตัดตัวเองไม่ดี แต่ความจริงก็คือมีดที่คมชัดมีความปลอดภัย

การใช้วิธีการแบบ swizzling สามารถเขียนโค้ดได้ดีขึ้นมีประสิทธิภาพมากขึ้นและบำรุงรักษาได้มากขึ้น นอกจากนี้ยังสามารถถูกทารุณกรรมและนำไปสู่ข้อบกพร่องที่น่ากลัว

พื้นหลัง

เช่นเดียวกับรูปแบบการออกแบบทั้งหมดหากเราตระหนักถึงผลที่จะตามมาของรูปแบบนั้นอย่างเต็มที่เราสามารถตัดสินใจได้อย่างละเอียดมากขึ้นว่าจะใช้หรือไม่ Singletons เป็นตัวอย่างที่ดีของบางสิ่งบางอย่างที่ค่อนข้างขัดแย้งและด้วยเหตุผลที่ดี - มันยากที่จะนำไปใช้อย่างถูกต้อง หลายคนยังคงเลือกใช้ซิงเกิลตัน เช่นเดียวกันสามารถพูดเกี่ยวกับการว่องไว คุณควรแสดงความคิดเห็นของคุณเองเมื่อคุณเข้าใจทั้งดีและไม่ดี

อภิปรายผล

นี่คือบางส่วนของข้อผิดพลาดของวิธีการที่ว่องไว:

  • วิธีการที่ว่องไวไม่ได้เป็นอะตอม
  • เปลี่ยนพฤติกรรมของรหัสที่ไม่ได้เป็นเจ้าของ
  • ข้อขัดแย้งในการตั้งชื่อที่เป็นไปได้
  • Swizzling เปลี่ยนอาร์กิวเมนต์ของเมธอด
  • คำสั่งของ Swizzles สำคัญ
  • ยากที่จะเข้าใจ (ดูซ้ำ)
  • การดีบักยาก

ประเด็นเหล่านี้ถูกต้องทั้งหมดและในการจัดการกับปัญหาเราสามารถปรับปรุงทั้งความเข้าใจของเราเกี่ยวกับวิธีการที่เกิดปัญหาและวิธีการที่ใช้เพื่อให้ได้ผลลัพธ์ ฉันจะกินทีละคน

วิธีการที่ว่องไวไม่ได้เป็นอะตอม

ฉันยังไม่เห็นการดำเนินการตามวิธี swizzling ที่มีความปลอดภัยที่จะใช้ควบคู่กันไป1 นี่ไม่ใช่ปัญหาจริงใน 95% ของกรณีที่คุณต้องการใช้วิธีการแบบ swizzling โดยปกติแล้วคุณเพียงแค่ต้องการแทนที่การใช้งานของวิธีการและคุณต้องการให้การใช้งานที่จะใช้ตลอดชีวิตของโปรแกรมของคุณ ซึ่งหมายความว่าคุณควรทำวิธีการของคุณร้อน+(void)loadแรง loadวิธีการเรียนจะถูกดำเนินการเป็นลำดับที่เริ่มต้นของแอพลิเคชันของคุณ คุณจะไม่มีปัญหาใด ๆ กับการเห็นพ้องด้วยถ้าคุณทำที่นี่ หากคุณต้องรีบ+(void)initializeเสียงฟู่อย่างแรงคุณสามารถจบลงด้วยสภาพการแข่งขันในการติดตั้งแบบรวดเร็วและรันไทม์อาจสิ้นสุดลงในสภาวะที่แปลก

เปลี่ยนพฤติกรรมของรหัสที่ไม่ได้เป็นเจ้าของ

นี่เป็นปัญหาของการ swizzling แต่มันเป็นประเด็นทั้งหมด เป้าหมายคือสามารถเปลี่ยนรหัสนั้นได้ เหตุผลที่ผู้คนชี้ให้เห็นว่าเรื่องนี้เป็นเรื่องใหญ่เพราะคุณไม่เพียงแค่เปลี่ยนสิ่งต่าง ๆ สำหรับอินสแตนซ์เดียวNSButtonที่คุณต้องการเปลี่ยนสิ่งต่าง ๆ แต่สำหรับทุกคนNSButtonกรณีในใบสมัครของคุณ ด้วยเหตุนี้คุณจึงควรระมัดระวังเมื่อคุณ swizzle แต่คุณไม่จำเป็นต้องหลีกเลี่ยงโดยสิ้นเชิง

ลองใช้วิธีนี้ ... ถ้าคุณแทนที่เมธอดในคลาสและคุณไม่เรียกเมธอดของคลาส super คุณอาจทำให้เกิดปัญหา ในกรณีส่วนใหญ่ผู้เรียนระดับสูงคาดว่าจะเรียกใช้วิธีการนั้นได้ (เว้นแต่จะมีการบันทึกไว้เป็นอย่างอื่น) หากคุณใช้ความคิดแบบเดียวกันนี้กับการมั่วสุมคุณได้ครอบคลุมปัญหาส่วนใหญ่ เรียกการใช้งานดั้งเดิมเสมอ หากคุณไม่ทำคุณอาจเปลี่ยนไปมากเกินไปเพื่อความปลอดภัย

ข้อขัดแย้งในการตั้งชื่อที่เป็นไปได้

ความขัดแย้งในการตั้งชื่อเป็นปัญหาทั้งหมดทั่วทั้งโกโก้ เรามักจะนำหน้าชื่อคลาสและชื่อเมธอดในหมวดหมู่ น่าเสียดายที่การตั้งชื่อความขัดแย้งเป็นภัยพิบัติในภาษาของเรา ในกรณีที่มีการว้าวุ่นพวกเขาไม่จำเป็นต้องเป็น เราเพียงแค่ต้องเปลี่ยนวิธีการที่เราคิดเกี่ยวกับวิธีการที่สั่นคลอนเล็กน้อย ส่วนใหญ่จะเป็นแบบนี้:

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

มันใช้งานได้ดี แต่จะเกิดอะไรขึ้นถ้าmy_setFrame:ถูกกำหนดไว้ที่อื่น? ปัญหานี้ไม่ได้เกิดจากการ swizzling แต่เราสามารถแก้ไขได้ วิธีแก้ปัญหามีประโยชน์เพิ่มเติมของการแก้ไขข้อผิดพลาดอื่น ๆ เช่นกัน นี่คือสิ่งที่เราทำแทน:

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

แม้ว่าสิ่งนี้จะดูคล้ายกับ Objective-C เล็กน้อย (เนื่องจากใช้ตัวชี้ฟังก์ชั่น) แต่ก็หลีกเลี่ยงความขัดแย้งในการตั้งชื่อ โดยหลักการแล้วมันกำลังทำสิ่งเดียวกันกับมาตรฐานการ swizzling นี่อาจเป็นการเปลี่ยนแปลงเล็กน้อยสำหรับผู้ที่ใช้การ swizzling ตามที่กำหนดไว้ในขณะที่ แต่ในท้ายที่สุดฉันคิดว่ามันจะดีกว่า วิธีการ swizzling ถูกกำหนดไว้ดังนี้:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

Swizzling โดยการเปลี่ยนชื่อเมธอดจะเปลี่ยนอาร์กิวเมนต์ของเมธอด

นี่คือสิ่งที่ยิ่งใหญ่ในใจของฉัน นี่คือเหตุผลที่ไม่ควรทำวิธีการมาตรฐานแบบ swizzling คุณกำลังเปลี่ยนข้อโต้แย้งที่ส่งผ่านไปยังการใช้วิธีการเดิม นี่คือที่มันเกิดขึ้น:

[self my_setFrame:frame];

บรรทัดนี้ทำอะไร:

objc_msgSend(self, @selector(my_setFrame:), frame);

my_setFrame:ซึ่งจะใช้รันไทม์ที่จะมองขึ้นการดำเนินงานของ เมื่อพบว่ามีการนำไปใช้งานจะเรียกใช้งานด้วยอาร์กิวเมนต์เดียวกันที่ได้รับ การนำไปปฏิบัติที่พบนั้นเป็นการนำไปปฏิบัติดั้งเดิมsetFrame:ดังนั้นจึงดำเนินการต่อไปและเรียกสิ่งนั้น แต่_cmdข้อโต้แย้งไม่setFrame:เหมือนที่ควรจะเป็น my_setFrame:ก็ตอนนี้ มีการเรียกใช้การเริ่มต้นด้วยอาร์กิวเมนต์โดยไม่คาดคิดว่าจะได้รับ มันไม่ดีเลย

มีวิธีแก้ปัญหาง่าย ๆ - ใช้เทคนิคทางเลือกใหม่ ๆ ที่กำหนดไว้ข้างต้น ข้อโต้แย้งจะยังคงไม่เปลี่ยนแปลง!

คำสั่งของ Swizzles สำคัญ

ลำดับที่วิธีการจะทำให้เกิดปัญหามากขึ้น สมมติว่าsetFrame:มีการกำหนดไว้เท่านั้นNSViewลองจินตนาการถึงสิ่งต่อไปนี้:

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

จะเกิดอะไรขึ้นเมื่อวิธีการNSButtonเป็นแบบ swizzled? การเปลี่ยนแปลงที่รวดเร็วส่วนใหญ่จะช่วยให้มั่นใจได้ว่าจะไม่แทนที่การใช้งานของsetFrame:ทุกมุมมองดังนั้นจึงจะดึงวิธีการอินสแตนซ์ สิ่งนี้จะใช้การนำไปปฏิบัติที่มีอยู่เพื่อกำหนดใหม่setFrame:ในNSButtonคลาสเพื่อให้การแลกเปลี่ยนการนำไปใช้งานไม่ส่งผลกระทบต่อมุมมองทั้งหมด NSViewการดำเนินงานที่มีอยู่คนหนึ่งที่กำหนดไว้ใน สิ่งเดียวกันจะเกิดขึ้นเมื่อเปิดเครื่องNSControl(ใช้งานอีกครั้งNSView)

เมื่อคุณเรียกsetFrame:ปุ่มมันจึงจะเรียกวิธี swizzled ของคุณแล้วกระโดดตรงไปที่setFrame:วิธีการที่กำหนดไว้ NSViewแต่เดิม การใช้งานNSControlและNSViewswizzled จะไม่ถูกเรียก

แต่ถ้าสั่งคือ:

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

เนื่องจากการดูแบบ swizzling เกิดขึ้นก่อนการควบคุมแบบ swizzling จะสามารถดึงวิธีการที่เหมาะสม ในทำนองเดียวกันตั้งแต่ swizzling ควบคุมคือก่อน swizzling ปุ่มปุ่มจะดึงการดำเนิน swizzled setFrame:การควบคุมของ นี่เป็นความสับสนเล็กน้อย แต่นี่เป็นลำดับที่ถูกต้อง เราจะมั่นใจในสิ่งนี้ได้อย่างไร

อีกครั้งเพียงใช้loadเพื่อทำสิ่งต่างๆ หากคุณ Swizzle เข้าloadและทำการเปลี่ยนแปลงเฉพาะคลาสที่กำลังโหลดคุณจะปลอดภัย loadวิธีการค้ำประกันว่าวิธีการโหลดชั้นซุปเปอร์จะถูกเรียกว่าก่อนที่จะ subclasses ใด ๆ เราจะได้คำสั่งที่ถูกต้องแน่นอน!

ยากที่จะเข้าใจ (ดูซ้ำ)

เมื่อมองไปที่วิธีการแบบ swizzled ที่กำหนดไว้ตามเนื้อผ้าฉันคิดว่ามันยากที่จะบอกว่าเกิดอะไรขึ้น แต่เมื่อมองไปที่วิธีอื่นที่เราทำไว้แล้วเราก็เข้าใจง่าย คนนี้ถูกแก้ไขแล้ว!

การดีบักยาก

หนึ่งในความสับสนระหว่างการดีบั๊กคือการเห็น backtrace แปลก ๆ ที่ชื่อ swizzled นั้นปะปนกันและทุกสิ่งทุกอย่างก็สับสนไปในหัวของคุณ อีกครั้งการดำเนินการทางเลือกที่อยู่นี้ คุณจะเห็นฟังก์ชั่นตั้งชื่ออย่างชัดเจนใน backtraces ถึงกระนั้นการดีบั๊กอาจทำได้ยากเพราะมันยากที่จะจำได้ว่าอะไรที่ส่งผลกระทบกับการมั่ว จัดทำเอกสารรหัสของคุณได้ดี (แม้ว่าคุณคิดว่าคุณเป็นผู้เดียวเท่านั้นที่จะเห็นมัน) ปฏิบัติตามแนวทางปฏิบัติที่ดีและคุณจะไม่เป็นไร การดีบักไม่ยากกว่ารหัสแบบมัลติเธรด

ข้อสรุป

วิธีการที่รวดเร็วจะปลอดภัยหากใช้อย่างถูกต้อง วัดความปลอดภัยง่ายๆที่คุณสามารถใช้เพื่อ swizzle loadเฉพาะใน เช่นเดียวกับหลาย ๆ อย่างในการเขียนโปรแกรมอาจเป็นอันตราย แต่การเข้าใจผลที่ตามมาจะช่วยให้คุณใช้งานได้อย่างถูกต้อง


1โดยใช้วิธีการ swizzling ที่กำหนดไว้ข้างต้นคุณสามารถทำให้สิ่งต่างๆปลอดภัยได้ถ้าคุณใช้ trampolines คุณจะต้องมีสองแทรมโพลีน ที่จุดเริ่มต้นของวิธีการคุณจะต้องกำหนดตัวชี้ฟังก์ชั่นstoreให้กับฟังก์ชั่นที่หมุนจนกว่าที่อยู่ที่storeชี้ไปจะเปลี่ยน สิ่งนี้จะหลีกเลี่ยงสภาวะการแย่งชิงที่เรียกใช้วิธีการแบบ swizzled ก่อนที่คุณจะสามารถตั้งค่าstoreตัวชี้ฟังก์ชันได้ จากนั้นคุณจะต้องใช้แทรมโพลีนในกรณีที่การใช้งานไม่ได้กำหนดไว้ในชั้นเรียนและมีการค้นหาแทรมโพลีนและเรียกใช้วิธีซูเปอร์คลาสอย่างถูกต้อง การกำหนดวิธีการเพื่อให้สามารถค้นหาการใช้งานขั้นสูงแบบไดนามิกได้จะทำให้มั่นใจได้ว่าลำดับการโทรแบบ swizzling ไม่สำคัญ


24
คำตอบที่ให้ข้อมูลและเสียงที่น่าอัศจรรย์ใจ การอภิปรายน่าสนใจจริงๆ ขอบคุณที่สละเวลาเขียนสิ่งนี้
Ricardo Sanchez-Saez

คุณช่วยชี้ให้เห็นได้หรือไม่ว่าประสบการณ์ของคุณเป็นที่ยอมรับในร้านค้าแอป ฉันกำลังนำคุณไปยังstackoverflow.com/questions/8834294เช่นกัน
ดิกกีซิงห์

3
สามารถใช้ Swizzling ได้ที่ App store มีแอพและเฟรมเวิร์กมากมายให้เลือก ทุกอย่างที่กล่าวมาข้างต้นยังคงอยู่และคุณไม่สามารถเปลี่ยนวิธีการแบบส่วนตัว (จริง ๆ แล้วคุณสามารถทำได้ แต่คุณจะเสี่ยงต่อการถูกปฏิเสธและอันตรายจากสิ่งนี้คือหัวข้ออื่น ๆ )
wbyoung

คำตอบที่น่าอัศจรรย์ แต่ทำไม swizzling ใน class_swizzleMethodAndStore () แทนโดยตรงใน + swizzle: with: store:? ทำไมฟังก์ชั่นเพิ่มเติมนี้?
Frizlab

2
@Frizlab เป็นคำถามที่ดี! มันเป็นสิ่งที่มีสไตล์จริงๆ หากคุณกำลังเขียนโค้ดจำนวนมากที่ทำงานโดยตรงกับ Objective-C runtime API (ใน C) ก็เป็นเรื่องดีที่จะเรียกมันว่าสไตล์ C เพื่อความสอดคล้อง เหตุผลเดียวที่ฉันคิดได้นอกเหนือจากนี้คือถ้าคุณเขียนอะไรบางอย่างใน C บริสุทธิ์แล้วมันก็ยังเรียกได้มากขึ้น ไม่มีเหตุผลที่คุณไม่สามารถทำได้ทั้งหมดในวิธีการ Objective-C
wbyoung

12

ครั้งแรกฉันจะกำหนดสิ่งที่ฉันหมายถึงโดยวิธีการแบบ swizzling:

  • โอนสายการโทรทั้งหมดที่ส่งไปยังวิธีการเดิม (เรียกว่า A) ไปยังวิธีการใหม่ (เรียกว่า B)
  • เราเป็นเจ้าของวิธี B
  • เราไม่มีวิธีก
  • วิธี B ทำงานบางอย่างแล้วเรียกวิธี A

วิธีการที่ว่องไวเป็นเรื่องทั่วไปมากกว่านี้ แต่นี่เป็นกรณีที่ฉันสนใจ

อันตราย:

  • การเปลี่ยนแปลงในระดับเดิม เราไม่ได้เป็นเจ้าของชั้นเรียนที่เรากำลังว่องไว หากการเรียนเปลี่ยน swizzle ของเราอาจหยุดทำงาน

  • บำรุงรักษายาก คุณไม่เพียงแค่ต้องเขียนและรักษาวิธีการแบบ swizzled คุณต้องเขียนและรักษารหัสที่ preforms swizzle

  • ยากที่จะแก้ปัญหา มันยากที่จะติดตามกระแสของ swizzle บางคนอาจไม่ได้ตระหนักถึง swizzle ที่ preformed หากมีข้อบกพร่องที่แนะนำจาก swizzle (อาจเป็นเพราะการเปลี่ยนแปลงในชั้นเรียนดั้งเดิม) พวกเขาจะยากที่จะแก้ไข

โดยสรุปคุณควรทำการ swizzling ให้น้อยที่สุดและพิจารณาว่าการเปลี่ยนแปลงในชั้นเรียนดั้งเดิมอาจส่งผลกระทบต่อ swizzle ของคุณอย่างไร นอกจากนี้คุณควรแสดงความคิดเห็นและบันทึกสิ่งที่คุณทำอย่างชัดเจน (หรือหลีกเลี่ยงมันทั้งหมด)


@ ทุกอย่างฉันได้รวมคำตอบของคุณเป็นรูปแบบที่ดี รู้สึกอิสระที่จะแก้ไข / เพิ่มมัน ขอบคุณสำหรับข้อมูลของคุณ
Robert

ฉันเป็นแฟนของจิตวิทยาของคุณ "ด้วยพลังการบรรเลงอันยิ่งใหญ่
Jacksonkr

7

ไม่ใช่ตัวของตัวเองที่อันตรายจริงๆ ปัญหาก็คือตามที่คุณพูดมักจะใช้เพื่อปรับเปลี่ยนพฤติกรรมของคลาสเฟรมเวิร์ก สมมติว่าคุณรู้อะไรบางอย่างเกี่ยวกับการทำงานของชั้นเรียนส่วนตัวว่า "อันตราย" แม้ว่าการแก้ไขของคุณจะทำงานวันนี้มีโอกาสที่ Apple จะเปลี่ยนคลาสในอนาคตและทำให้การแก้ไขของคุณหยุดชะงัก นอกจากนี้หากแอพต่าง ๆ ทำเช่นนั้นมันทำให้แอปเปิ้ลเปลี่ยนเฟรมได้ยากขึ้นโดยไม่ทำลายซอฟต์แวร์ที่มีอยู่มากมาย


ฉันจะบอกว่า Devs นั้นควรเก็บเกี่ยวสิ่งที่พวกเขาหว่าน - พวกเขาเชื่อมโยงรหัสของพวกเขากับการใช้งานเฉพาะอย่างแน่นหนา ดังนั้นจึงเป็นความผิดของพวกเขาหากการดำเนินการนั้นเปลี่ยนแปลงไปในทางที่ลึกซึ้งซึ่งไม่ควรทำให้รหัสของพวกเขาเสียถ้าไม่ใช่เพื่อการตัดสินใจที่ชัดเจนในการจับคู่รหัสของพวกเขาอย่างแน่นหนาเหมือนที่พวกเขาทำ
Arafangion

แน่นอน แต่บอกว่าแอพยอดนิยมจะหยุดพักภายใต้ iOS เวอร์ชันถัดไป มันง่ายที่จะบอกว่ามันเป็นความผิดของนักพัฒนาและพวกเขาควรรู้ดีกว่า แต่มันทำให้ Apple ดูไม่ดีในสายตาของผู้ใช้ ฉันไม่เห็นอันตรายใด ๆ ในวิธีการของคุณเองแบบ swizzling หรือแม้แต่คลาสถ้าคุณต้องการทำนอกเหนือจากจุดของคุณที่จะทำให้โค้ดสับสน มันเริ่มมีความเสี่ยงเพิ่มขึ้นเล็กน้อยเมื่อคุณ swizzle code ที่ถูกควบคุมโดยบุคคลอื่น - และนั่นคือสถานการณ์ที่ swizzling ดูเหมือนจะดึงดูดมากที่สุด
Caleb

Apple ไม่ได้เป็นผู้ให้บริการเพียงคลาสพื้นฐานเท่านั้นที่สามารถแก้ไขได้ผ่าน siwzzling คำตอบของคุณควรได้รับการแก้ไขให้ครอบคลุมทุกคน
AR

@AR บางที แต่คำถามถูกแท็ก iOS และ iPhone ดังนั้นเราควรพิจารณาในบริบทนั้น เนื่องจากแอป iOS ต้องเชื่อมโยงไลบรารีและกรอบงานใด ๆ นอกเหนือจากที่ได้รับจาก iOS แบบคงที่ในความเป็นจริงแล้วแอปเปิ้ลเป็นนิติบุคคลเดียวที่สามารถเปลี่ยนการใช้งานคลาสเฟรมเวิร์กโดยไม่ต้องมีส่วนร่วมจากนักพัฒนาแอป แต่ฉันเห็นด้วยกับประเด็นที่ว่าปัญหาที่คล้ายกันส่งผลกระทบต่อแพลตฟอร์มอื่น ๆ และฉันก็บอกได้ว่าคำแนะนำสามารถสรุปได้โดยทั่วไปมากกว่าการทำเสียงดัง โดยทั่วไปแล้วคำแนะนำคือ: ให้มือของคุณเอง หลีกเลี่ยงการดัดแปลงส่วนประกอบที่คนอื่นดูแล
Caleb

วิธีการที่ว่องไวอาจเป็นอันตรายได้ แต่เป็นกรอบออกมาพวกเขาไม่ได้ละเว้นก่อนหน้าอย่างสมบูรณ์ คุณยังต้องอัปเดตเฟรมเวิร์กพื้นฐานที่แอพของคุณคาดหวัง และการอัปเดตนั้นจะทำให้แอปของคุณพัง มันไม่น่าจะเป็นปัญหาใหญ่เมื่อมีการอัพเดท iOS รูปแบบการใช้งานของฉันคือการแทนที่ reuseIdentifier เริ่มต้น เนื่องจากฉันไม่สามารถเข้าถึงตัวแปร _reuseIdentifier และไม่ต้องการแทนที่เว้นแต่ว่าจะไม่ได้ให้โซลูชันเดียวของฉันคือการ subclass ทุกคนและให้ตัวบ่งชี้ที่นำมาใช้ใหม่หรือ swizzle และแทนที่ถ้าไม่มี ประเภทการใช้งานเป็นกุญแจสำคัญ ...
Lazy Coder

5

ใช้อย่างรอบคอบและชาญฉลาดมันสามารถนำไปสู่รหัสที่สง่างาม แต่โดยปกติแล้วมันจะนำไปสู่การสร้างรหัสที่สับสน

ฉันบอกว่ามันควรถูกแบนเว้นแต่คุณจะรู้ว่ามันนำเสนอโอกาสที่หรูหรามากสำหรับงานออกแบบโดยเฉพาะ แต่คุณต้องรู้อย่างชัดเจนว่าทำไมมันถึงใช้งานได้ดีกับสถานการณ์ .

ยกตัวอย่างเช่นการประยุกต์ใช้วิธีการ swizzling ที่ดีอย่างหนึ่งคือ isa swizzling ซึ่งเป็นวิธีที่ ObjC ใช้ในการสังเกตการณ์ค่าคีย์

ตัวอย่างที่ไม่ดีอาจอาศัยวิธีการที่ใช้เป็นวิธีการขยายชั้นเรียนของคุณซึ่งนำไปสู่การมีเพศสัมพันธ์ที่สูงมาก


1
-1 เอ่ยถึง KVO ในบริบทของวิธี swizzling isa swizzling เป็นเพียงอย่างอื่น
Nikolai Ruhe

@NikolaiRuhe: โปรดส่งข้อมูลอ้างอิงเพื่อที่ฉันจะได้รู้แจ้ง
Arafangion

นี่คือการอ้างอิงถึงรายละเอียดการดำเนินงานของ KVO วิธีการอธิบายการเปลี่ยนแปลงใน CocoaDev
นิโคไล Ruhe

5

แม้ว่าฉันจะใช้เทคนิคนี้ฉันอยากจะชี้ให้เห็นว่า:

  • มันทำให้รหัสของคุณสับสนเพราะอาจทำให้เกิดผลข้างเคียงที่ไม่ได้จัดทำเป็นเอกสารไว้ได้ เมื่อมีคนอ่านรหัสเขา / เธออาจจะไม่ทราบถึงพฤติกรรมที่มีผลข้างเคียงที่จำเป็นเว้นแต่เขา / เธอจำได้ว่าต้องค้นหาฐานของรหัสเพื่อดูว่ามันได้รับการ swizzled ฉันไม่แน่ใจว่าจะบรรเทาปัญหานี้ได้อย่างไรเนื่องจากไม่สามารถบันทึกทุก ๆ ที่ที่โค้ดขึ้นอยู่กับผลข้างเคียงที่เกิดจากพฤติกรรมที่คลาดเคลื่อน
  • มันสามารถทำให้โค้ดของคุณสามารถนำกลับมาใช้ใหม่ได้น้อยลงเพราะคนที่พบเซ็กเมนต์ของโค้ดซึ่งขึ้นอยู่กับพฤติกรรมแบบ swizzled ที่พวกเขาต้องการใช้ที่อื่นไม่สามารถตัดและวางลงในฐานรหัสอื่นโดยไม่ต้องค้นหาและคัดลอก

4

ฉันรู้สึกว่าอันตรายที่ใหญ่ที่สุดคือการสร้างผลข้างเคียงที่ไม่ต้องการจำนวนมากโดยไม่ได้ตั้งใจ ผลข้างเคียงเหล่านี้อาจแสดงว่าตัวเองเป็น 'แมลง' ซึ่งจะนำคุณไปสู่เส้นทางที่ผิดเพื่อค้นหาวิธีแก้ปัญหา จากประสบการณ์ของฉันอันตรายเป็นสิ่งที่อ่านไม่ออกสับสนและทำลายรหัส ชนิดของสิ่งที่ชอบเมื่อมีคนใช้ฟังก์ชันพอยน์เตอร์ใน C ++ มากเกินไป


ตกลงปัญหาคือยากที่จะดีบัก ปัญหาเกิดขึ้นเนื่องจากผู้ตรวจสอบไม่ได้ตระหนักถึง / ลืมว่ารหัสนั้นมีการเปลี่ยนแปลงอย่างรวดเร็วและกำลังมองหาที่ผิดเมื่อทำการดีบัก
Robert

3
ใช่สวยมาก นอกจากนี้เมื่อใช้มากเกินไปคุณสามารถเข้าสู่เครือข่ายที่ไม่มีที่สิ้นสุดหรือเว็บของ swizzles ลองนึกภาพสิ่งที่อาจเกิดขึ้นเมื่อคุณเปลี่ยนเพียงหนึ่งในอนาคต ฉันเปรียบประสบการณ์การซ้อนถ้วยชาหรือเล่น 'code jenga'
AR

4

คุณอาจท้ายด้วยรหัสที่ดูแปลก ๆ เช่น

- (void)addSubview:(UIView *)view atIndex:(NSInteger)index {
    //this looks like an infinite loop but we're swizzling so default will get called
    [self addSubview:view atIndex:index];

จากรหัสการผลิตจริงที่เกี่ยวข้องกับมายากล UI บางอย่าง


3

วิธีการที่รวดเร็วจะมีประโยชน์มากในการทดสอบหน่วย

อนุญาตให้คุณเขียนวัตถุจำลองและใช้วัตถุจำลองนั้นแทนวัตถุจริง รหัสของคุณยังคงสะอาดและการทดสอบหน่วยของคุณมีพฤติกรรมที่คาดเดาได้ สมมติว่าคุณต้องการทดสอบรหัสที่ใช้ CLLocationManager การทดสอบหน่วยของคุณสามารถ Swizzle startUpdatingLocation เพื่อให้ฟีดสถานที่ตั้งที่กำหนดไว้ล่วงหน้าแก่ตัวแทนของคุณและรหัสของคุณจะไม่ต้องเปลี่ยน


isa-swizzling จะเป็นทางเลือกที่ดีกว่า
vikingosegundo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.