ฉันสงสัยว่าจะระงับคำเตือนได้อย่างไร:
หมวดหมู่กำลังใช้วิธีการซึ่งจะถูกนำไปใช้โดยคลาสหลักด้วย
ฉันมีสิ่งนี้สำหรับหมวดหมู่รหัสเฉพาะ:
+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
return [self aCustomFontOfSize:fontSize];
}
ฉันสงสัยว่าจะระงับคำเตือนได้อย่างไร:
หมวดหมู่กำลังใช้วิธีการซึ่งจะถูกนำไปใช้โดยคลาสหลักด้วย
ฉันมีสิ่งนี้สำหรับหมวดหมู่รหัสเฉพาะ:
+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
return [self aCustomFontOfSize:fontSize];
}
คำตอบ:
หมวดหมู่ช่วยให้คุณสามารถเพิ่มวิธีการใหม่ให้กับคลาสที่มีอยู่ หากคุณต้องการนำเมธอดที่มีอยู่แล้วไปใช้อีกครั้งโดยทั่วไปคุณจะสร้างคลาสย่อยแทนหมวดหมู่
เอกสารของ Apple: การปรับแต่งคลาสที่มีอยู่
หากชื่อของเมธอดที่ประกาศในหมวดหมู่นั้นเหมือนกับเมธอดในคลาสดั้งเดิมหรือเมธอดในหมวดหมู่อื่นในคลาสเดียวกัน (หรือแม้แต่ซูเปอร์คลาส) พฤติกรรมจะไม่ได้กำหนดว่าจะใช้เมธอดใดที่ รันไทม์
สองวิธีที่มีลายเซ็นเดียวกันแน่นอนในคลาสเดียวกันจะนำไปสู่พฤติกรรมที่คาดเดาไม่ได้เนื่องจากผู้เรียกแต่ละคนไม่สามารถระบุได้ว่าต้องการใช้งานแบบใด
ดังนั้นคุณควรใช้ประเภทและระบุชื่อเมธอดที่ใหม่และไม่ซ้ำกันสำหรับคลาสหรือคลาสย่อยถ้าคุณต้องการเปลี่ยนพฤติกรรมของเมธอดที่มีอยู่ในคลาส
แม้ว่าทุกอย่างจะถูกต้อง แต่ก็ไม่ได้ตอบคำถามของคุณเกี่ยวกับวิธีระงับคำเตือน
หากคุณต้องมีรหัสนี้ด้วยเหตุผลบางประการ (ในกรณีของฉันฉันมี HockeyKit ในโปรเจ็กต์ของฉันและพวกเขาจะแทนที่เมธอดในหมวด UIImage [แก้ไข: นี่ไม่ใช่กรณีอีกต่อไป]) และคุณต้องได้รับโครงการของคุณเพื่อรวบรวม คุณสามารถใช้#pragma
คำสั่งเพื่อปิดกั้นคำเตือนดังนี้:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// do your override
#pragma clang diagnostic pop
ฉันพบข้อมูลที่นี่: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html
ทางเลือกอื่นที่ดีกว่า (ดูคำตอบของ bneely ว่าเหตุใดคำเตือนนี้จึงช่วยคุณให้รอดพ้นจากภัยพิบัติ) คือการใช้วิธีการ swizzling ด้วยการใช้วิธีการ swizzling คุณสามารถแทนที่วิธีการที่มีอยู่จากหมวดหมู่ได้โดยไม่ต้องมีความแน่นอนว่าใคร "ชนะ" และในขณะที่ยังคงรักษาความสามารถในการเรียกใช้วิธีการเดิม เคล็ดลับคือการตั้งชื่อเมธอดการลบล้างที่แตกต่างกันจากนั้นสลับโดยใช้ฟังก์ชันรันไทม์
#import <objc/runtime.h>
#import <objc/message.h>
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
จากนั้นกำหนดการใช้งานแบบกำหนดเองของคุณ:
+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}
แทนที่การใช้งานเริ่มต้นกับคุณ:
MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));
ลองใช้รหัสนี้:
+(void)load{
EXCHANGE_METHOD(Method1, Method1Impl);
}
UPDATE2: เพิ่มมาโครนี้
#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]
@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end
#import <objc/runtime.h>
@implementation NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
Class class = [self class];
Method origMethod = class_getInstanceMethod(class, origSel);
if (!origMethod){
origMethod = class_getClassMethod(class, origSel);
}
if (!origMethod)
@throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
Method newMethod = class_getInstanceMethod(class, newSel);
if (!newMethod){
newMethod = class_getClassMethod(class, newSel);
}
if (!newMethod)
@throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
if (origMethod==newMethod)
@throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
method_exchangeImplementations(origMethod, newMethod);
}
@end
คุณสามารถใช้วิธีการ swizzling เพื่อระงับคำเตือนของคอมไพเลอร์นี้ นี่คือวิธีที่ฉันใช้วิธีการ swizzling สำหรับการวาดระยะขอบใน UITextField เมื่อเราใช้พื้นหลังที่กำหนดเองกับ UITextBorderStyleNone:
#import <UIKit/UIKit.h>
@interface UITextField (UITextFieldCatagory)
+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end
#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>
@implementation UITextField (UITextFieldCatagory)
+(void)load
{
Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));
Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));
method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);
}
- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
@end
คุณสมบัติ Over-riding ใช้ได้กับ Class Extension (Anonymous Category) แต่ไม่ใช่สำหรับ Category ทั่วไป
ตามเอกสารของ Apple โดยใช้ Class Extension (Anonymous Category) คุณสามารถสร้างอินเทอร์เฟซส่วนตัวให้กับคลาสสาธารณะเพื่อให้อินเทอร์เฟซส่วนตัวสามารถแทนที่คุณสมบัติที่เปิดเผยต่อสาธารณะ กล่าวคือคุณสามารถเปลี่ยนคุณสมบัติจากอ่านอย่างเดียวเป็นอ่านเขียน
กรณีการใช้งานสำหรับกรณีนี้คือเมื่อคุณเขียนไลบรารีที่ จำกัด การเข้าถึงคุณสมบัติสาธารณะในขณะที่คุณสมบัติเดียวกันต้องการการเข้าถึงการเขียนแบบอ่านทั้งหมดภายในไลบรารี
ลิงก์ Apple Docs: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
ค้นหา " ใช้ส่วนขยายของชั้นเรียนเพื่อซ่อนข้อมูลส่วนตัว "
ดังนั้นเทคนิคนี้ใช้ได้กับ Class Extension แต่ใช้ไม่ได้กับ Category
หมวดหมู่เป็นสิ่งที่ดี แต่สามารถใช้ในทางที่ผิดได้ เมื่อเขียนหมวดหมู่คุณควรเป็นหลักการอย่าใช้วิธีการออกจากระบบซ้ำ การทำเช่นนั้นอาจทำให้เกิดผลข้างเคียงที่แปลกประหลาดเนื่องจากตอนนี้คุณกำลังเขียนโค้ดใหม่ที่คลาสอื่นขึ้นอยู่ คุณสามารถทำลายคลาสที่รู้จักและจบลงด้วยการเปลี่ยนดีบักเกอร์ของคุณภายใน - ภายนอก เป็นการเขียนโปรแกรมที่ไม่ดี
หากคุณจำเป็นต้องทำคุณควรย่อยคลาสนั้น ๆ
จากนั้นข้อเสนอแนะของการหมุนวนนั่นคือ NO-NO-NO ที่ยิ่งใหญ่สำหรับฉัน
Swizzing ในขณะรันไทม์เป็น NO-NO-NO ที่สมบูรณ์
คุณต้องการให้กล้วยมีลักษณะเป็นสีส้ม แต่เฉพาะที่รันไทม์? ถ้าอยากได้ส้มก็เขียนส้ม
อย่าทำให้กล้วยมีลักษณะและทำตัวเหมือนส้ม และที่แย่กว่านั้นคืออย่าเปลี่ยนกล้วยของคุณให้เป็นสายลับที่จะทำลายกล้วยทั่วโลกอย่างเงียบ ๆ เพื่อสนับสนุนส้ม
อ๊ะ!
ฉันมีปัญหานี้เมื่อฉันใช้วิธีการมอบหมายในหมวดหมู่แทนที่จะเป็นคลาสหลัก (แม้ว่าจะไม่มีการใช้งานคลาสหลักก็ตาม) วิธีแก้ปัญหาสำหรับฉันคือการย้ายจากไฟล์ส่วนหัวของคลาสหลักไปยังไฟล์ส่วนหัวประเภทซึ่งใช้งานได้ดี
super
อย่างอื่น