Agents lang-objc-library-dev
Objective-C-specific library and framework development patterns. Use when creating reusable frameworks, designing public APIs with protocols and categories, configuring Podspecs/Cartfiles/Package.swift, managing header visibility, implementing XCTest suites, writing HeaderDoc/appledoc documentation, or publishing to CocoaPods/Carthage/SPM. Extends lang-objc-dev with framework tooling and distribution practices.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/lang-objc-library-dev" ~/.claude/skills/arustydev-agents-lang-objc-library-dev && rm -rf "$T"
content/skills/lang-objc-library-dev/SKILL.mdObjective-C Library Development
Objective-C-specific patterns for library and framework development. This skill extends
lang-objc-dev with framework tooling, API design patterns, and ecosystem practices for creating reusable components.
This Skill Extends
- Foundational Objective-C patterns (classes, protocols, categories, memory management)lang-objc-dev
For general Objective-C syntax, memory management, and Foundation framework usage, see the foundational skill first.
This Skill Adds
- Framework tooling: Xcode framework projects, umbrella headers, module maps
- API design: Public/private header organization, protocol-driven design, category patterns
- Testing: XCTest for libraries, dependency injection, mocking strategies
- Documentation: HeaderDoc, appledoc, inline documentation
- Distribution: CocoaPods, Carthage, Swift Package Manager integration
This Skill Does NOT Cover
- General Objective-C patterns - see
lang-objc-dev - Swift interoperability - see
lang-swift-objc-bridge-dev - iOS/macOS application development - see platform-specific skills
- Advanced design patterns - see
lang-objc-patterns-dev
Quick Reference
| Task | Command/Tool |
|---|---|
| New framework | Xcode: File > New > Project > Framework |
| Build framework | |
| Run tests | |
| CocoaPods spec | |
| Validate podspec | |
| Publish to CocoaPods | |
| Carthage build | |
| SPM build | |
| Generate docs | |
Framework Project Structure
Standard Framework Layout
MyLibrary.framework/ ├── MyLibrary.xcodeproj ├── MyLibrary/ │ ├── MyLibrary.h # Umbrella header │ ├── Info.plist │ ├── Public/ # Public headers │ │ ├── MLPublicClass.h │ │ ├── MLProtocol.h │ │ └── MLConstants.h │ ├── Private/ # Private headers (not exported) │ │ └── MLInternal.h │ └── Implementation/ # .m files │ ├── MLPublicClass.m │ └── MLPrivateHelper.m ├── MyLibraryTests/ │ └── MyLibraryTests.m ├── MyLibrary.podspec ├── Cartfile ├── Package.swift ├── README.md ├── CHANGELOG.md └── LICENSE
Umbrella Header
// MyLibrary.h - Umbrella header that imports all public headers #import <Foundation/Foundation.h> //! Project version number for MyLibrary. FOUNDATION_EXPORT double MyLibraryVersionNumber; //! Project version string for MyLibrary. FOUNDATION_EXPORT const unsigned char MyLibraryVersionString[]; // Public headers #import <MyLibrary/MLPublicClass.h> #import <MyLibrary/MLProtocol.h> #import <MyLibrary/MLConstants.h> #import <MyLibrary/MLCategories.h>
Header Visibility Configuration
In Xcode target settings:
Build Phases > Headers ├── Public # Exported headers (API) │ ├── MyLibrary.h │ ├── MLPublicClass.h │ └── MLProtocol.h ├── Private # Not exported, but visible to framework │ └── MLInternal.h └── Project # Completely internal └── MLPrivateHelper.h
Public API Design
Header Organization Patterns
Pattern 1: Monolithic Umbrella
// Good for small libraries with few classes #import <MyLibrary/MLClassA.h> #import <MyLibrary/MLClassB.h> #import <MyLibrary/MLClassC.h>
Pattern 2: Feature-Based Modules
// Better for larger libraries #import <MyLibrary/MLCore.h> // Core functionality #import <MyLibrary/MLNetworking.h> // Network-related #import <MyLibrary/MLUI.h> // UI components // Each module header imports its components // MLCore.h: #import <MyLibrary/MLCoreClass.h> #import <MyLibrary/MLCoreProtocol.h>
Pattern 3: Prefix-Based Organization
// Consistent prefix prevents naming conflicts ML = MyLibrary MLC = MyLibrary Core MLN = MyLibrary Networking MLU = MyLibrary UI // Examples: @interface MLCClient : NSObject @interface MLNRequest : NSObject @interface MLUButton : UIButton
Protocol-Driven API Design
Define clear protocols for extensibility:
// MLDataProvider.h @protocol MLDataProvider <NSObject> @required /// Fetch data asynchronously - (void)fetchDataWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion; @optional /// Cancel ongoing fetch operation - (void)cancelFetch; /// Configure provider behavior - (void)configureWithOptions:(NSDictionary<NSString *, id> *)options; @end
Provide default implementation:
// MLDefaultDataProvider.h @interface MLDefaultDataProvider : NSObject <MLDataProvider> /// Designated initializer - (instancetype)initWithBaseURL:(NSURL *)baseURL NS_DESIGNATED_INITIALIZER; /// Convenience initializer + (instancetype)providerWithBaseURL:(NSURL *)baseURL; @end // MLDefaultDataProvider.m @implementation MLDefaultDataProvider - (instancetype)initWithBaseURL:(NSURL *)baseURL { self = [super init]; if (self) { _baseURL = [baseURL copy]; } return self; } + (instancetype)providerWithBaseURL:(NSURL *)baseURL { return [[self alloc] initWithBaseURL:baseURL]; } - (void)fetchDataWithCompletion:(void (^)(NSData *, NSError *))completion { // Default implementation } @end
Category-Based API Extensions
Provide convenience methods via categories:
// NSString+MLAdditions.h @interface NSString (MLAdditions) /// Returns MD5 hash of string - (NSString *)ml_md5Hash; /// Returns URL-encoded string - (NSString *)ml_urlEncode; /// Safe subscripting that returns nil for out-of-bounds - (nullable NSString *)ml_substringWithRange:(NSRange)range; @end // NSString+MLAdditions.m @implementation NSString (MLAdditions) - (NSString *)ml_md5Hash { // Implementation const char *cStr = [self UTF8String]; unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(cStr, (CC_LONG)strlen(cStr), digest); NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [output appendFormat:@"%02x", digest[i]]; } return output; } - (NSString *)ml_urlEncode { NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet]; return [self stringByAddingPercentEncodingWithAllowedCharacters:allowed]; } - (nullable NSString *)ml_substringWithRange:(NSRange)range { if (range.location + range.length > self.length) { return nil; } return [self substringWithRange:range]; } @end
Naming convention for categories:
- Prefix all methods with library prefix (e.g.,
)ml_ - Prevents collisions with other libraries or Apple's private APIs
- Makes it clear the method is from your library
Constants and Enumerations
Define constants in a dedicated header:
// MLConstants.h #import <Foundation/Foundation.h> /// Notification names FOUNDATION_EXPORT NSNotificationName const MLDidCompleteNotification; FOUNDATION_EXPORT NSNotificationName const MLDidFailNotification; /// Error domain FOUNDATION_EXPORT NSErrorDomain const MLErrorDomain; /// Error codes typedef NS_ERROR_ENUM(MLErrorDomain, MLErrorCode) { MLErrorCodeUnknown = 0, MLErrorCodeInvalidParameter = 1, MLErrorCodeNetworkFailure = 2, MLErrorCodeAuthenticationRequired = 3, }; /// Configuration keys FOUNDATION_EXPORT NSString * const MLConfigurationKeyTimeout; FOUNDATION_EXPORT NSString * const MLConfigurationKeyRetryCount; /// Options typedef NS_OPTIONS(NSUInteger, MLOptions) { MLOptionNone = 0, MLOptionVerbose = 1 << 0, MLOptionAsync = 1 << 1, MLOptionPersistent = 1 << 2, }; // MLConstants.m NSNotificationName const MLDidCompleteNotification = @"MLDidCompleteNotification"; NSNotificationName const MLDidFailNotification = @"MLDidFailNotification"; NSErrorDomain const MLErrorDomain = @"com.example.mylibrary.error"; NSString * const MLConfigurationKeyTimeout = @"timeout"; NSString * const MLConfigurationKeyRetryCount = @"retryCount";
Version Information
// MLVersion.h #import <Foundation/Foundation.h> /// Current library version FOUNDATION_EXPORT NSString * const MLVersionString; /// Version components FOUNDATION_EXPORT NSInteger const MLVersionMajor; FOUNDATION_EXPORT NSInteger const MLVersionMinor; FOUNDATION_EXPORT NSInteger const MLVersionPatch; // MLVersion.m NSString * const MLVersionString = @"1.2.3"; NSInteger const MLVersionMajor = 1; NSInteger const MLVersionMinor = 2; NSInteger const MLVersionPatch = 3;
XCTest Testing Strategies
Test Target Organization
MyLibraryTests/ ├── Unit/ │ ├── MLClassATests.m │ ├── MLClassBTests.m │ └── MLUtilsTests.m ├── Integration/ │ ├── MLNetworkingTests.m │ └── MLDataFlowTests.m ├── Helpers/ │ ├── MLTestHelpers.h │ ├── MLTestHelpers.m │ └── MLMockObjects.m └── Resources/ ├── test_data.json └── fixture.plist
Unit Test Patterns
// MLPublicClassTests.m #import <XCTest/XCTest.h> #import <MyLibrary/MyLibrary.h> @interface MLPublicClassTests : XCTestCase @property (nonatomic, strong) MLPublicClass *sut; // System Under Test @end @implementation MLPublicClassTests - (void)setUp { [super setUp]; // Create fresh instance for each test self.sut = [[MLPublicClass alloc] init]; } - (void)tearDown { // Clean up self.sut = nil; [super tearDown]; } #pragma mark - Initialization Tests - (void)testDesignatedInitializer { MLPublicClass *instance = [[MLPublicClass alloc] initWithName:@"Test"]; XCTAssertNotNil(instance, @"Instance should not be nil"); XCTAssertEqualObjects(instance.name, @"Test", @"Name should be set"); } - (void)testConvenienceInitializer { MLPublicClass *instance = [MLPublicClass instanceWithName:@"Test"]; XCTAssertNotNil(instance); XCTAssertEqualObjects(instance.name, @"Test"); } #pragma mark - Behavior Tests - (void)testProcessDataWithValidInput { NSData *input = [@"valid data" dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; BOOL result = [self.sut processData:input error:&error]; XCTAssertTrue(result, @"Processing should succeed"); XCTAssertNil(error, @"No error should occur"); } - (void)testProcessDataWithInvalidInput { NSError *error = nil; BOOL result = [self.sut processData:nil error:&error]; XCTAssertFalse(result, @"Processing should fail"); XCTAssertNotNil(error, @"Error should be populated"); XCTAssertEqual(error.code, MLErrorCodeInvalidParameter); } #pragma mark - Async Tests - (void)testAsyncFetchWithExpectation { XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch completes"]; [self.sut fetchDataWithCompletion:^(NSData *data, NSError *error) { XCTAssertNotNil(data, @"Data should be fetched"); XCTAssertNil(error, @"No error should occur"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { XCTFail(@"Timeout waiting for fetch: %@", error); } }]; } #pragma mark - Edge Cases - (void)testEmptyString { NSString *result = [self.sut processString:@""]; XCTAssertEqualObjects(result, @"", @"Empty string should be handled"); } - (void)testNilParameter { XCTAssertNoThrow([self.sut processString:nil], @"Should handle nil gracefully"); } - (void)testLargeInput { NSMutableString *large = [NSMutableString string]; for (int i = 0; i < 100000; i++) { [large appendString:@"x"]; } NSString *result = [self.sut processString:large]; XCTAssertNotNil(result, @"Should handle large input"); } @end
Mock Objects for Testing
// MLMockDataProvider.h @interface MLMockDataProvider : NSObject <MLDataProvider> @property (nonatomic, strong) NSData *mockData; @property (nonatomic, strong) NSError *mockError; @property (nonatomic, assign) BOOL shouldFail; @property (nonatomic, assign) NSTimeInterval delay; @end // MLMockDataProvider.m @implementation MLMockDataProvider - (void)fetchDataWithCompletion:(void (^)(NSData *, NSError *))completion { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.shouldFail) { completion(nil, self.mockError); } else { completion(self.mockData, nil); } }); } @end // Usage in tests: - (void)testWithMockProvider { MLMockDataProvider *mock = [[MLMockDataProvider alloc] init]; mock.mockData = [@"test" dataUsingEncoding:NSUTF8StringEncoding]; mock.shouldFail = NO; MLClient *client = [[MLClient alloc] initWithDataProvider:mock]; // Test client with predictable mock behavior }
Test Helpers
// MLTestHelpers.h @interface MLTestHelpers : NSObject /// Load JSON from test bundle + (NSDictionary *)loadJSONFixture:(NSString *)filename; /// Create temporary directory for test files + (NSURL *)createTemporaryDirectory; /// Clean up test resources + (void)cleanupTemporaryDirectory:(NSURL *)directory; @end // MLTestHelpers.m @implementation MLTestHelpers + (NSDictionary *)loadJSONFixture:(NSString *)filename { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSURL *url = [bundle URLForResource:filename withExtension:@"json"]; NSData *data = [NSData dataWithContentsOfURL:url]; return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; } + (NSURL *)createTemporaryDirectory { NSString *tempDir = NSTemporaryDirectory(); NSString *guid = [[NSUUID UUID] UUIDString]; NSString *path = [tempDir stringByAppendingPathComponent:guid]; [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; return [NSURL fileURLWithPath:path]; } + (void)cleanupTemporaryDirectory:(NSURL *)directory { [[NSFileManager defaultManager] removeItemAtURL:directory error:nil]; } @end
HeaderDoc and Documentation
HeaderDoc Syntax
// MLPublicClass.h /*! @class MLPublicClass @abstract A brief description of the class @discussion A more detailed description of what this class does, its purpose, and how it should be used. Example usage: @code MLPublicClass *instance = [[MLPublicClass alloc] initWithName:@"Example"]; [instance processData:data error:&error]; @endcode @note This class is thread-safe. @warning Do not use this class in a tight loop. */ @interface MLPublicClass : NSObject /*! @property name @abstract The name associated with this instance @discussion The name is used to identify this instance in logs and error messages. Must not be nil or empty. */ @property (nonatomic, copy, readonly) NSString *name; /*! @method initWithName: @abstract Designated initializer @param name The name for this instance. Must not be nil. @return An initialized instance, or nil if name is invalid @discussion This is the designated initializer for this class. All other initializers should call this method. @throws NSInvalidArgumentException if name is nil */ - (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; /*! @method instanceWithName: @abstract Convenience class method to create instances @param name The name for the instance @return A new autoreleased instance */ + (instancetype)instanceWithName:(NSString *)name; /*! @method processData:error: @abstract Process input data @param data The data to process. Must not be nil. @param error On input, a pointer to an error object. If an error occurs, this pointer is set to an NSError object describing the error. Pass NULL if you don't care about error information. @return YES if processing succeeded, NO otherwise @discussion This method processes the input data according to the configured rules. If processing fails, the error parameter will be populated with details. Error codes: - MLErrorCodeInvalidParameter: data parameter was nil - MLErrorCodeProcessingFailed: processing encountered an error @see fetchDataWithCompletion: */ - (BOOL)processData:(NSData *)data error:(NSError **)error; /*! @method fetchDataWithCompletion: @abstract Asynchronously fetch data @param completion Completion block called when fetch completes or fails. Called on the main queue. @discussion This method fetches data asynchronously. The completion block is always called, either with data on success or an error on failure. @code [instance fetchDataWithCompletion:^(NSData *data, NSError *error) { if (error) { NSLog(@"Failed: %@", error); } else { // Use data } }]; @endcode */ - (void)fetchDataWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion; @end
Nullability Annotations
NS_ASSUME_NONNULL_BEGIN @interface MLClient : NSObject /// Returns a value, never nil - (NSString *)getName; /// May return nil - (nullable NSString *)getOptionalValue; /// Takes non-null parameter - (void)setName:(NSString *)name; /// Takes nullable parameter - (void)setOptionalValue:(nullable NSString *)value; /// Block with nullable parameters and return - (void)fetchWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion; @end NS_ASSUME_NONNULL_END
Appledoc Configuration
Create a
.appledoc configuration file:
--project-name "MyLibrary" --project-company "Your Company" --company-id "com.yourcompany" --output "./Documentation" --logformat xcode --keep-intermediate-files --no-repeat-first-par --no-warn-invalid-crossref --ignore "*.m" --ignore "Private" --index-desc "./README.md"
Generate documentation:
appledoc .
CocoaPods Distribution
Podspec Structure
# MyLibrary.podspec Pod::Spec.new do |s| # Metadata s.name = 'MyLibrary' s.version = '1.2.3' s.summary = 'A brief description of MyLibrary' s.description = <<-DESC A longer description of MyLibrary that explains what it does, why it exists, and what problems it solves. This should be more detailed than the summary. DESC # Homepage and license s.homepage = 'https://github.com/yourusername/mylibrary' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'Your Name' => 'you@example.com' } s.source = { :git => 'https://github.com/yourusername/mylibrary.git', :tag => s.version.to_s } # Platform requirements s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.13' s.watchos.deployment_target = '5.0' s.tvos.deployment_target = '12.0' # Source files s.source_files = 'MyLibrary/**/*.{h,m}' s.public_header_files = 'MyLibrary/Public/**/*.h' s.private_header_files = 'MyLibrary/Private/**/*.h' # Resources s.resources = 'MyLibrary/Resources/**/*' # Dependencies s.dependency 'AFNetworking', '~> 4.0' s.ios.dependency 'SDWebImage', '~> 5.0' # Frameworks s.frameworks = 'Foundation', 'UIKit' # Build settings s.requires_arc = true s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } # Subspecs for modular features s.subspec 'Core' do |core| core.source_files = 'MyLibrary/Core/**/*.{h,m}' core.public_header_files = 'MyLibrary/Core/Public/**/*.h' end s.subspec 'Networking' do |net| net.source_files = 'MyLibrary/Networking/**/*.{h,m}' net.dependency 'MyLibrary/Core' net.dependency 'AFNetworking', '~> 4.0' end s.default_subspecs = 'Core' end
CocoaPods Workflow
# Create podspec pod spec create MyLibrary # Validate locally pod lib lint MyLibrary.podspec # Validate with remote sources pod spec lint MyLibrary.podspec # Register with CocoaPods trunk (first time only) pod trunk register you@example.com 'Your Name' # Push to CocoaPods pod trunk push MyLibrary.podspec # Add collaborators pod trunk add-owner MyLibrary other@example.com
Podfile for Development
# Podfile for development/testing platform :ios, '12.0' use_frameworks! target 'MyLibrary' do # Your library dependencies pod 'AFNetworking', '~> 4.0' end target 'MyLibraryTests' do # Test dependencies pod 'OCMock', '~> 3.9' end # Use local development pod # pod 'MyLibrary', :path => './'
Carthage Distribution
Cartfile Structure
# Cartfile - Dependencies for your library github "AFNetworking/AFNetworking" ~> 4.0
Carthage Compatibility
Ensure your framework is Carthage-compatible:
- Use shared schemes - Make sure your framework scheme is shared
- Build universal frameworks - Support both iOS devices and simulator
# Build for Carthage carthage build --no-skip-current # Create XCFramework (Xcode 11+) carthage build --use-xcframeworks
Build Script for Universal Framework
#!/bin/bash # build-universal.sh FRAMEWORK_NAME="MyLibrary" SCHEME="MyLibrary" # Build for device xcodebuild archive \ -scheme "${SCHEME}" \ -archivePath "build/ios.xcarchive" \ -sdk iphoneos \ SKIP_INSTALL=NO \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES # Build for simulator xcodebuild archive \ -scheme "${SCHEME}" \ -archivePath "build/ios-simulator.xcarchive" \ -sdk iphonesimulator \ SKIP_INSTALL=NO \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES # Create XCFramework xcodebuild -create-xcframework \ -framework "build/ios.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ -framework "build/ios-simulator.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ -output "build/${FRAMEWORK_NAME}.xcframework"
Swift Package Manager
Package.swift
// swift-tools-version:5.5 import PackageDescription let package = Package( name: "MyLibrary", platforms: [ .iOS(.v12), .macOS(.v10_13), .watchOS(.v5), .tvOS(.v12) ], products: [ .library( name: "MyLibrary", targets: ["MyLibrary"]), ], dependencies: [ // External dependencies .package(url: "https://github.com/AFNetworking/AFNetworking.git", from: "4.0.0"), ], targets: [ .target( name: "MyLibrary", dependencies: ["AFNetworking"], path: "MyLibrary", publicHeadersPath: "Public", cSettings: [ .headerSearchPath("Private"), ]), .testTarget( name: "MyLibraryTests", dependencies: ["MyLibrary"], path: "MyLibraryTests"), ] )
SPM Directory Structure
MyLibrary/ ├── Package.swift ├── Sources/ │ └── MyLibrary/ │ ├── include/ # Public headers │ │ ├── MyLibrary.h │ │ ├── MLPublicClass.h │ │ └── MLProtocol.h │ └── Implementation/ # .m files │ └── MLPublicClass.m ├── Tests/ │ └── MyLibraryTests/ │ └── MyLibraryTests.m └── README.md
Module Maps
Custom Module Map
// module.modulemap framework module MyLibrary { umbrella header "MyLibrary.h" export * module * { export * } explicit module Private { header "MLInternal.h" export * } }
Module Map in Xcode
Configure in Build Settings:
DEFINES_MODULE = YES MODULEMAP_FILE = $(SRCROOT)/MyLibrary/module.modulemap
Versioning and Release Strategy
Semantic Versioning
Follow semver.org:
- MAJOR: Incompatible API changes
- MINOR: Backwards-compatible functionality additions
- PATCH: Backwards-compatible bug fixes
Deprecation Strategy
// Deprecate old API - (void)oldMethod:(id)parameter DEPRECATED_MSG_ATTRIBUTE("Use newMethod:withOptions: instead"); // Introduce replacement - (void)newMethod:(id)parameter withOptions:(NSDictionary *)options; // Provide compatibility shim - (void)oldMethod:(id)parameter { NSLog(@"Warning: oldMethod: is deprecated. Use newMethod:withOptions:"); [self newMethod:parameter withOptions:@{}]; }
Version Macro
// MLVersion.h #define ML_VERSION_MAJOR 1 #define ML_VERSION_MINOR 2 #define ML_VERSION_PATCH 3 #define ML_VERSION_CHECK(major, minor, patch) \ (ML_VERSION_MAJOR > (major) || \ (ML_VERSION_MAJOR == (major) && ML_VERSION_MINOR > (minor)) || \ (ML_VERSION_MAJOR == (major) && ML_VERSION_MINOR == (minor) && ML_VERSION_PATCH >= (patch))) // Usage: #if ML_VERSION_CHECK(1, 2, 0) // Code for version 1.2.0 and later #endif
CHANGELOG.md
# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added - New feature in development ## [1.2.3] - 2024-01-15 ### Fixed - Fixed crash when processing nil data - Corrected memory leak in network layer ## [1.2.0] - 2024-01-01 ### Added - New `MLClient` class for network operations - Support for custom data providers via `MLDataProvider` protocol ### Changed - Improved performance of data processing by 50% - Updated minimum deployment target to iOS 12.0 ### Deprecated - `oldMethod:` in favor of `newMethod:withOptions:` ## [1.1.0] - 2023-12-01 ### Added - Initial public release
Troubleshooting
Framework Not Found at Runtime
Symptoms:
dyld: Library not loaded: @rpath/MyLibrary.framework/MyLibrary
Solution:
1. Check Build Settings > Runpath Search Paths - Should include @executable_path/Frameworks 2. Verify framework is embedded: - Build Phases > Embed Frameworks - Framework should be listed 3. For CocoaPods, ensure: - use_frameworks! is in Podfile - Run pod install after changes
Duplicate Symbol Errors
Symptoms:
duplicate symbol _OBJC_CLASS_$_ClassName
Solution:
// Ensure category methods use unique prefix // BAD: @implementation NSString (Additions) - (NSString *)reverse { ... } @end // GOOD: @implementation NSString (MLAdditions) - (NSString *)ml_reverse { ... } @end
Missing Header in Umbrella
Symptoms: Header not visible to framework users
Solution:
1. Check header visibility in Xcode: - Target > Build Phases > Headers - Move header from Project/Private to Public 2. Add to umbrella header: - #import <MyLibrary/MLNewClass.h> 3. Clean and rebuild: - Product > Clean Build Folder (Cmd+Shift+K)
Swift Bridging Issues
Symptoms: Objective-C types not visible in Swift
Solution:
// Use nullability annotations NS_ASSUME_NONNULL_BEGIN @interface MLClass : NSObject - (NSString *)getName; // Nonnull by default - (nullable NSString *)getOptionalName; @end NS_ASSUME_NONNULL_END // Use generics for collections @property (nonatomic, copy) NSArray<NSString *> *items; @property (nonatomic, copy) NSDictionary<NSString *, NSNumber *> *counts;
Podspec Validation Failures
Symptoms:
pod lib lint fails
Solution:
# Get verbose output pod lib lint --verbose # Common issues: # 1. Missing source files # - Check s.source_files pattern matches actual files # 2. Dependency conflicts # - Ensure dependency versions are compatible # - Use ~> for flexible version matching # 3. Build settings issues # - Add necessary frameworks to s.frameworks # - Set s.requires_arc appropriately # 4. Missing license # - Ensure LICENSE file exists # - Match s.license specification
Best Practices Summary
- Prefix all public symbols with 2-3 letter prefix (classes, protocols, constants, categories)
- Use nullability annotations for Swift interoperability
- Document with HeaderDoc for generated documentation
- Provide protocol-based APIs for extensibility
- Include comprehensive tests with XCTest
- Support multiple distribution methods (CocoaPods, Carthage, SPM)
- Follow semantic versioning strictly
- Maintain backwards compatibility when possible
- Deprecate before removing APIs
- Keep umbrella header clean and organized
See Also
- Foundational Objective-C patternslang-objc-dev
- Swift interoperabilitylang-swift-objc-bridge-dev
- Advanced design patternslang-objc-patterns-dev- CocoaPods Guides
- Carthage Documentation
- Swift Package Manager Documentation
- HeaderDoc User Guide