What is the equivalent of + (void)load {} in Swift 4? [duplicate] - objective-c

In Objective-C, NSObject had a class method called load that gets called when the class is loaded for the first time. What is the equivalent in Swift?
#implementation MyClass
+ (void)load
{
[self registerClass];
}
#end

Prior to Swift 1.2:
override class func load() {
NSLog("load");
}
EDIT:
As of Swift 1.2 you can no longer override the load method. Look into the method initialize instead, it behaves different than load though, it get's called the first time the class is being referenced somewhere rather than on application initial load

Support for overriding load was removed in Swift 1.2

Update: Starting from Swift 5 class extensions and categories on Swift classes are not allowed to have +load methods, +initialize doesn’t seem to be prohibited, though.
While direct equivalent is not available with Swift, this can be achieved relatively elegantly with Objective-C and categories. Foo should still inherit from NSObject and have a class / static non-private swiftyLoad/Initialize methods, which get invoked from Objective-C in Loader.m, which you include in compile sources alongside Foo.swift:
# Loader.m
#import <Foundation/Foundation.h>
#import <MyProject/MyProject-Swift.h>
// This was a more concise solution in Swift 4.2 and earlier. If you try
// this with Swift 5 you'd get "Swift class extensions and categories
// on Swift classes are not allowed to have +load methods" runtime error.
// #implementation Foo (private)
// + (void)load { [self swiftyLoad]; }
// + (void)initialize { [self swiftyInitialize]; }
// #end
// This is what you'd want to use with Swift 5 onwards, basically
// just a pure Objective-C defined class.
#interface Loader : NSObject
#end
#implementation Loader : NSObject
+ (void)load { [Foo swiftyLoad]; }
+ (void)initialize { [Foo swiftyInitialize]; }
#end
# Foo.swift
import Foundation
public class Foo: NSObject {
#objc public static func swiftyLoad() {
Swift.print("Rock 'n' roll!")
}
#objc public static func swiftyInitialize() {
Swift.print("Hello initialize!")
}
}
The best part there's no need to mess with bridging headers or anything else, it just works. There are couple of gotchas, especially when using this approach in static libraries, check out the complete post on Medium for details! ✌️

in Swift 2.0, Please use this method
public override class func initialize()

What is the equivalent in Swift?
There is none.
Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.
Source: "The Swift Programming Language", by Apple
Types simply don't have an initializer in Swift. As several answers here suggest, you may be able to work around that by using the Objective-C bridge and having your Swift class inherit from NSObject or by using a Objective-C loader bootstrap code but if you have a 100% Swift project, you basically only have two options:
Manually initialize your classes somewhere within the code:
class MyCoolClass {
static func initClass ( ) {
// Do your init stuff here...
}
}
// Somewhere in a method/function that is guaranteed to be called
// on app startup or at some other relevant event:
MyCoolClass.initClass()
Use a run once pattern in all methods that require an initialized class:
class MyCoolClass {
private static var classInitialized: Bool = {
// Do your init stuff here...
// This code will for sure only run once!
return true
}()
private static func ensureClassIsInitialized ( ) {
_ = self.classInitialized
}
func whateverA ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
func whateverB ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
func whateverC ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
}
This follows a clean pattern that no code in Swift runs "automagically", code only runs if other code instructs it to run, which provides a clear and trackable code flow.
There might be a better solution for special cases yet you have not provided any information as for why you need that feature to begin with.

The abilitity to use this method was removed in this PR.
It provides some rationale:
Swift's language model doesn't guarantee that type metadata will ever really be used, which makes overriding initialize() error-prone and not really any better than manually invoking an initialization function. Warn about this for Swift 3 compatibility and reject attempts to override +initialize in Swift 4.

For Swift 2 or 3 (i.e. post-Swift 1.2), you can use:
class MySwiftClass: NSObject {
internal override class func initialize() {
DoStuff()
super.initialize()
}
}
But, as you can see, your class needs to inherit (directly or indirectly) form NSObject. This is required because the initialize() is called by the ObjC runtime.
And the initialize() method will only be called when MySwiftClass is referenced. So it will not be as magic as load().
But it will also be safer. For example: including a framework (let's say, by just adding it to your Podfile) won't allow the framework to mysteriously start to behave as soon as your app launches, without the need to add a single line of code to your project (at least… I hope! 😉).

I get conclusion for swift 1.2~5 doable ways:
Doable |swift-load |swift-initialze|bridgeToOC-load|bridgeToOC-initialze|
--- |---- |--- |--- |--- |
swift1.2~4.2|X |O |O |O |
swift4.2~5.0|X |X |O |O |
swift5.0~? |X |X |X |O |
and how to make it ? check here
https://medium.com/post-mortem/using-nsobjects-load-and-initialize-from-swift-f6f2c6d9aad0
but I think here is another way to make it swifter. use runtime and protol without load/initialze.
http://jordansmith.io/handling-the-deprecation-of-initialize/

Swift 5
Method 'load()' defines Objective-C class method 'load', which is not permitted by Swift
You can not override load() method now but there is a legal way to call your custom swift_load() method of your swift classes on app startup. It is needed to make ObjC class which redirect its own load() method to your swift classes.
Just add next SwiftLoader.m file to your project:
// SwiftLoader.m
#interface SwiftLoader : NSObject
#end
#implementation SwiftLoader
+ (void)load {
SEL selector = #selector(swift_load);
int numClasses = objc_getClassList(NULL, 0);
Class* classes = (Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class class = classes[i];
Method method = class_getClassMethod(class, selector);
if (method != NULL) {
IMP imp = method_getImplementation(method);
((id (*)(Class, SEL))imp)(class, selector);
}
}
free(classes);
}
Now you can add swift_load class method to any of your swift classes to make setup tasks:
class MyClass {
#objc
class func swift_load() {
print("load")
}
}

Related

Exposing a Swift variable in Objective-C

I am using a Swift class which uses a variable sharedInstance. When I am trying to use this variable in my Objective-C file, I am getting the error:
No known class method for selector 'sharedInstance'
My Swift class:
class SpeakToMeForUnity : NSObject, SFSpeechRecognizerDelegate {
fileprivate var speechRecognizer : SFSpeechRecognizer?
fileprivate var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
fileprivate var recognitionTask : SFSpeechRecognitionTask?
fileprivate var audioEngine : AVAudioEngine?
static let sharedInstance = SpeakToMeForUnity()
override fileprivate init() {
super.init()
self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
self.audioEngine = AVAudioEngine()
self.speechRecognizer?.delegate = self
}
// Some other functions
}
Below is my Objective-C file:
#import <Speech/Speech.h>
#import "SpeakToMeForUnity-Swift.h"
extern "C"
{
void _prepareRecording()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance prepareRecording];
}
void _recordButtonTapped()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance recordButtonTapped];
}
}
I am using Swift 4 and Xcode 10.3.
Normally to make Swift available in Objective-C you just need to import your swift module #import "ProductModuleName-Swift.h", as you're doing.
If you need the swift code in the Objective-C header use #class MySwiftClass; or #protocol MySwiftClass;.
You can force some class or property to be accessible in Objective-C by adding the attribute #objc.
Remember that private or fileprivate access level attributes will not be accessible unless you add the previous attribute.
To expose things to ObjC, you need to mark them #objc. In this case you should add this before class and before the static let.
In previous versions of Swift, inheritance from NSObject automatically implied #objc, but that was removed, and it now needs to be explicit.

Accessing readonly property in testing iOS Swift

I am pretty new to iOS and Swift and currently I'm facing a problem with writing a Unit Test. I have a class (let's suppose it is called A) which has (readonly property from Objective-C) and in my test I want to have object of this class to pass to the method which later does something with it. Oh, I don't have any initializers as well... My question is, how to test such think? Maybe I have to mock such object somehow?
------EDIT-----
Okay. My post wasn't quite precise. Okay, so I know only basics of Swift (unfortunately I don't have time now to learn Objective - C as I was asked to write sth in Swift). I have a class provided by some company in which I have a class (written in Objective-C) like:
#interface MainClassX : NSObject
#property (nonatomic, strong, readonly) NSString* code;
#property (nonatomic, strong, readonly) NSArray<XYZ*>* classification;
#end
And in my test want to create an object of this class and init at least 'code' property... but the setter is private so I can't do any 'inheritance trick'...? Is there any option to do it or should I do it another way? The problem is that I want to test a method which takes array of such objects and does sth with them.
It's pretty tricky because they wanted those properties to be readonly, why do you want to test them?
Regardless the purpose, you can do these steps:
1. Look into adding methods to that class using Category (in Objective C), or extension(in Swift).
2. Implement that new init method, set the code properpty using Key-Value Programming
I have managed to do it real quick in Objective C, it's pretty straight forward to convert to Swift.
#implementation MainClassX(Test)
-(instancetype)initWithCode:(NSString *)code {
self = [self init];
if (self) {
[self setValue:code forKey:#"code"];
}
return self;
}
#end
Test it:
MainClassX *test = [[MainClassX alloc] initWithCode:#"TEST"];
NSLog(#"code: %#", test.code); // Should print out "TEST" in the console
Swift:
extension MainClassX {
convenience init(_ code: String) {
self.init()
self.setValue(code, forKey: "code")
}
}
In the unit test:
import XCTest
#testable import YourAppModule
class YourAppModuleTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let cls = MainClassX("TEST")
XCTAssert(cls.code == "TEST")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
You are likely looking for dependancy injection. This is a way that you can initialize a class with an optional value that can set values for testing, as you want to do.
The following is a simple example.
Create an optional initialize for your Objective-C class:
- (instancetype)initWithOption:(NSString *)option {
self = [super init];
if (self) {
self.option = option;
}
return self;
}
You can have it such that when you normally call the class, you call its default init. But for testing, initialize it with this function. Another thing to consider if you may want to have a protected header file (such as classname_protected.h) which you only use in your unit tests so that you do not expose this function to your application.
Without seeing more of your test, it is a bit difficult to add on to this, but DI is likely where you need to go for it.

How to extend an Objective-C class with Swift in a framework?

I have an (OS X) Objective-C framework to which I want to add some Swift extensions and I'm using Xcode 7ß6 to work with this. There is class in the framework (let's call it "Sample") implemented in the files "Sample.h" and "Sample.m" .. "Sample.h" contains:
#import <Foundation/Foundation.h>
#interface Sample : NSObject
#property int x;
#end
.. and "Sample.m" contains:
#import "Sample.h"
#implementation Sample
- (instancetype) init {
if ((self = [super init]) == nil) return nil;
self.x = 99;
return self;
}
#end
I have added "Sample.swift" to the framework containing:
import Foundation
extension Sample {
func PrettyPrint () {
print("\(x)")
}
}
This is clearly a trivial version of what I want to do in a larger context, here I want to use the Swift file to extend "Sample" by adding a "PrettyPrint" function.
.. the framework builds without error, but the framework function "PrettyPrint" is not visible to a calling app. App code calling into the framework like:
import Foundation
import TestKit
let sample = Sample()
sample.PrettyPrint()
fails on "sample.PrettyPrint()" with: Value of type 'Sample' has no member 'PrettyPrint'
Why does this fail? and Can it be made to work?
Extra info: If I remove the file "Sample.swift" from the framework and place it the app which is calling into the framework, the "Sample" class is successfully extended and "sample.PrettyPrint()" works as expected (printing "99").
Have you tried making the extension and function public?
public extension Sample {
public func PrettyPrint () {
print("\(x)")
}
}
In a similar case, Tie's answer really helped me but it didn't work for me, I had to add #objc for the method:
public extension SearchBarWidget {
#objc public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.endEditing(true)
}
}
The SearchBarWidget class was #objc public signatured.

Swift - Objective-C load class method?

In Objective-C, NSObject had a class method called load that gets called when the class is loaded for the first time. What is the equivalent in Swift?
#implementation MyClass
+ (void)load
{
[self registerClass];
}
#end
Prior to Swift 1.2:
override class func load() {
NSLog("load");
}
EDIT:
As of Swift 1.2 you can no longer override the load method. Look into the method initialize instead, it behaves different than load though, it get's called the first time the class is being referenced somewhere rather than on application initial load
Support for overriding load was removed in Swift 1.2
Update: Starting from Swift 5 class extensions and categories on Swift classes are not allowed to have +load methods, +initialize doesn’t seem to be prohibited, though.
While direct equivalent is not available with Swift, this can be achieved relatively elegantly with Objective-C and categories. Foo should still inherit from NSObject and have a class / static non-private swiftyLoad/Initialize methods, which get invoked from Objective-C in Loader.m, which you include in compile sources alongside Foo.swift:
# Loader.m
#import <Foundation/Foundation.h>
#import <MyProject/MyProject-Swift.h>
// This was a more concise solution in Swift 4.2 and earlier. If you try
// this with Swift 5 you'd get "Swift class extensions and categories
// on Swift classes are not allowed to have +load methods" runtime error.
// #implementation Foo (private)
// + (void)load { [self swiftyLoad]; }
// + (void)initialize { [self swiftyInitialize]; }
// #end
// This is what you'd want to use with Swift 5 onwards, basically
// just a pure Objective-C defined class.
#interface Loader : NSObject
#end
#implementation Loader : NSObject
+ (void)load { [Foo swiftyLoad]; }
+ (void)initialize { [Foo swiftyInitialize]; }
#end
# Foo.swift
import Foundation
public class Foo: NSObject {
#objc public static func swiftyLoad() {
Swift.print("Rock 'n' roll!")
}
#objc public static func swiftyInitialize() {
Swift.print("Hello initialize!")
}
}
The best part there's no need to mess with bridging headers or anything else, it just works. There are couple of gotchas, especially when using this approach in static libraries, check out the complete post on Medium for details! ✌️
in Swift 2.0, Please use this method
public override class func initialize()
What is the equivalent in Swift?
There is none.
Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.
Source: "The Swift Programming Language", by Apple
Types simply don't have an initializer in Swift. As several answers here suggest, you may be able to work around that by using the Objective-C bridge and having your Swift class inherit from NSObject or by using a Objective-C loader bootstrap code but if you have a 100% Swift project, you basically only have two options:
Manually initialize your classes somewhere within the code:
class MyCoolClass {
static func initClass ( ) {
// Do your init stuff here...
}
}
// Somewhere in a method/function that is guaranteed to be called
// on app startup or at some other relevant event:
MyCoolClass.initClass()
Use a run once pattern in all methods that require an initialized class:
class MyCoolClass {
private static var classInitialized: Bool = {
// Do your init stuff here...
// This code will for sure only run once!
return true
}()
private static func ensureClassIsInitialized ( ) {
_ = self.classInitialized
}
func whateverA ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
func whateverB ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
func whateverC ( ... ) {
MyCoolClass.ensureClassIsInitialized()
// Do something...
}
}
This follows a clean pattern that no code in Swift runs "automagically", code only runs if other code instructs it to run, which provides a clear and trackable code flow.
There might be a better solution for special cases yet you have not provided any information as for why you need that feature to begin with.
The abilitity to use this method was removed in this PR.
It provides some rationale:
Swift's language model doesn't guarantee that type metadata will ever really be used, which makes overriding initialize() error-prone and not really any better than manually invoking an initialization function. Warn about this for Swift 3 compatibility and reject attempts to override +initialize in Swift 4.
For Swift 2 or 3 (i.e. post-Swift 1.2), you can use:
class MySwiftClass: NSObject {
internal override class func initialize() {
DoStuff()
super.initialize()
}
}
But, as you can see, your class needs to inherit (directly or indirectly) form NSObject. This is required because the initialize() is called by the ObjC runtime.
And the initialize() method will only be called when MySwiftClass is referenced. So it will not be as magic as load().
But it will also be safer. For example: including a framework (let's say, by just adding it to your Podfile) won't allow the framework to mysteriously start to behave as soon as your app launches, without the need to add a single line of code to your project (at least… I hope! 😉).
I get conclusion for swift 1.2~5 doable ways:
Doable |swift-load |swift-initialze|bridgeToOC-load|bridgeToOC-initialze|
--- |---- |--- |--- |--- |
swift1.2~4.2|X |O |O |O |
swift4.2~5.0|X |X |O |O |
swift5.0~? |X |X |X |O |
and how to make it ? check here
https://medium.com/post-mortem/using-nsobjects-load-and-initialize-from-swift-f6f2c6d9aad0
but I think here is another way to make it swifter. use runtime and protol without load/initialze.
http://jordansmith.io/handling-the-deprecation-of-initialize/
Swift 5
Method 'load()' defines Objective-C class method 'load', which is not permitted by Swift
You can not override load() method now but there is a legal way to call your custom swift_load() method of your swift classes on app startup. It is needed to make ObjC class which redirect its own load() method to your swift classes.
Just add next SwiftLoader.m file to your project:
// SwiftLoader.m
#interface SwiftLoader : NSObject
#end
#implementation SwiftLoader
+ (void)load {
SEL selector = #selector(swift_load);
int numClasses = objc_getClassList(NULL, 0);
Class* classes = (Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class class = classes[i];
Method method = class_getClassMethod(class, selector);
if (method != NULL) {
IMP imp = method_getImplementation(method);
((id (*)(Class, SEL))imp)(class, selector);
}
}
free(classes);
}
Now you can add swift_load class method to any of your swift classes to make setup tasks:
class MyClass {
#objc
class func swift_load() {
print("load")
}
}

How to declare a constant in swift that can be used in objective c

if I declare the swift constant as a global constant like:
let a = "123"
but the a cannot be found in objective c.
How to solve this?
From Apple Doc:
You’ll have access to anything within a class or protocol that’s marked with the #objc attribute as long as it’s compatible with Objective-C. This excludes Swift-only features such as those listed here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Therefore its not possible to access global variables(Constants) or global functions defined in Swift.
Possible Solutions:
From the Apple Document Swift programming language, You can Declare Type Properties as
class var constant: Int = {
return 10
}()
But currently in Swift(beta-3) Type properties are not supported.
You can declare a Class function to get a constant value:
In Swift:
class func myConst() -> String {
return "Your constant"
}
Accessing from Objective-C:
NSString *constantValue = [ClassName myConst];
NSLog(#"%#", constantValue);
Swift code:
public class MyClass: NSObject {
public static let myConst = "aConst"
}
and then in Objective-C:
[MyClass myConst]
Isn't this working as well? As in this works for me.
Also this is somewhat shorter as creating a object first (alloc, init). Making a new function for every constant is... not pretty :/
Update for Swift 4
Because of the changes in Swift 4's Objective-C inference, you need to add the #objc annotation to the declared constant as well. The previous declaration then becomes:
#objcMembers
public class MyClass: NSObject {
public static let myConst = "aConst"
}
The calling Objective-C code remains the same.
Using #objcMembers makes all constants available (as if you'd write #objc before each constant), but I've had times where the compiler somehow wouldn't generate the corresponding ObjC code.
In those cases I'd suggest adding the #objc decorator before the constant as well.
I.e.: #objc public static let myConst = "aConst"
You should not have any problem by using let in Objective-C, next example was made with Xcode 7.2 :
MyClass.swift
import Foundation
import UIKit
#objc class MyClass : NSObject { // <== #objc AND NSObject ARE BOTH NECESSARY!!!
let my_color = UIColor( red:128/255,green:32/255,blue:64/255,alpha:1 ) // <== CONSTANT!!!
}
MyObjectiveC.m
#import "PROJECTNAME-Swift.h" // <== NECESSARY TO RECOGNIZE SWIFT CLASSES!!!
#interface MyObjectiveC ()
#end
#implementation MyObjectiveC
#synthesize tableview; // <== ANY UI OBJECT, JUST AS EXAMPLE!!!
- (void) viewDidLoad () {
MyClass * mc = [ [ MyClass alloc ] init ]; // <== INSTANTIATE SWIFT CLASS!!!
tableview.backgroundColor = mc.my_color; // <== USE THE CONSTANT!!!
}
#end
PROJECTNAME is the name of your Xcode project, as shown in Project Navigator.
In your swift class,
let constant: Float = -1
class YourClass: NSObject {
class func getMyConstant() -> Float {return constant}
...
}
Clean, build to let xcode prepare this method useable for obj-c.
Then at your obj-c class
if ([YourClass getMyConstant] != 0) {
...
}
First of all you need to know about the important of auto-generated Swift header file.
It is the one that will made the magic to transcribe the Swift code to be understandable from Objective-C.
This file is auto-generated by Xcode (do not look for it in your project).
The important of this file is to use the correct name, it can match with your target name, but, may not, it is the product module name. (Search for it in your project settings as "Product module")
You need to import this file on the Objective-C class that you want to use a Swift class and also the Swift class name of your Swift file.
#import <ProductModuleName-Swift.h>
#class MySwiftClassName;
My Swift class should have the prefix #objc and inherit from NSObject:
#objc class MySwiftClassName: NSObject {
let mySwiftVar = "123"
}
Then you can call your Swift variable from the Objective-C file:
MySwiftClassName *mySwiftClassO = [[MySwiftClassName alloc] init];
NSString *myVar = mySwiftClassO.mySwiftVar;
Make sure to clean and rebuild your project after each change to force regenerate this auto-generated file.
If your Swift header file was auto-generated correctly you can navigate to it by clicking over the import file name and check if all the code you need was properly transcribed.
In the following post you can find more detailed information about this. https://solidgeargroup.com/bridging-swift-objective-c
Classes func don't work. The only solution I have found out is this one:
class YourController: NSObject {
#objc static let shared = YourController()
private override init() { }
#objc class func sharedInstance() -> YourController {
return YourController.shared
}
#objc let terms = "Your-String-here"
And then on Obj-c file:
[[YourController sharedInstance].terms]