Effective Objective-C 52方法要点笔记

第一章:熟悉Objective-C

1. 特性:动态语言,运行时确定绑定关系
2. 减少头文件的引用;记住一个“向前声明”;避免类相互引用;使用#import
3. 多用字面量写法(现代语法),少用等价长方法
4. 多用类型变量,少用#define预处理指令,如:
  • 类内使用static const NSInteger kTime = 0.3
  • 类外使用,名称使用类名为前缀
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. 属性
  • 封装对象数据
  • 点语法 - 访问对象数据,编译器在编译时,生成存取方法和变量:
    Property = _str + - (NSString *)str + - (void)getStr:(NSString *)str
  • 属性特质(修饰符),控制编译器生成存取方法
7.在对象内部尽量直接访问实例变量
  • 读数据 - 直接使用实例变量
  • 写数据 - 使用属性
  • 初始化方法或者dealloc方法中直接使用实例变量来读、写数据
  • 懒加载时,使用属性来读取数据
8. 对象等同性
  • 对比:
    == 指针相等
    hash值(先判断) + (BOOL)isEqual:(id)object;
    重写isEqual方法:
- (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实例所属的“类继承体系”

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

  • 定义id对象
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;
  • 此结构体存放类的“元数据metadata”:
    isa - 对象所属类型
    super_class - 元类
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

深拷贝与浅拷贝对比图

浅拷贝后内容与原内容均指向相同对象,而深拷贝后的内容是原内容对象的拷贝的一份。注意其内容对象的可变性未变。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容