移动开发俱乐部

Effective Objective-C 52方法要点笔记

2021-03-22  本文已影响0人  恩莱客

第一章:熟悉Objective-C

1. 特性:动态语言,运行时确定绑定关系
2. 减少头文件的引用;记住一个“向前声明”;避免类相互引用;使用#import
3. 多用字面量写法(现代语法),少用等价长方法
4. 多用类型变量,少用#define预处理指令,如:
xxx.h
extern nsstring *const classname_a;
xxx.m
nsstring const classname_a = @"value";
5. 用枚举表示状态、选项、状态码,配合Switch使用。
typedef NS_ENUM(NSInteger, UIButtonRole) {
    UIButtonRoleNormal,
    UIButtonRolePrimary,
    UIButtonRoleCancel,
    UIButtonRoleDestructive
} API_AVAILABLE(ios(14.0));

typedef NS_OPTIONS(NSUInteger, SDRectCorner) {
    SDRectCornerTopLeft     = 1 << 0,
    SDRectCornerTopRight    = 1 << 1,
    SDRectCornerBottomLeft  = 1 << 2,
    SDRectCornerBottomRight = 1 << 3,
    SDRectCornerAllCorners  = ~0UL // 无符号长整型0
};

第二章:对象、消息、运行期

OC编程 = 对象(基本结构单元)+ 消息传递(messaging)

6. 属性
7.在对象内部尽量直接访问实例变量
8. 对象等同性
- (BOOL)isEqual:(id)object {
    if (self == object) {//指针相等
        return YES;
    }
    if (![object isKindOfClass:[Person class]]) {//同类
        return NO;
    }
    return [self isEqualToPerson:(Person *)object];
}
- (BOOL)isEqualToPerson:(Person *)person {//类中数据相等(即属性)
    if (!person) {
        return NO;
    }
    BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];
    return haveEqualNames && haveEqualBirthdays;
}

重写hash方法

//In reality, a simple XOR over the hash values of critical properties is sufficient 99% of the time(对关键属性的hash值进行位或运算作为hash值)
- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash];
}
- (BOOL)isEqualToDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;
- (BOOL)isEqualToArray:(NSArray<ObjectType> *)otherArray
- (BOOL)isEqualToString:(NSString *)aString;
9. 以“类簇”模式隐藏实现细节

原理解释:通过一个对象(类)来存储不同类型的数据变量,其内部不能修改。

NSNumber
注:请不要尝试去创建NSString、NSArray或NSDictionary的子类。如果必须添加或修改某个方法,可以使用类别的方式。
10. 关联对象(AssociatedObject)- 把两个对象关联起来
// 关联对象:policy 内存管理策略
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
// 获取关联对象
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
// 删除关联对象
objc_removeAssociatedObjects(id _Nonnull object)
11. 消息传递
// self - receiver接收者、op - Selector选择子
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

发送消息 = 调用方法

//
id returnValue = [someObject messageName:parameter];
id returnValue = objc_msgSend(someObject, @selector(messageName:), paramater);
12. 消息转发机制

例:[obj test]; - > objc_msgSend(obj, test)(runtime方法)
runtime的执行步骤:

  1. obj(isa) - > class
  2. 在class method list中找到test
  3. 没有找到test,找superclass
  4. 找到test后,执行IMP
动态方法解析 + 消息转发

相关API:

+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");

代码实现过程:

  1. 定义Property,声明为@dynamic,没有实现方法
  2. 在方法
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

添加转发代码。

13. 方法调配技术 - 运行时,交换类方法

涉及方法:

OBJC_EXPORT IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) ;
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) );

以下是腾讯云SDK的捕获方法调用异常代码。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self changeImplementation];
    });
}
+ (void)changeImplementation {
    Class class = object_getClass((id)self);
    Method originMethod = class_getClassMethod(class, @selector(exceptionWithName:reason:userInfo:));
    Method replacedMethod = class_getClassMethod(class, @selector(qcloud_exceptionWithName:reason:userInfo:));
    method_exchangeImplementations(originMethod, replacedMethod);
}
+(NSException *)qcloud_exceptionWithName:(NSExceptionName)name reason:(NSString *)reason userInfo:(NSDictionary *)userInfo{
    NSException *exp = [self qcloud_exceptionWithName:name reason:reason userInfo:userInfo];
    [QualityDataUploader trackSDKExceptionWithException:exp];
    return exp;
}
14. 理解类对象的用意

SomeClass的子类从NSObject中继承而来,其继承体系:


SomeClass实例所属的“类继承体系”

每个类仅有一个“类对象”,每个“类对象”仅有一个与之相关的“元类”。

typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
// 类或者派生类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 特定类的
- (BOOL)isMemberOfClass:(Class)aClass;
16. 提供“全能初始化方法”
Person.h
@interface Person : NSObject
@property (nonatomic, assign) float age;
@property (nonatomic, strong) NSString *name;
// 全能初始化方法
- (id)initWithAge:(CGFloat)age
         withName:(NSString *)name;
@end

Person.m
@implementation Person
- (id)initWithAge:(CGFloat)age
         withName:(NSString *)name{
    if (self = [super init]) {
        _age = age;
        _name = name;
    }
    return self;
}
// 覆写超类的init方法
- (instancetype)init{
    return [self initWithAge:0 withName:@""];
   // @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"replace init with new function" userInfo:nil];

}
@end

17. 自定义对象的description,po调试使用,或者对象类型强转打印
- (NSString *)description{
    return [NSString stringWithFormat:@"%@ is %ld years old", _name, (long)_age];
}

断点debug打印:

(lldb) po person
Lucy is 20 years old

对象类型强转后打印:

(lldb) po person
<Person: 0x600003e79960>

(lldb) po ((Person *)person).name
Lucy

(lldb) po ((Person *)person).age
20
18. 尽量使用不可变对象

尽量创建不可变对象,对象内部确实需要修改时,才把扩展属性有readonly换成readwrite(或缺省),外部使用时,使用方法公开、修改回调。

19. function命名方式
// 返回类型 with 传参1...2...3 
+ (instancetype)stringWithString:(NSString *)string;
返回类型 with 传参1...2...3  属性赋值
+ (instancetype)stringWithCharacters:(const unichar *)characters length:(NSUInteger)length;
// “是否有”
- (BOOL)hasPrefix:(NSString *)str;
// "是否相等"
- (BOOL)isEqualToString:(NSString *)aString;
20. 区分公共方法和私有方法

公共方法名尽量不要修改,修改后外部调用会出错。
给私有方法前边加上p_,如:

- (void)p_privateMethod;

或者

#pragma mark  public
// 外部访问方法

#pragma mark  private
// 内部方法
21. “异常(NSException)”处理机制

极端情况下抛出异常,不用考虑恢复,所以异常代码简单就行。

- (QCloudSignature *)signatureForData:(id)signData {
    @throw [NSException exceptionWithName:QCloudErrorDomain reason:@"请在子类中实现该函数" userInfo:nil];
}
22. 理解NSCopying、NSMutableCopying协议
@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone;

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

@end

类遵循了NSCopying协议,其对象就支持copy操作。
具体代码实现:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    return [self doCopyWithZone:zone];
}

- (id)doCopyWithZone:(nullable NSZone *)zone {
    TVideoSegmentItem *model = [[TVideoSegmentItem allocWithZone:zone] init];
    [[[self class] getPropertyNameList:[model class]] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        id value = [self valueForKey:obj];
        if ([value isKindOfClass:[NSMutableDictionary class]] ||
            [value isKindOfClass:[NSDictionary class]]) {
            NSMutableDictionary *newObj = [[NSMutableDictionary alloc] initWithDictionary:value copyItems:YES];
            [model setValue:newObj forKey:obj];
        }
        else if ([value isKindOfClass:[NSArray class]] ||
                 [value isKindOfClass:[NSMutableArray class]]) {
            NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:value copyItems:YES];
            [model setValue:newArray forKey:obj];
        }
        else if ([value isKindOfClass:[NSMutableString class]] ||
                 [value isKindOfClass:[NSString class]]) {
            NSMutableString* newObj = [value mutableCopy];
            [model setValue:newObj forKey:obj];
        }
        else {
            [model setValue:[self valueForKey:obj] forKey:obj];
        }
    }];
    return model;
}

+ (NSArray *)getPropertyNameList:(Class)cls {
    NSMutableArray *propertyNameListArray = [NSMutableArray array];
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count);
    for (NSInteger i = 0 ; i < count; i++) {
        const char *propertyCharName = property_getName(properties[i]);//c的字符串
        NSString *propertyOCName = [NSString stringWithFormat:@"%s",propertyCharName];//转化成oc 字符串
        [propertyNameListArray addObject:propertyOCName];
    }
    NSArray *dataArray = [NSArray arrayWithArray:propertyNameListArray];
    return dataArray;
}

注:

// NSCopying
[NSMuatbleArray copy] => array
// NSMutableCopying
[NSArray mutableCopy] => mArray
深拷贝与浅拷贝对比图
浅拷贝后内容与原内容均指向相同对象,而深拷贝后的内容是原内容对象的拷贝的一份。注意其内容对象的可变性未变。
上一篇 下一篇

猜你喜欢

热点阅读