iOS ------代理 分类 拓展

代理协议

一,概念:

代理,又称委托代理(delegate),是iOS中常用的一种设计模式。顾名思义,它是把某个对象要做的事委托给别的对象去做。那么别的对象就是这个对象的代理,代替它来打理要做的事,这个对象就是别的对象的委托。

协议,是多个类共享的一个方法列表,在协议中所列出的方法没有响应的实现,由其他类来实现

委托,指给一个对象提供机会对另一个对象中的变化做出反应或者响应另一个对象的行为,其基本思想就是协同解决问题。

在iOS程序设计中,委托通过一种@protocol的方式来实现,所以又称为协议。UITableView, UITextField等都用到这种设计模式。

二,委托模式的好处

  1. 避免子类化带来的过多的子类以及子类与父类的耦合
    委托模式通过将特定功能的实现委托给另一个对象(委托对象),避免了通过创建大量子类来实现不同功能的需要。相比于继承,委托模式更加灵活,可以在运行时动态地改变委托对象,而无需修改或创建新的子类。这样可以减少类的层级结构,提高代码的可维护性和可扩展性,并降低类之间的耦合度。
  2. 通过委托传递消息机制实现分层解耦
    委托模式通过委托对象作为中介,将消息的传递和处理分离。委托对象负责接收并处理某些特定的消息或事件,而委托方只需调用委托对象的方法,将消息传递给委托对象进行处理。这种分层解耦的设计使得委托方和委托对象之间的关注点分离,提高了代码的可读性和可维护性。同时,委托模式也支持多个对象作为委托对象,实现更灵活的消息传递和处理。

三,各自的工作

委托的工作

  1. 定义协议和方法
  2. 声明委托变量
  3. 设置代理
  4. 通过委托变量调用委托方法

代理的工作

  1. 遵循协议
  2. 实现委托方法

具体实现代码参考博客:

iOS代理协议

分类

1.适用范围

  • 当你已经封装好了一个类(也可能是系统类,第三方库),不想再改动这个类,随着程序功能的增加需要在类中添加一个方法,这时我们不必修改主类,只需要给你原来的类增加一个分类。
  • 将一个大型的类拆封成不同的分类,在不同分类中实现类别声明的方法,这样可以将一个类的实现写到多个.m文件中,方便管理和开发。

2.语法格式

.h

#import "ECOPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface ECOPerson (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end

NS_ASSUME_NONNULL_END

.m

#import "ECOPerson+Work.h"

@implementation ECOPerson (Work)
- (void)performDaysWork {
    
}
- (void)takeVacationFromWork {
    
}
@end

文件名:主类名+分类名

调用方法时,只需要向主类引用发送消息即可

3.注意事项

  • 分类中的方法的优先级比原来的类中的方法高,如果在分类中重写了原来类的方法,那么分类中的方法会覆盖原来类中的方法。
  • 分类中只能声明方法,不能添加属性变量
  • 通常,分类.h文件,但也可以定义在.m文件,此时分类的方法就变成了私有方法。

4,为什么分类中只能声明方法,不能添加属性变量

先看一段代码

@interface Person (Friendship)
@property (nonatomic, strong) NSString *friends;

- (void)addFriend:(Person*)person;
- (void)removeFriend:(Person*)person;
- (BOOL)isFriendWith:(Person*)person;

@end

编译会报错
在这里插入图片描述
意思是说此分类无法合成与friends属性相关的实例变量.

我们先看一看分类的本质

分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。具体原因看源码组成:

在这里插入图片描述

在分类的结构体指针中,没有属性列表,只有方法列表。所以原则上它只能添加方法,不能添加属性(成员变量),实例上可以通过其他方式添加属性

问题:
为什么在分类中声明属性时,运行不会出错?既然分类不让添加属性,为什么写@property仍然可以编译通过?

我们知道用@property声明属性时,编译器会自动帮我们生成_成员变量和setter和getter方法,但在分类的结构体指针中根本没有成员列表,所以在分类中用@property声明属性,即无法生成_成员变量和setter和getter方法只有getter和setter和声明。编译和运行都可以通过。如果调用了_成员变量和setter/getter方法,报错就会发生。

5.虽然不能再类别中定义成员属性,但是也可以让它支持添加属性和成员变量

OC是动态的,我们可以手动添加。
一种常见的方法就是通过runtime.h 中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。通过这种方法来模拟生成对象。

在这里插入图片描述

在这里插入图片描述

在其他地方使用
在这里插入图片描述

注意,以上代码只是手动实现了setter/getter方法,但调用_成员变量依然会报错

6.关联对象

1.主要函数:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
//相当于 setValue:forKey 进行关联value对象
//objc_AssociationPolicy  属性 是设定该value在object内的属性,即 assgin, (retain,nonatomic)...等
id objc_getAssociatedObject(id object, const void *key); 
//用来读取对象
void objc_removeAssociatedObjects(id object);
//函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。
2.相关参数
  • 参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
  • 参数二:void * == id key : key值,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
  • 参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
  • 参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。
OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。
OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。
OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)

我们在代码中使用了 OBJC_ASSOCIATION_RETAIN_NONATOMIC 就相当于使用了 nonatomic 和 strong 修饰符。

4.关联对象的原理
实现关联对象技术的核心对象
  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

源码实现

objc_setAssociatedObject函数的源码实现
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

再看_object_set_associative_reference方法

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // 这段代码在以前当 object 和 key 为 nil 时是有效的。某些代码可能依赖于此以避免崩溃。显式检查和处理。
    // rdar://problem/44094390
    if (!object && !value) return;
    // 如果 object 和 value 均为 nil,直接返回,避免崩溃

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    // 如果对象所属的类禁止使用关联对象,则触发致命错误

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};
    // 创建 DisguisedPtr 包装的对象指针和 ObjcAssociation 对象,用于关联对象的存储

    // 在锁之外对新值进行 retain 操作
    association.acquireValue();
    
    bool isFirstAssociation = false;
    {
        AssociationsManager manager;//
        AssociationsHashMap &associations(manager.get());//
        // 获取关联对象的管理器和关联对象的哈希映射

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            // 如果 value 不为空,则将对象和一个新的关联对象映射插入哈希映射中,并标记为第一个关联对象

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
            // 建立或替换关联对象,将关联对象存储在哈希映射的指定键下
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
            // 如果 value 为空,则从哈希映射中删除指定键对应的关联对象
        }
    }

    // 在锁之外调用 setHasAssociatedObjects 方法,如果对象实现了 _noteAssociatedObjects 方法,则会触发调用,并且可能触发 +initialize 方法,从而执行其他操作,包括设置更多的关联对象
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // 在锁之外释放旧值
    association.releaseHeldValue();
}

运行思路可以总结如下:

  1. 首先,检查传入的对象和值是否为 nil,如果都是 nil,则直接返回,避免崩溃。
  2. 检查对象所属的类是否禁止使用关联对象,如果是,则触发致命错误。
  3. 创建一个 DisguisedPtr 对象,用于包装对象指针,以及一个 ObjcAssociation 对象,用于存储关联对象的策略和值。
  4. 在锁之外对新值进行 retain 操作,确保新值在函数执行期间有效。
  5. 获取关联对象的管理器和关联对象的哈希映射。
  6. 如果新值不为空,将对象和一个新的关联对象映射插入哈希映射中,并标记为第一个关联对象。
  7. 建立或替换关联对象,将关联对象存储在哈希映射的指定键下。
  8. 如果新值为空,从哈希映射中删除指定键对应的关联对象。
  9. 在锁之外调用对象的 setHasAssociatedObjects 方法,如果对象实现了 _noteAssociatedObjects 方法,则会触发调用,并且可能触发 +initialize 方法,从而执行其他操作,包括设置更多的关联对象。
  10. 在锁之外释放旧值,确保旧值在函数执行期间正确释放。
_object_get_associative_reference函数的源码实现
id _object_get_associative_reference(id object, const void *key) {
    ObjcAssociation association{}; // 创建一个空的 ObjcAssociation 对象,用于存储关联对象的策略和值

    {
        AssociationsManager manager; // 创建 AssociationsManager 对象,用于管理关联对象的哈希表
        AssociationsHashMap &associations(manager.get()); // 获取关联对象的哈希表
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 在哈希表中查找指定对象的关联对象集合
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second; // 获取指定对象的关联对象集合
            ObjectAssociationMap::iterator j = refs.find(key); // 在关联对象集合中查找指定键的关联对象
            if (j != refs.end()) {
                association = j->second; // 将查找到的关联对象赋值给 association
                association.retainReturnedValue(); // 对返回的关联对象进行保留操作,以便在返回后保持有效
            }
        }
    }

    return association.autoreleaseReturnedValue(); // 对返回的关联对象进行自动释放操作,并返回该关联对象
}

这段代码的运行思路如下:

  1. 首先,创建一个空的 ObjcAssociation 对象 association,用于存储关联对象的策略和值。

  2. 创建 AssociationsManager 对象 manager,用于管理关联对象的哈希表。

  3. 通过 manager.get() 获取关联对象的哈希表 associations

  4. associations 哈希表中查找指定对象 (objc_object *)object 的关联对象集合,使用 associations.find() 方法进行查找,返回一个迭代器 i

  5. 如果找到了指定对象的关联对象集合,进入内部的 if 代码块。

  6. 在关联对象集合 refs 中查找指定键 key 的关联对象,使用 refs.find() 方法进行查找,返回一个迭代器 j

  7. 如果找到了指定键的关联对象,进入内部的第二个 if 代码块。

  8. 将找到的关联对象赋值给 association

  9. 对返回的关联对象进行保留操作,使用 association.retainReturnedValue() 方法。

  10. 返回自动释放后的关联对象,使用 association.autoreleaseReturnedValue() 方法。

总体而言,这段代码的目的是在给定的对象中查找指定键的关联对象,并返回关联对象的值。它通过迭代访问嵌套的哈希表结构来实现这一目标,并使用 ObjcAssociation 类来封装和管理关联对象的策略和值

_object_remove_associations函数的源码实现
void _object_remove_associations(id object, bool deallocating) {
    ObjectAssociationMap refs{}; // 创建一个空的 ObjectAssociationMap 对象,用于存储关联对象的集合

    {
        AssociationsManager manager; // 创建 AssociationsManager 对象,用于管理关联对象的哈希表
        AssociationsHashMap &associations(manager.get()); // 获取关联对象的哈希表
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 在哈希表中查找指定对象的关联对象集合
        if (i != associations.end()) {
            refs.swap(i->second); // 交换指定对象的关联对象集合和空的 refs,将关联对象集合存储在 refs 中

            // 如果不是正在释放对象,则保留 SYSTEM_OBJECT 类型的关联对象
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref : refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref); // 将 SYSTEM_OBJECT 类型的关联对象重新插入到关联对象集合中
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i); // 如果没有重新插入 SYSTEM_OBJECT 类型的关联对象,则从哈希表中删除关联对象集合
        }
    }

    SmallVector<ObjcAssociation *, 4> laterRefs; // 创建一个 SmallVector 对象,用于存储稍后释放的关联对象

    // 释放所有关联对象(在锁的外部进行)
    for (auto &i : refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // 如果不是正在释放对象,则将 RELEASE_LATER 类型的关联对象存储在 laterRefs 中
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue(); // 释放普通类型的关联对象的值
        }
    }
    for (auto *later : laterRefs) {
        later->releaseHeldValue(); // 释放稍后释放的关联对象的值
    }
}

这段代码的运行思路如下:

  1. 首先创建一个空的 ObjectAssociationMap 对象 refs,用于存储关联对象的集合。

  2. 创建 AssociationsManager 对象 manager,用于管理关联对象的哈希表。

  3. 通过 manager.get() 获取关联对象的哈希表 associations

  4. associations 哈希表中查找指定对象 (objc_object *)object 的关联对象集合,使用 associations.find() 方法进行查找,返回一个迭代器 i

  5. 如果找到了指定对象的关联对象集合,进入内部的 if 代码块。

  6. 将指定对象的关联对象集合和空的 refs 进行交换,将关联对象集合存储在 refs 中。

  7. 如果不是正在释放对象(deallocating 为假),则遍历 refs 中的关联对象。

  8. 对于具有 OBJC_ASSOCIATION_SYSTEM_OBJECT 策略的关联对象,重新插入到关联对象集合中,以保留这些关联对象。

  9. 如果没有重新插入 OBJC_ASSOCIATION_SYSTEM_OBJECT 类型的关联对象,则从哈希表中删除关联对象集合。

  10. 创建一个 SmallVector 对象 laterRefs,用于存储稍后需要释放的关联对象。

  11. 遍历 refs 中的关联对象。

  12. 对于具有 OBJC_ASSOCIATION_SYSTEM_OBJECT 策略的关联对象,如果正在释放对象(deallocating 为真),将其存储在 laterRefs 中,稍后释放。

  13. 对于普通类型的关联对象,调用 releaseHeldValue() 方法释放其值。

  14. 遍历 laterRefs 中的稍后释放的关联对象。

  15. 对于 laterRefs 中的关联对象,调用 releaseHeldValue() 方法释放其值。

这段代码的功能是移除指定对象的关联对象,并根据标志 deallocating 决定是否保留特定类型的关联对象。它通过使用哈希表和向量来存储和管理关联对象,并根据不同的条件执行相应的操作,最终释放关联对象的值。

下面具体看一看实现关联对象技术的四个核心对象

ASSociatonsManager
class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // 使用 DisguisedPtr<objc_object> 作为键,ObjectAssociationMap 作为值的哈希映射类型别名

    static Storage _mapStorage;
    // 静态成员变量,用于存储对象的关联对象的哈希映射

public:
    AssociationsManager() { AssociationsManagerLock.lock(); }
    // 构造函数,在创建 AssociationsManager 实例时加锁

    ~AssociationsManager() { AssociationsManagerLock.unlock(); }
    // 析构函数,在销毁 AssociationsManager 实例时解锁

    AssociationsHashMap& get() {
        return _mapStorage.get();
    }
    // 返回对象的关联对象的哈希映射

    static void init() {
        _mapStorage.init();
    }
    // 静态方法,用于初始化关联对象的哈希映射
};

AssociationsManager 类具体实现了以下功能:

  1. 它使用静态成员变量 _mapStorage 存储对象的关联对象的哈希映射。
  2. 通过构造函数和析构函数的加锁和解锁操作,确保在创建和销毁 AssociationsManager 实例时对关联对象的访问是线程安全的。
  3. AssociationsManager 提供了 get() 方法,用于获取对象的关联对象的哈希映射。这样,其他代码可以通过调用 get() 方法来获取特定对象的关联对象,并进行操作。
  4. AssociationsManager 还提供了 init() 方法,用于初始化关联对象的哈希映射。这个方法在适当的时候被调用,以确保在使用关联对象之前,相关的数据结构已经被正确初始化。
ObjcAssociation
class ObjcAssociation {
    uintptr_t _policy;
    id _value;

public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    // 构造函数,使用给定的策略和值初始化 ObjcAssociation 对象

    ObjcAssociation() : _policy(0), _value(nil) {}
    // 默认构造函数,将策略和值初始化为默认值

    ObjcAssociation(const ObjcAssociation& other) = default;
    ObjcAssociation& operator=(const ObjcAssociation& other) = default;
    // 默认拷贝构造函数和赋值运算符重载

    ObjcAssociation(ObjcAssociation&& other) : ObjcAssociation() {
        swap(other);
    }
    // 移动构造函数,通过交换操作,将源对象的值转移到当前对象

    inline void swap(ObjcAssociation& other) {
        std::swap(_policy, other._policy);
        std::swap(_value, other._value);
    }
    // 交换当前对象和另一个对象的值

    inline uintptr_t policy() const { return _policy; }
    // 返回对象的关联策略

    inline id value() const { return _value; }
    // 返回对象的关联值

    inline void acquireValue() {
        if (_value) {
            switch (_policy & 0xFF) {
                case OBJC_ASSOCIATION_SETTER_RETAIN:
                    _value = objc_retain(_value);
                    break;
                case OBJC_ASSOCIATION_SETTER_COPY:
                    _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                    break;
            }
        }
    }
    // 获取关联值并根据策略进行内存管理

    inline void releaseHeldValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            objc_release(_value);
        }
    }
    // 释放持有的关联值,如果策略需要释放内存

    inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value);
        }
    }
    // 对返回的关联值进行保留操作,如果策略需要保留内存

    inline id autoreleaseReturnedValue() {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
            return objc_autorelease(_value);
        }
        return _value;
    }
    // 对返回的关联值进行自动释放操作,如果策略需要自动释放内存
};

ObjcAssociation 类的实例对象可以用于存储一个对象的关联对象的策略和值。它的成员变量 _policy 存储关联对象的策略信息_value 存储关联对象的值
该类提供了以下主要功能和方法:

  1. 构造函数:通过构造函数可以创建一个 ObjcAssociation 对象,并指定关联对象的策略和值。
  2. 值获取和设置:通过 policy() 方法可以获取关联对象的策略,通过 value() 方法可以获取关联对象的值。
  3. 内存管理:acquireValue() 方法用于根据策略进行内存管理,它会根据策略对关联对象的值进行保留操作或复制操作。releaseHeldValue() 方法用于释放持有的关联值,如果策略需要释放内存。retainReturnedValue() 方法用于对返回的关联值进行保留操作,如果策略需要保留内存。autoreleaseReturnedValue() 方法用于对返回的关联值进行自动释放操作,如果策略需要自动释放内存。
AssociationsHashMap和 ObjectAssociationMap
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

这段代码定义了两个类型别名:ObjectAssociationMapAssociationsHashMap,它们都是基于 LLVM 的 DenseMap 实现的哈希表。

  1. ObjectAssociationMap 是一个哈希表,用于存储指向 ObjcAssociation 类对象的指针和对应的关联对象。它的键类型是 const void *,即指向常量空指针的指针类型,通常用作关联对象的标识符。值类型是 ObjcAssociation,即 ObjcAssociation 类的对象,用于存储关联对象的策略和值。
  2. AssociationsHashMap 是一个嵌套的哈希表,用于存储 Objective-C 对象和其关联对象的映射关系。它的键类型是 DisguisedPtr<objc_object>,是一个包装了 objc_object 类型的指针的类模板,用于隐藏指针的真实类型。值类型是 ObjectAssociationMap,即前面提到的 ObjectAssociationMap 类型,用于存储指向 ObjcAssociation 类对象的指针和对应的关联对象。
    这些哈希表的设计是为了高效地存储和检索 Objective-C 对象的关联对象。

ObjectAssociationMap 通过将关联对象的标识符作为键,与实际的 ObjcAssociation 对象建立映射关系。而 AssociationsHashMap 则通过将 Objective-C 对象的指针作为键,与 ObjectAssociationMap 建立映射关系,从而实现了快速查找某个对象的关联对象的能力

总结一下这几个核心类的关系

在这里插入图片描述

总结
  • 关联对象并不存储在被关联对象本身的内存中,而是有一个全局统一的AssociationManager中
  • AssociationsHashMap中存储着多个实例对象的指针DisguisedPtr<objc_object>和对应的ObjectAssociationMap
  • 一个实例对象就对应一个ObjectAssociationMap,ObjectAssociationMap中存储多个此实例对象的关联对象的key和objcAssciation
  • objcAssciation中存储着关联对象的value和policy策略
使用场景
  • 添加额外的属性:当你需要为现有的类添加属性,但又无法修改该类的源代码时,可以使用关联对象。例如,你可以为系统类添加自定义属性,以满足特定的业务需求。
  • 为分类添加属性:分类无法直接添加实例变量,但通过关联对象,你可以为分类添加属性。这允许你为现有类的不同分类添加自定义属性,以扩展其功能。
  • 在类别中添加方法实现:通过关联对象,你可以在运行时动态地将方法实现关联到一个对象上。这样,你可以在不修改原始类的情况下,为特定的对象或类别添加自定义行为。
  • 实现代理模式:当一个对象需要充当另一个对象的代理时,可以使用关联对象将代理对象与被代理对象关联起来。这样,代理对象就可以接收并处理被代理对象的消息。
  • 实现观察者模式:通过关联对象,你可以动态地添加观察者对象,并将其与被观察的对象关联起来。这样,观察者对象可以接收到被观察对象的状态变化通知。
  • 扩展第三方库或框架:当你需要在第三方库或框架中添加自定义属性或行为时,可以使用关联对象。这样,你可以在不修改第三方代码的情况下,为其添加额外的功能。

拓展

1,适用范围

拓展是分类的一种特殊形式

2,语法格式

@interface 主类类名()

@end

拓展通常定义在主类的.m文件中,拓展声明的方法直接在主类的.m文件中实现

3,注意事项

  • 拓展可以声明实例变量,可以声明属性
  • 因为拓展通常定义在.m文件中,所以拓展声明的文件和属性通常是私有的

4,使用

方式一:以单独的文件定义

XYZPopViewController_ExViewController.h文件
#import"XYZPopViewController.h"

@interface XYZPopViewController()
@property(nonatomic,strong)NSString*stringOfEx;
- (void)testEx;
@end

在主类的.m文件中定义

XYZPopViewController.m文件
#import"XYZPopViewController.h"

@interface XYZPopViewController()
@property(nonatomic,strong)NSString*stringOfEx;
- (void)testEx;
@end

在主类的.m文件中实现拓展定义的方法

@implementation XYZPopViewController
- (void)testEx {
     self.stringOfEx=@"给扩展里面定义的属性字符串赋值";
     NSLog(@"定义的属性String是:%@",self.stringOfEx);
 }
 @end

拓展和分类的区别

  1. 分类是不可以声明实例变量,通常是公开的,文件名是:主类名+分类名.h
  2. 扩展是可以声明实例变量,是私有的,扩展只能在自身类中使用,而不是子类或者其他地方,
    文件名为:主类名_扩展标识.h,在主类的.m文件中#import该头文件
  3. 类拓展是在编译阶段添加到类中,而分类是在运行时添加到类中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/568482.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux部署MySQL8.0—手把手保姆级教程

&#x1f469;&#x1f3fd;‍&#x1f4bb;个人主页&#xff1a;阿木木AEcru &#x1f525; 系列专栏&#xff1a;《Docker容器化部署系列》 《Java每日面筋》 &#x1f4b9;每一次技术突破&#xff0c;都是对自我能力的挑战和超越。 目录 一、下载MySQL8.0安装包二、安装MySQ…

springboot的坑

问题&#xff1a;使用Autowired注入一个service&#xff0c;然后写了两个接口&#xff0c;第一个接口与请求时显示注入的service为空一直报错&#xff0c;但是第二个接口请求时service竟然不是空&#xff1f;在这里插入图片描述 凶手找到了&#xff0c;是private修饰。果然没仔…

HTB Runner

Runner User Nmap ──(root㉿kali)-[/home/…/machine/SeasonV/linux/Runner] └─# nmap -A runner.htb -T 4 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-22 23:07 EDT Stats: 0:00:01 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Sca…

OpenHarmony实战开发-内存快照Snapshot Profiler功能使用指导。

DevEco Studio集成的DevEco Profiler性能调优工具&#xff08;以下简称为Profiler&#xff09;&#xff0c;提供Time、Allocation、Snapshot、CPU等场景化分析任务类型。内存快照&#xff08;Snapshot&#xff09;是一种用于分析应用程序内存使用情况的工具&#xff0c;通过记录…

鸿蒙OpenHarmony【小型系统运行案例】 (基于Hi3516开发板)

运行 启动系统 在完成Hi3516DV300的烧录后&#xff0c;还需要设置BootLoader引导程序&#xff0c;才能运行OpenHarmony系统。 在Hi3516DV300任务中&#xff0c;单击Configure bootloader&#xff08;Boot OS&#xff09;进行配置即可。 说明&#xff1a; DevEco Device Tool…

【大模型系列】预训练

数据 数据预处理 预处理流程&#xff1a; 原始语料库—>质量过滤&#xff08;语种过滤、统计过滤、关键词过滤、分类器过滤&#xff09;—>敏感内容过滤&#xff08;有毒内容、隐私内容PII&#xff09;—>数据去重&#xff08;句子级别、文档级别、数据集级别&#…

【AI】【Python】pydantic库学习demo

因为工作中学习AI&#xff0c;然后包括看源码&#xff0c;以及看代码都使用到了pydantic库&#xff0c;因此下面是一些最主要的20%&#xff0c;以学会其80%的精髓。 pydantic 库是 python 中用于数据接口定义检查与设置管理的库。 pydantic 在运行时强制执行类型提示&#xff0…

内插和抽取

抽取&#xff1a; 频域表达式的关系&#xff1a; 1、角频率扩大M倍 2、移动2pi、22pi…&#xff08;n-1&#xff09; 2pi 3、相加 4、幅度变为1/M 内插&#xff1a; 加入低通滤波&#xff0c;减小混叠&#xff0c;但是由于截短&#xff0c;也会造成误差&#xff0c;但是…

【MySQL 数据宝典】【磁盘结构】- 004 redolog 重做日志

一、背景介绍 持久性要求&#xff1a; 对于已提交的事务&#xff0c;即使系统发生崩溃&#xff0c;其对数据库的更改也不能丢失。问题&#xff1a; 在事务提交前将所有修改的页面刷新到磁盘浪费资源。随机IO导致刷新速度慢。 解决方案&#xff1a; 【数据副本】记录事务执行过…

[Meachines][Easy]Bizness

Main $ nmap -p- 10.10.11.252 --min-rate 1000 $ dirsearch -u https://bizness.htb/ $ whatweb https://bizness.htb/control/login 存在一个未授权的RCE $ git clone https://github.com/jakabakos/Apache-OFBiz-Authentication-Bypass.git $ cd Apache-OFBiz-Authenticat…

java:观察者模式

java&#xff1a;观察者模式 1 前言 观察者模式&#xff0c;又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09;模式&#xff0c;他定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时&#xff0c;会通知所…

Visual Studio2022中使用水晶报表

1.创建水晶报表项目 选择需要的表 自动生成连接 选项&#xff1a;可跳过 后续还有一些 都能跳过 看你自己的需求 自己选的样式 自动生成 查看你的数据源&#xff0c;在选择数据集时已经有啦 不懂得可以看上集 字段可以直接拖&#xff0c;页面上的都是初始化选过的 点击生成 成功…

【系统架构师】-选择题(一)

1、信息系统规划方法中&#xff0c;关键成功因素法通过对关键成功因素的识别&#xff0c;找出实现目标所需要的关键信息集合&#xff0c;从而确定系统开发的 &#xff08;优先次序&#xff09; 。关键成功因素来源于组织的目标&#xff0c;通过组织的目标分解和关键成功因素识别…

docker容器内彻底移除iptables服务的实现方法

背景 我创建的容器使用的是centos6的标准镜像&#xff0c;所以内置了iptables服务。容器启动后iptables服务默认就启动了。iptables设置的规则默认是所有流量都无法通行。而对于服务器的管理使用的是宿主机的防火墙。这样就导致在实现用iptables动态给容器添加端口映射时不成功…

关于C++STL的总结(基础使用和底层原理)

STL是什么&#xff1f; STL即&#xff08;Standard Template Library&#xff09;标准模板库&#xff0c;提供了常见的数据结构和算法函数等&#xff0c;其下共包含六大组件&#xff1a; 容器算法迭代器仿函数适配器空间配置器 本篇重点介绍容器的使用和简单的底层实现原理&…

推荐六款图片编辑软件

图片编辑、抠图、拼图、压缩、放大、加字体、转格式等各种功能应有尽有&#xff0c;收藏这一篇就够了&#xff01; 综合编辑&#xff1a;图片编辑助手 这是一款电脑软件&#xff0c;里面有超多图片处理功能&#xff0c;压缩图片到指定大小、消除任意位置的图片水印、按指定大小…

【C++】---STL之vector的模拟实现

【C】---STL之vector的模拟实现 一、vector在源码中的结构&#xff1a;二、vector类的实现&#xff1a;1、vector的构造2、析构3、拷贝构造4、赋值运算符重载5、迭代器6、operator[ ]7、size()8、capacity()9、reserve()10、resize()11、empty()12、push_back()13、pop_back()1…

如何在PostgreSQL中设置自动清理过期数据的策略

文章目录 方法一&#xff1a;使用临时表和定期清理步骤&#xff1a;示例代码&#xff1a;创建临时表&#xff1a;定期清理脚本&#xff08;bash psql&#xff09;&#xff1a; 方法二&#xff1a;使用分区表和定期清理步骤&#xff1a;示例代码&#xff1a;创建分区表&#xf…

《内向者优势》:不要低估一个内向的人

#世界读书日 作者主页&#xff1a; &#x1f517;进朱者赤的博客 精选专栏&#xff1a;&#x1f517;经典算法 作者简介&#xff1a;阿里非典型程序员一枚 &#xff0c;记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; ❤…

Redis篇:缓存更新策略最佳实践

前景&#xff1a; 缓存更新是redis为了节约内存而设计出来的一个东西&#xff0c;主要是因为内存数据宝贵&#xff0c;当我们向redis插入太多数据&#xff0c;此时就可能会导致缓存中的数据过多&#xff0c;所以redis会对部分数据进行更新&#xff0c;或者把他叫为淘汰更合适&a…
最新文章