美文网首页
在iOS混编项目中改造objc代码

在iOS混编项目中改造objc代码

作者: yue博客 | 来源:发表于2022-02-23 19:03 被阅读0次

    对objc代码改造,适应swift调用的同时,也能提升objc代码质量

    1.可选值

    //在这两个宏之间的都默认是nonnull型
    NS_ASSUME_NONNULL_BEGIN
    NS_ASSUME_NONNULL_END
    //如要指定某个属性、返回值或者参数为可选值类型,则可以单独使用下面的关键字
    nullable _Nullable
    

    nullable、nonnull_Nullable、_Nonnull的区别
    nullable、nonnull用在方法参数或者属性的修饰
    _Nullable、_Nonnull用在常量、任意指针的修饰
    当不确定是否会返回空值时,可使用null_unspecified、_Null_unspecified修饰
    ps.当使用以上关键词修饰后,需特别注意编译器抛出的警告⚠️
    例如一个使用nonnull修饰的NSString属性,返回了nil,此时编译器会抛出警告,不影响编译执行,但在swift调用中,则会返回一个""空的字符串,这可能就会导致类似 if string == "xx" else的条件语句结果与预期不一致,导致逻辑错误。而如果是其它类型,则可能会导致闪退

    2.枚举、常量、宏

    枚举,首先要避免使用c的enum,改为使用NS_ENUMNS_OPTION;然后使用NS_SWIFT_NAME适配swift规范

    如下一个OC的枚举

    typedef NS_ENUM(NSUInteger, NotificationServiceType) {
        NotificationServiceTypeLocal,
        NotificationServiceTypeRemote
    };
    

    系统会自动转换为

    public enum NotificationServiceType : UInt {
        case local = 0
        case remote = 1
    }
    

    如果要像UITableViewCellStyle转换为UITableViewCell.Style则可使用NS_SWIFT_NAME(NotificationService.Type)

    typedef NS_ENUM(NSUInteger, NotificationServiceType) {
        NotificationServiceTypeLocal,
        NotificationServiceTypeRemote
    } NS_SWIFT_NAME(NotificationService.Type);
    @interface NotificationService : UNNotificationServiceExtension
    @end
    
    extension NotificationService {
        public enum `Type` : UInt {
            case local = 0
            case remote = 1
        }
    }
    open class NotificationService : UNNotificationServiceExtension {
        open var type: NotificationService.`Type`
    }
    

    常量改造

    字符串常量使用:NS_STRING_ENUM NS_EXTENSIBLE_STRING_ENUM
    常数常量使用:NS_TYPED_ENUM NS_TYPED_EXTENSIBLE_ENUM
    如下定义的字符串常量

    FOUNDATION_EXTERN NSString *const NotificationServiceOptionTitle;
    FOUNDATION_EXTERN NSString *const NotificationServiceOptionSubtitle;
    FOUNDATION_EXTERN NSString *const NotificationServiceOptionBody;
    

    如要更适用swift调用,则需做如下改造
    1.首先上述的例子,在objc中也是不够规范的,应当typedef NSString *const NotificationServiceOption;进行类型声明
    2.使用NS_STRING_ENUM或者NS_EXTENSIBLE_STRING_ENUM修饰NotificationServiceOption
    改造如下:

    typedef NSString *const NotificationServiceOption NS_STRING_ENUM;
    FOUNDATION_EXTERN NotificationServiceOption NotificationServiceOptionTitle;
    FOUNDATION_EXTERN NotificationServiceOption NotificationServiceOptionSubtitle;
    FOUNDATION_EXTERN NotificationServiceOption NotificationServiceOptionBody;
    

    swift转换如下

    //NS_STRING_ENUM
    public struct NotificationServiceOption : Hashable, Equatable, RawRepresentable {
        public init(rawValue: String)
    }
    extension NotificationServiceOption {
        public static let title: NotificationServiceOption
        public static let subtitle: NotificationServiceOption
        public static let body: NotificationServiceOption
    }
    

    NS_STRING_ENUMNS_EXTENSIBLE_STRING_ENUM的区别在于,NS_EXTENSIBLE_STRING_ENUM在swift中可扩展

    宏定义

    swift无法完全识别objc中的宏定义,对于单个变量的宏定义,如字符串、常数等,swift会识别为常量
    #define NotificationServiceToken @"abcdefg"会被识别为public var NotificationServiceToken: String { get }
    而一些复杂的表达式,方法宏则无法被swift识别

    3.错误处理

    使用NS_ERROR_ENUM定义error枚举
    SDWebImage中的做法为例

    FOUNDATION_EXPORT NSErrorDomain const _Nonnull SDWebImageErrorDomain;
    typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
        SDWebImageErrorInvalidURL = 1000, 
        SDWebImageErrorBadImageData = 1001,
    };
    

    会被swift识别为

    public let SDWebImageErrorDomain: String
    public struct SDWebImageError {
        public init(_nsError: NSError)
        public static var errorDomain: String { get }
        public enum Code : Int {
            public typealias _ErrorType = SDWebImageError
            case invalidURL = 1000
            case badImageData = 1001 
        }
        public static var invalidURL: SDWebImageError.Code { get }
        public static var badImageData: SDWebImageError.Code { get }
    }
    

    这样就可以像swift原生一样,使用SDWebImageError处理异常

    func testSDWebImageError() {
        do {
            let url = try testThrowSDError(path: "")
        } catch let error as SDWebImageError {
            switch error.code {
            case .invalidURL:
                // do something
                print("invalidURL")
                break
            default: break
            }
        } catch {
        }
    }
    func testThrowSDError(path: String?) throws -> URL {
        if let path = path, !path.isEmpty, let url = URL(string: path) {
            return url
        } else {
            throw SDWebImageError(.invalidURL)
        }
    }
    

    4.api命名 NS_SWIFT_NAME

    通常swift识别的objc方法,会根据返回值类型、方法名、参数类型、参数名等语义识别出恰当的swift函数名、参数标签、参数名等,但有时并不能完全符合开发者的期望,此时可NS_SWIFT_NAME手动声明swift转换的函数名

    5.构造器

    NS_DESIGNATED_INITIALIZER,NS_UNAVAILABLE

    6.第三方库的引用

    1.CocoaPods引用OC库,在pod路径后添加:modular_headers => true参数
    2....

    7.使用objc泛型

    //bad
    @property (strong) NSArray *array;
    @property (strong) NSDictionary *dictionary;
    //good
    @property (strong) NSArray<NSString *> *genericArray;
    @property (strong) NSDictionary<NSString *, NSString *> *genericDictionary;
    
    
    //bad
    open var array: [Any]
    open var dictionary: [AnyHashable : Any]
    //good
    open var genericArray: [String]
    open var genericDictionary: [String : String]
    

    最后,值得一提的是,以上改造不仅仅便于swift调用,也能提升objc的代码质量和规范。同时从swift的角度去思考objc代码,也能加深对objc结构的理解。

    相关文章

      网友评论

          本文标题:在iOS混编项目中改造objc代码

          本文链接:https://www.haomeiwen.com/subject/diinkrtx.html